Fundamentals/Patterns/Data Structure Design10 problems· Design

Build a class supporting a defined set of operations efficiently

When to useLRU cache, median finder, trie, segment tree, design hit counter, twitter — any class-style problem with operation sequence

Queue Operationsdesign
class Queue {
constructor() {
this.items = [];
this.head = 0;
}
enqueue(value) {
this.items.push(value);
}
dequeue() {
if (this.head >= this.items.length) return undefined;
const value = this.items[this.head];
this.head++;
return value;
}
peek() {
return this.items[this.head];
}
size() {
return this.items.length - this.head;
}
isEmpty() {
return this.size() === 0;
}
}
Queue with Arraydesign
function runCircularQueue(capacity, operations) {
const buffer = new Array(capacity);
let head = 0;
let tail = 0;
let size = 0;
const results = [];
for (const op of operations) {
if (op[0] === 'enqueue') {
if (size === capacity) {
results.push(false);
} else {
buffer[tail] = op[1];
tail = (tail + 1) % capacity;
size += 1;
results.push(true);
}
} else if (op[0] === 'dequeue') {
if (size === 0) {
results.push(null);
} else {
const value = buffer[head];
head = (head + 1) % capacity;
size -= 1;
results.push(value);
}
}
}
return results;
}
Queue Introdesign
class Queue {
constructor() {
this.items = [];
this.head = 0;
}
enqueue(value) {
this.items.push(value);
}
dequeue() {
if (this.head >= this.items.length) return null;
const value = this.items[this.head];
this.items[this.head] = undefined;
this.head++;
return value;
}
size() {
return this.items.length - this.head;
}
}
Segment Tree Introductiondesign
class SegmentTree {
constructor(arr) {
this.n = arr.length;
this.tree = new Array(4 * this.n).fill(0);
if (this.n > 0) this._build(arr, 1, 0, this.n - 1);
}
_build(arr, node, start, end) {
if (start === end) {
this.tree[node] = arr[start];
return;
}
const mid = Math.floor((start + end) / 2);
this._build(arr, 2 * node, start, mid);
this._build(arr, 2 * node + 1, mid + 1, end);
this.tree[node] = this.tree[2 * node] + this.tree[2 * node + 1];
}
update(index, value) {
this._update(1, 0, this.n - 1, index, value);
}
_update(node, start, end, index, value) {
if (start === end) {
this.tree[node] = value;
return;
}
const mid = Math.floor((start + end) / 2);
if (index <= mid) {
this._update(2 * node, start, mid, index, value);
} else {
this._update(2 * node + 1, mid + 1, end, index, value);
}
this.tree[node] = this.tree[2 * node] + this.tree[2 * node + 1];
}
rangeSum(left, right) {
return this._query(1, 0, this.n - 1, left, right - 1);
}
_query(node, start, end, l, r) {
if (r < start || end < l) return 0;
if (l <= start && end <= r) return this.tree[node];
const mid = Math.floor((start + end) / 2);
return this._query(2 * node, start, mid, l, r)
+ this._query(2 * node + 1, mid + 1, end, l, r);
}
}
Trie Introductiondesign
class Trie {
constructor() {
this.root = { children: new Map(), end: false };
}
insert(word) {
let node = this.root;
for (const ch of word) {
if (!node.children.has(ch)) {
node.children.set(ch, { children: new Map(), end: false });
}
node = node.children.get(ch);
}
node.end = true;
}
search(word) {
const node = this._traverse(word);
return node !== null && node.end;
}
startsWith(prefix) {
return this._traverse(prefix) !== null;
}
_traverse(s) {
let node = this.root;
for (const ch of s) {
if (!node.children.has(ch)) return null;
node = node.children.get(ch);
}
return node;
}
}
Binary Search Tree Introdesign
class BST {
constructor() {
this.root = null;
}
insert(value) {
this.root = this._insert(this.root, value);
}
_insert(node, value) {
if (node === null) return { val: value, left: null, right: null };
if (value < node.val) {
node.left = this._insert(node.left, value);
} else if (value > node.val) {
node.right = this._insert(node.right, value);
}
return node;
}
contains(value) {
let node = this.root;
while (node !== null) {
if (value === node.val) return true;
node = value < node.val ? node.left : node.right;
}
return false;
}
inorder() {
const result = [];
function walk(node) {
if (node === null) return;
walk(node.left);
result.push(node.val);
walk(node.right);
}
walk(this.root);
return result;
}
}
LRU Cachedesign
class LRUCache {
constructor(capacity) {
this.capacity = capacity;
this.map = new Map(); // key -> node
// Dummy sentinel nodes
this.head = { key: null, val: null, prev: null, next: null };
this.tail = { key: null, val: null, prev: null, next: null };
this.head.next = this.tail;
this.tail.prev = this.head;
}
_removeNode(node) {
node.prev.next = node.next;
node.next.prev = node.prev;
}
_addToFront(node) {
node.next = this.head.next;
node.prev = this.head;
this.head.next.prev = node;
this.head.next = node;
}
get(key) {
if (!this.map.has(key)) return -1;
const node = this.map.get(key);
this._removeNode(node);
this._addToFront(node);
return node.val;
}
put(key, value) {
if (this.map.has(key)) {
const node = this.map.get(key);
node.val = value;
this._removeNode(node);
this._addToFront(node);
} else {
if (this.map.size >= this.capacity) {
const lru = this.tail.prev;
this._removeNode(lru);
this.map.delete(lru.key);
}
const node = { key, val: value, prev: null, next: null };
this._addToFront(node);
this.map.set(key, node);
}
}
}
Median of Data Streamdesign
class MedianFinder {
constructor() {
this.low = new MaxHeap();
this.high = new MinHeap();
}
addNum(num) {
if (this.low.size() === 0 || num <= this.low.peek()) {
this.low.push(num);
} else {
this.high.push(num);
}
if (this.low.size() > this.high.size() + 1) {
this.high.push(this.low.pop());
} else if (this.high.size() > this.low.size()) {
this.low.push(this.high.pop());
}
}
findMedian() {
if (this.low.size() > this.high.size()) {
return this.low.peek();
}
return (this.low.peek() + this.high.peek()) / 2;
}
}
class MaxHeap {
constructor() { this.data = []; }
size() { return this.data.length; }
peek() { return this.data[0]; }
push(value) {
this.data.push(value);
this._siftUp(this.data.length - 1);
}
pop() {
const top = this.data[0];
const last = this.data.pop();
if (this.data.length > 0) {
this.data[0] = last;
this._siftDown(0);
}
return top;
}
_siftUp(i) {
while (i > 0) {
const parent = Math.floor((i - 1) / 2);
if (this.data[parent] < this.data[i]) {
[this.data[parent], this.data[i]] = [this.data[i], this.data[parent]];
i = parent;
} else break;
}
}
_siftDown(i) {
const n = this.data.length;
while (true) {
const left = 2 * i + 1;
const right = 2 * i + 2;
let largest = i;
if (left < n && this.data[left] > this.data[largest]) largest = left;
if (right < n && this.data[right] > this.data[largest]) largest = right;
if (largest === i) break;
[this.data[i], this.data[largest]] = [this.data[largest], this.data[i]];
i = largest;
}
}
}
class MinHeap {
constructor() { this.data = []; }
size() { return this.data.length; }
peek() { return this.data[0]; }
push(value) {
this.data.push(value);
this._siftUp(this.data.length - 1);
}
pop() {
const top = this.data[0];
const last = this.data.pop();
if (this.data.length > 0) {
this.data[0] = last;
this._siftDown(0);
}
return top;
}
_siftUp(i) {
while (i > 0) {
const parent = Math.floor((i - 1) / 2);
if (this.data[parent] > this.data[i]) {
[this.data[parent], this.data[i]] = [this.data[i], this.data[parent]];
i = parent;
} else break;
}
}
_siftDown(i) {
const n = this.data.length;
while (true) {
const left = 2 * i + 1;
const right = 2 * i + 2;
let smallest = i;
if (left < n && this.data[left] < this.data[smallest]) smallest = left;
if (right < n && this.data[right] < this.data[smallest]) smallest = right;
if (smallest === i) break;
[this.data[i], this.data[smallest]] = [this.data[smallest], this.data[i]];
i = smallest;
}
}
}
Playing Cardsdesign
class Card {
get cardValue() {
throw new Error('Not implemented');
}
}
class PlayingCard extends Card {
static VALUES = {
'A': 1, '2': 2, '3': 3, '4': 4, '5': 5,
'6': 6, '7': 7, '8': 8, '9': 9, '10': 10,
'J': 11, 'Q': 12, 'K': 13,
};
static VALUE_NAMES = Object.fromEntries(
Object.entries(PlayingCard.VALUES).map(([k, v]) => [v, k])
);
constructor(suit, value) {
super();
this.suit = suit;
this._value = PlayingCard.VALUES[value];
}
get cardValue() {
return this._value;
}
toString() {
return `${PlayingCard.VALUE_NAMES[this._value]} of ${this.suit}`;
}
}
class Joker extends Card {
constructor(color) {
super();
this.color = color;
}
get cardValue() {
return 14;
}
toString() {
return `${this.color} Joker`;
}
}
class Hand {
constructor(cards) {
this.cards = [...cards];
}
toString() {
return this.cards.map(c => c.toString()).join(', ');
}
compareTo(other) {
const a = [...this.cards].sort((x, y) => y.cardValue - x.cardValue);
const b = [...other.cards].sort((x, y) => y.cardValue - x.cardValue);
for (let i = 0; i < Math.min(a.length, b.length); i++) {
const diff = a[i].cardValue - b[i].cardValue;
if (diff !== 0) return diff;
}
return 0;
}
}
class Game {
constructor() {
this.cards = [];
this.hands = [];
}
addCard(suit, value) {
this.cards.push(new PlayingCard(suit, value));
}
addJoker(color) {
this.cards.push(new Joker(color));
}
cardString(i) {
return this.cards[i].toString();
}
cardBeats(a, b) {
return this.cards[a].cardValue > this.cards[b].cardValue;
}
addHand(cardIndices) {
this.hands.push(new Hand(cardIndices.map(i => this.cards[i])));
}
handString(i) {
return this.hands[i].toString();
}
handBeats(a, b) {
return this.hands[a].compareTo(this.hands[b]) > 0;
}
}
Range Sum Query - Immutabledesign
class NumArray {
constructor(nums) {
this.prefix = new Array(nums.length + 1).fill(0);
for (let i = 0; i < nums.length; i++) {
this.prefix[i + 1] = this.prefix[i] + nums[i];
}
}
sumRange(left, right) {
return this.prefix[right + 1] - this.prefix[left];
}
}