let lo = 0, ans = 0;
for (let hi = 0; hi < n; hi++) {
// expand window with nums[hi]
while (/* window invalid */) {
// shrink from left
lo++;
}
ans = Math.max(ans, hi - lo + 1);
}
▸Sort order is preserved in window
▸Exactly k = atMost(k) - atMost(k-1)
▸Variable window: while loop to shrink; fixed: slide both ends
let lo = 0, hi = n - 1;
while (lo < hi) {
const mid = (lo + hi) >> 1;
if (check(mid)) hi = mid; // mid is valid, try smaller
else lo = mid + 1; // mid is invalid, try larger
}
return lo; // first valid
▸lo < hi + return lo for "first valid"
▸lo <= hi + return -1 for "find exact"
▸Binary search on answer: check(mid) = "can we achieve mid?"
const visited = new Set([start]);
let queue = [start], level = 0;
while (queue.length) {
const next = [];
for (const node of queue) {
if (node === target) return level;
for (const nb of graph[node])
if (!visited.has(nb)) { visited.add(nb); next.push(nb); }
}
queue = next;
level++;
}
When: tree path, subtree value, diameter, max sum through node
let globalAns = -Infinity;
function dfs(node) {
if (!node) return 0;
const left = Math.max(0, dfs(node.left));
const right = Math.max(0, dfs(node.right));
// update answer: path THROUGH this node (uses both branches)
globalAns = Math.max(globalAns, left + node.val + right);
// return to parent: single branch only
return node.val + Math.max(left, right);
}
▸Return value = single branch going UP
▸Global update = path bending at this node (both children)
▸Clamp to 0 for max-sum; keep negatives for diameter/length
When: interval scheduling, activity selection, always take best now
// Activity selection: max non-overlapping intervals
intervals.sort((a, b) => a[1] - b[1]); // sort by END
let count = 0, lastEnd = -Infinity;
for (const [start, end] of intervals) {
if (start >= lastEnd) {
count++; // take this interval
lastEnd = end;
}
}
▸Interval problems: sort by END for max selection, START for merging
▸Prove greedy: exchange argument or induction
▸Heap-greedy: repeatedly take the locally best available choice
When: k-th largest/smallest, streaming top-k, merge k sorted
// Top-k smallest: max-heap of size k
const heap = new MaxHeap();
for (const num of nums) {
heap.push(num);
if (heap.size() > k) heap.pop(); // remove largest
}
return heap.top(); // kth smallest
// k-way merge: min-heap with source index
const pq = new MinHeap(); // [val, listIdx, elemIdx]
for (let i = 0; i < lists.length; i++)
if (lists[i]) pq.push([lists[i].val, i, lists[i]]);
▸Top-k smallest: max-heap of size k (pop when > k)
When: next greater element, span, rectangle, trapped water
const stack = []; // indices, in decreasing value order
const result = new Array(n).fill(-1);
for (let i = 0; i < n; i++) {
// pop elements that are smaller (for next greater)
while (stack.length && nums[stack.at(-1)] < nums[i]) {
result[stack.pop()] = i; // current i is "next greater"
}
stack.push(i);
}
When: XOR, subset enumeration, power of 2, single number
// Common bit tricks:
x & (x-1) // clear lowest set bit (check power of 2: x & (x-1) === 0)
x & (-x) // isolate lowest set bit
x ^ x === 0 // XOR of same number = 0
a ^ b ^ a === b // XOR is its own inverse
// Enumerate all subsets of mask
for (let sub = mask; sub > 0; sub = (sub-1) & mask) {
// process sub
}
▸XOR: a^a=0, a^0=a → find single number, missing number