DSA โ
Zero to Interview
Ready
Every data structure and algorithm topic that appears in Indian tech interviews โ from basic arrays to dynamic programming. Covers every pattern asked at Amazon, Google, Flipkart, Swiggy, and 1-10 year experience rounds. With complexity analysis, templates, and problem patterns.
Big O Notation & Complexity Analysis
Big O describes how an algorithm's time or space grows as input size (n) grows. Every single interview requires you to state the complexity of your solution. This is non-negotiable.
| Complexity | Name | Example | n=1000 |
|---|---|---|---|
| O(1) | Constant | Array index access, HashMap get/set | 1 op |
| O(log n) | Logarithmic | Binary search, balanced BST operations | ~10 ops |
| O(n) | Linear | Array scan, linked list traversal | 1,000 ops |
| O(n log n) | Linearithmic | Merge sort, heap sort, good sorting | ~10,000 ops |
| O(nยฒ) | Quadratic | Bubble/insertion sort, nested loops | 1,000,000 ops |
| O(2โฟ) | Exponential | Recursive Fibonacci, subset generation | 10ยณโฐยน ops โ avoid! |
| O(n!) | Factorial | Permutations, TSP brute force | astronomical โ never |
// O(1) โ constant, no matter size of input const get = (arr, i) => arr[i]; const setMap = (map, k, v) => map.set(k, v); // O(n) โ one pass through array const sum = (arr) => arr.reduce((a, b) => a + b, 0); // O(nยฒ) โ nested loops over same input function hasDupe(arr) { for (let i = 0; i < arr.length; i++) // n for (let j = i + 1; j < arr.length; j++) // n-1, n-2... if (arr[i] === arr[j]) return true; // โ O(nยฒ) return false; } // Better: O(n) with HashSet const hasDupeO1 = (arr) => arr.length !== new Set(arr).size; // O(log n) โ halving the input each step function binarySearch(arr, target) { let lo = 0, hi = arr.length - 1; while (lo <= hi) { const mid = lo + ((hi - lo) >> 1); // avoid overflow if (arr[mid] === target) return mid; arr[mid] < target ? lo = mid + 1 : hi = mid - 1; } return -1; } // โ O(log n) time, O(1) space // Space complexity โ memory used // O(1): variables only. O(n): array/map proportional to input. // Recursive calls use O(depth) stack space!
Arrays & Strings
Arrays are the most tested data structure. Almost every problem involves arrays. Master these operations, patterns, and built-in methods cold.
| Operation | Array Time | What happens |
|---|---|---|
| Access arr[i] | O(1) | Direct memory offset calculation |
| Search (unsorted) | O(n) | Must check every element |
| Search (sorted) | O(log n) | Binary search possible |
| Insert at end | O(1) amortized | Dynamic array may resize occasionally |
| Insert at start/mid | O(n) | Must shift all elements right |
| Delete at end | O(1) | Just decrement length |
| Delete at start/mid | O(n) | Must shift all elements left |
// โโโ Prefix Sum โ range query in O(1) after O(n) prep โโโโโ function buildPrefix(arr) { const prefix = [0]; for (const n of arr) prefix.push(prefix.at(-1) + n); return prefix; } // rangeSum(i, j) = prefix[j+1] - prefix[i] โ O(1) per query! // โโโ Kadane's Algorithm โ Maximum Subarray Sum O(n) โโโโโโโ function maxSubarray(nums) { let maxSum = -Infinity, curr = 0; for (const n of nums) { curr = Math.max(n, curr + n); // extend or restart maxSum = Math.max(maxSum, curr); } return maxSum; } // โโโ Dutch National Flag โ sort 0,1,2 in O(n) one pass โโโโ function sortColors(nums) { let lo = 0, mid = 0, hi = nums.length - 1; while (mid <= hi) { if (nums[mid] === 0) [nums[lo++], nums[mid++]] = [nums[mid], nums[lo]]; else if (nums[mid] === 2) [nums[mid], nums[hi--]] = [nums[hi], nums[mid]]; else mid++; } } // โโโ String: Anagram check O(n) โโโโโโโโโโโโโโโโโโโโโโโโโโโ function isAnagram(s, t) { if (s.length !== t.length) return false; const count = new Array(26).fill(0); for (let i = 0; i < s.length; i++) { count[s.charCodeAt(i) - 97]++; count[t.charCodeAt(i) - 97]--; } return count.every(c => c === 0); }
Linked Lists
Linked lists test pointer manipulation under pressure. The classic interview questions (reverse, detect cycle, find middle, merge sorted lists) follow predictable patterns. Learn the patterns, not the problems.
class ListNode { constructor(val, next=null) { this.val=val; this.next=next; } } // โโโ Reverse Linked List O(n) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ function reverse(head) { let prev = null, curr = head; while (curr) { const next = curr.next; // save curr.next = prev; // reverse pointer prev = curr; // advance prev curr = next; // advance curr } return prev; } // โโโ Detect Cycle โ Floyd's Tortoise & Hare O(n) O(1) โโโโ function hasCycle(head) { let slow = head, fast = head; while (fast?.next) { slow = slow.next; // 1 step fast = fast.next.next; // 2 steps if (slow === fast) return true; // they meet โ cycle! } return false; } // โโโ Find Middle โ slow/fast pointers โโโโโโโโโโโโโโโโโโโโ function findMiddle(head) { let slow = head, fast = head; while (fast?.next) { slow = slow.next; fast = fast.next.next; } return slow; // slow is at middle when fast reaches end } // โโโ Merge Two Sorted Lists O(n+m) โโโโโโโโโโโโโโโโโโโโโโโ function mergeSorted(l1, l2) { const dummy = new ListNode(0); // dummy head trick let curr = dummy; while (l1 && l2) { if (l1.val <= l2.val) { curr.next = l1; l1 = l1.next; } else { curr.next = l2; l2 = l2.next; } curr = curr.next; } curr.next = l1 ?? l2; return dummy.next; } // โโโ Remove Nth From End โ two-pointer gap trick โโโโโโโโโโ function removeNthFromEnd(head, n) { const dummy = new ListNode(0, head); let fast = dummy, slow = dummy; for (let i = 0; i <= n; i++) fast = fast.next; // gap of n+1 while (fast) { fast = fast.next; slow = slow.next; } slow.next = slow.next.next; // skip the nth node return dummy.next; }
Stacks & Queues
Stack: Last In First Out (LIFO). Queue: First In First Out (FIFO). Monotonic stack is the most powerful pattern for "next greater element" family of problems โ very common in interviews.
// โโโ Valid Parentheses โ classic stack problem โโโโโโโโโโโโ function isValid(s) { const stack = [], map = { ')': '(', ']': '[', '}': '{' }; for (const ch of s) { if ('([{'.includes(ch)) stack.push(ch); else if (stack.pop() !== map[ch]) return false; } return stack.length === 0; } // โโโ Monotonic Stack โ Next Greater Element O(n) โโโโโโโโโโ // TEMPLATE: maintains stack in decreasing order function nextGreater(nums) { const result = new Array(nums.length).fill(-1); const stack = []; // stores INDICES for (let i = 0; i < nums.length; i++) { while (stack.length && nums[i] > nums[stack.at(-1)]) { result[stack.pop()] = nums[i]; // current is next greater for popped } stack.push(i); } return result; } // Same pattern: Largest Rectangle in Histogram, Daily Temperatures, Stock Span // โโโ Queue with two stacks (implement queue using stacks) โ class MyQueue { in = []; out = []; push(x) { this.in.push(x); } pop() { if (!this.out.length) while (this.in.length) this.out.push(this.in.pop()); return this.out.pop(); } } // โโโ Sliding Window Maximum โ deque O(n) โโโโโโโโโโโโโโโโโ function maxSlidingWindow(nums, k) { const deque = [], result = []; for (let i = 0; i < nums.length; i++) { if (deque[0] < i - k + 1) deque.shift(); // remove out-of-window while (deque.length && nums[deque.at(-1)] < nums[i]) deque.pop(); deque.push(i); if (i >= k - 1) result.push(nums[deque[0]]); } return result; }
Hash Maps & Sets
Hash maps are the most powerful tool for reducing time complexity. If you see an O(nยฒ) solution, the fix is usually a HashMap. Every "two-sum" style problem uses this pattern.
// โโโ Two Sum โ the canonical HashMap problem O(n) โโโโโโโโโ function twoSum(nums, target) { const seen = new Map(); // value โ index for (let i = 0; i < nums.length; i++) { const complement = target - nums[i]; if (seen.has(complement)) return [seen.get(complement), i]; seen.set(nums[i], i); } } // Key insight: store what you've SEEN, look for what you NEED // โโโ Frequency counting โ group anagrams O(n*k) โโโโโโโโโโ function groupAnagrams(strs) { const map = new Map(); for (const s of strs) { const key = [...s].sort().join(''); // sorted string = canonical form if (!map.has(key)) map.set(key, []); map.get(key).push(s); } return [...map.values()]; } // โโโ LRU Cache โ HashMap + Doubly Linked List O(1) โโโโโโโโ class LRUCache { constructor(capacity) { this.cap = capacity; this.map = new Map(); // Map preserves INSERTION ORDER in JS! } get(key) { if (!this.map.has(key)) return -1; const val = this.map.get(key); this.map.delete(key); this.map.set(key, val); // move to end = most recent return val; } put(key, value) { this.map.delete(key); if (this.map.size === this.cap) this.map.delete(this.map.keys().next().value); this.map.set(key, value); } }
Binary Trees & BST
Tree problems make up 25-30% of coding interview questions. DFS (pre/in/post-order), BFS (level-order), and BST properties are the core patterns. Learn the recursive template and apply it to any tree problem.
class TreeNode { constructor(val, left=null, right=null) { this.val=val; this.left=left; this.right=right; } } // โโโ DFS Traversals (recursive) โโโโโโโโโโโโโโโโโโโโโโโโโโโ const preorder = node => node ? [node.val, ...preorder(node.left), ...preorder(node.right)] : []; const inorder = node => node ? [...inorder(node.left), node.val, ...inorder(node.right)] : []; const postorder = node => node ? [...postorder(node.left), ...postorder(node.right), node.val] : []; // BST inorder = SORTED array (crucial property!) // โโโ BFS / Level Order Traversal โโโโโโโโโโโโโโโโโโโโโโโโโโ function levelOrder(root) { if (!root) return []; const result = [], queue = [root]; while (queue.length) { const level = [], size = queue.length; for (let i = 0; i < size; i++) { const node = queue.shift(); level.push(node.val); if (node.left) queue.push(node.left); if (node.right) queue.push(node.right); } result.push(level); } return result; } // โโโ Height / Depth O(n) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ const height = node => node ? 1 + Math.max(height(node.left), height(node.right)) : 0; // โโโ Lowest Common Ancestor (LCA) โโโโโโโโโโโโโโโโโโโโโโโโ function lca(root, p, q) { if (!root || root === p || root === q) return root; const left = lca(root.left, p, q); const right = lca(root.right, p, q); return left && right ? root : left ?? right; // both found = LCA } // โโโ Validate BST โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ function isValidBST(node, min=-Infinity, max=Infinity) { if (!node) return true; if (node.val <= min || node.val >= max) return false; return isValidBST(node.left, min, node.val) && isValidBST(node.right, node.val, max); } // โโโ Diameter of Binary Tree โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ function diameterOfBinaryTree(root) { let max = 0; function depth(node) { if (!node) return 0; const l = depth(node.left), r = depth(node.right); max = Math.max(max, l + r); // path through this node return 1 + Math.max(l, r); // height from this node } depth(root); return max; }
Heaps & Priority Queue
A heap is a complete binary tree where parent is always greater (max-heap) or smaller (min-heap) than children. Used for: Kth largest/smallest, Top K elements, Merge K sorted lists, Median of stream.
// โโโ Kth Largest โ min-heap of size k โโโโโโโโโโโโโโโโโโโโโ // Keep a min-heap. If size > k, pop the min. // After all elements, root = kth largest. // O(n log k) time โ better than sort O(n log n) for small k // โโโ Top K Frequent Elements O(n log k) โโโโโโโโโโโโโโโโโโโ function topKFrequent(nums, k) { const freq = new Map(); for (const n of nums) freq.set(n, (freq.get(n) ?? 0) + 1); // Bucket sort by frequency โ O(n) approach! const buckets = new Array(nums.length + 1).fill(null).map(() => []); freq.forEach((count, num) => buckets[count].push(num)); const result = []; for (let i = buckets.length - 1; i >= 0 && result.length < k; i--) result.push(...buckets[i]); return result.slice(0, k); } // โโโ Median of Data Stream โ two heaps trick โโโโโโโโโโโโโโ // Lower half: max-heap (top = max of lower) // Upper half: min-heap (top = min of upper) // Balance sizes: |lowerHeap| - |upperHeap| โค 1 // Median = avg of tops (even) or top of larger heap (odd)
Graphs, BFS & DFS
Graphs are networks of nodes and edges. Real-world applications: social networks, maps, dependencies. BFS for shortest paths, DFS for exploring all paths. Union-Find for connected components. Topological sort for dependencies.
// โโโ Graph representation โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ const graph = new Map(); // adjacency list โ most common graph.set('A', ['B', 'C']); graph.set('B', ['A', 'D']); // โโโ BFS โ shortest path in UNWEIGHTED graph O(V+E) โโโโโโโ function bfs(graph, start) { const visited = new Set([start]); const queue = [start]; while (queue.length) { const node = queue.shift(); for (const neighbor of graph.get(node) ?? []) { if (!visited.has(neighbor)) { visited.add(neighbor); queue.push(neighbor); } } } } // โโโ DFS โ iterative โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ function dfs(graph, start) { const visited = new Set(), stack = [start]; while (stack.length) { const node = stack.pop(); if (visited.has(node)) continue; visited.add(node); for (const n of graph.get(node) ?? []) stack.push(n); } } // โโโ Number of Islands โ DFS on grid O(m*n) โโโโโโโโโโโโโโ function numIslands(grid) { let count = 0; function dfs(r, c) { if (r<0||c<0||r>=grid.length||c>=grid[0].length||grid[r][c]!=='1') return; grid[r][c] = '0'; // mark visited by mutating [[1,0],[-1,0],[0,1],[0,-1]].forEach(([dr,dc]) => dfs(r+dr, c+dc)); } for (let r=0; r<grid.length; r++) for (let c=0; c<grid[0].length; c++) if (grid[r][c]==='1') { dfs(r,c); count++; } return count; } // โโโ Topological Sort (Kahn's BFS) โ for DAG dependencies โ function topoSort(n, edges) { const adj = Array.from({length: n}, () => []); const inDegree = new Array(n).fill(0); edges.forEach(([u,v]) => { adj[u].push(v); inDegree[v]++; }); const queue = [], result = []; for (let i=0; i<n; i++) if (!inDegree[i]) queue.push(i); while (queue.length) { const node = queue.shift(); result.push(node); for (const next of adj[node]) if (!--inDegree[next]) queue.push(next); } return result.length === n ? result : []; // empty = cycle exists }
Sorting Algorithms
| Algorithm | Best | Average | Worst | Space | Stable? |
|---|---|---|---|---|---|
| Bubble Sort | O(n) | O(nยฒ) | O(nยฒ) | O(1) | Yes |
| Selection Sort | O(nยฒ) | O(nยฒ) | O(nยฒ) | O(1) | No |
| Insertion Sort | O(n) | O(nยฒ) | O(nยฒ) | O(1) | Yes โ good for small/nearly sorted |
| Merge Sort | O(n log n) | O(n log n) | O(n log n) | O(n) | Yes โ stable, predictable |
| Quick Sort | O(n log n) | O(n log n) | O(nยฒ) | O(log n) | No โ fastest in practice |
| Heap Sort | O(n log n) | O(n log n) | O(n log n) | O(1) | No โ guaranteed O(n log n) |
| Counting Sort | O(n+k) | O(n+k) | O(n+k) | O(k) | Yes โ only for integers in range |
// โโโ Merge Sort โ stable, O(n log n) guaranteed โโโโโโโโโโโ function mergeSort(arr) { if (arr.length <= 1) return arr; const mid = arr.length >> 1; const left = mergeSort(arr.slice(0, mid)); const right = mergeSort(arr.slice(mid)); return merge(left, right); } function merge(l, r) { const result = []; let i = 0, j = 0; while (i < l.length && j < r.length) result.push(l[i] <= r[j] ? l[i++] : r[j++]); return [...result, ...l.slice(i), ...r.slice(j)]; } // โโโ Quick Sort โ fastest in practice, O(n log n) avg โโโโโ function quickSort(arr, lo=0, hi=arr.length-1) { if (lo < hi) { const pi = partition(arr, lo, hi); quickSort(arr, lo, pi-1); quickSort(arr, pi+1, hi); } return arr; } function partition(arr, lo, hi) { const pivot = arr[hi]; let i = lo; for (let j = lo; j < hi; j++) if (arr[j] < pivot) [arr[i++], arr[j]] = [arr[j], arr[i]]; [arr[i], arr[hi]] = [arr[hi], arr[i]]; return i; }
Binary Search โ All Variants
Binary search is not just for "find element in sorted array." The pattern extends to: find first/last occurrence, search in rotated array, find answer in a range of values (minimization problems). Master all variants.
// โโโ Template 1: Basic โ find exact target โโโโโโโโโโโโโโโโ function search(arr, target) { let lo = 0, hi = arr.length - 1; while (lo <= hi) { const mid = lo + ((hi - lo) >> 1); if (arr[mid] === target) return mid; arr[mid] < target ? lo = mid + 1 : hi = mid - 1; } return -1; } // โโโ Template 2: Find first position satisfying condition โ function firstTrue(arr, condition) { let lo = 0, hi = arr.length - 1, ans = -1; while (lo <= hi) { const mid = lo + ((hi - lo) >> 1); if (condition(arr[mid])) { ans = mid; hi = mid - 1; } // found, go left for first else lo = mid + 1; } return ans; } // โโโ Search in Rotated Sorted Array โโโโโโโโโโโโโโโโโโโโโโ function searchRotated(nums, target) { let lo = 0, hi = nums.length - 1; while (lo <= hi) { const mid = lo + ((hi - lo) >> 1); if (nums[mid] === target) return mid; if (nums[lo] <= nums[mid]) { // left half is sorted nums[lo] <= target && target < nums[mid] ? hi = mid-1 : lo = mid+1; } else { // right half is sorted nums[mid] < target && target <= nums[hi] ? lo = mid+1 : hi = mid-1; } } return -1; } // โโโ Binary search on ANSWER โ minimize max / maximize min // "What is the minimum capacity to ship packages in D days?" // lo = max(packages), hi = sum(packages) // Binary search on capacity, check if feasible in D days // Pattern: can(mid) โ search in answer space, not array space!
canDo(mid), binary search on the answer, find the smallest/largest valid answer. This pattern appears in 30%+ of hard problems.Two Pointers & Sliding Window
Two of the most powerful O(n) patterns. Two pointers works when elements are sorted or you need to find pairs. Sliding window finds optimal subarrays/substrings with a constraint.
// โโโ Two Pointers โ sorted array pair sum โโโโโโโโโโโโโโโโโ function twoSumSorted(nums, target) { let lo = 0, hi = nums.length - 1; while (lo < hi) { const sum = nums[lo] + nums[hi]; if (sum === target) return [lo, hi]; sum < target ? lo++ : hi--; } } // โโโ 3Sum โ reduce to 2Sum with outer loop O(nยฒ) โโโโโโโโโ function threeSum(nums) { nums.sort((a,b) => a-b); const result = []; for (let i = 0; i < nums.length - 2; i++) { if (i > 0 && nums[i] === nums[i-1]) continue; // skip duplicates let lo = i+1, hi = nums.length-1; while (lo < hi) { const sum = nums[i] + nums[lo] + nums[hi]; if (sum === 0) { result.push([nums[i],nums[lo++],nums[hi--]]); } else if (sum < 0) lo++; else hi--; } } return result; } // โโโ Sliding Window TEMPLATE โ variable size โโโโโโโโโโโโโโ function slidingWindow(s, target) { let lo = 0, best = Infinity; const window = {}; // track window state for (let hi = 0; hi < s.length; hi++) { // 1. Expand: add s[hi] to window window[s[hi]] = (window[s[hi]] ?? 0) + 1; // 2. Shrink: while condition violated, remove s[lo] and advance lo while (/* condition violated */ false) { window[s[lo]]--; if (!window[s[lo]]) delete window[s[lo]]; lo++; } // 3. Update answer best = Math.min(best, hi - lo + 1); } return best; } // Apply to: Minimum Window Substring, Longest Without Repeating, // Longest Repeating After Replacement, Fruit Into Baskets
Recursion & Backtracking
Backtracking = recursion + pruning. For every "generate all X" problem: choose, explore, unchoose. The template is the same for all problems โ permutations, combinations, subsets, N-Queens, Sudoku.
// โโโ BACKTRACKING TEMPLATE โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ function backtrack(result, current, ...params) { if (baseCaseReached) { result.push([...current]); return; } for (const choice of choices) { current.push(choice); // CHOOSE backtrack(result, current, ...newParams); // EXPLORE current.pop(); // UNCHOOSE (backtrack) } } // โโโ Permutations โ O(n * n!) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโ function permute(nums) { const result = []; const bt = (curr, used) => { if (curr.length === nums.length) { result.push([...curr]); return; } for (let i = 0; i < nums.length; i++) { if (used.has(i)) continue; curr.push(nums[i]); used.add(i); bt(curr, used); curr.pop(); used.delete(i); } }; bt([], new Set()); return result; } // โโโ Subsets โ O(n * 2โฟ) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ function subsets(nums) { const result = []; const bt = (start, curr) => { result.push([...curr]); for (let i = start; i < nums.length; i++) { curr.push(nums[i]); bt(i + 1, curr); curr.pop(); } }; bt(0, []); return result; } // โโโ Combination Sum โ O(n^(target/min)) โโโโโโโโโโโโโโโโโ function combinationSum(candidates, target) { const result = []; const bt = (start, curr, remaining) => { if (remaining === 0) { result.push([...curr]); return; } for (let i = start; i < candidates.length; i++) { if (candidates[i] > remaining) continue; // prune! curr.push(candidates[i]); bt(i, curr, remaining - candidates[i]); // can reuse same index curr.pop(); } }; candidates.sort((a,b) => a-b); // sort enables pruning bt(0, [], target); return result; }
Dynamic Programming
DP = breaking problems into overlapping subproblems + caching. Every DP problem follows two approaches: Top-Down (memoization/recursion) and Bottom-Up (tabulation/iteration). Learn the patterns โ there are only a few dozen canonical DP problems.
// โโโ Fibonacci โ both approaches โโโโโโโโโโโโโโโโโโโโโโโโโ // Top-down: const memo = new Map(); const fib = n => n <= 1 ? n : (memo.get(n) ?? memo.set(n, fib(n-1) + fib(n-2)) && memo.get(n)); // Bottom-up O(n) time O(1) space: function fibDP(n) { if (n<=1) return n; let [a,b] = [0,1]; for (let i=2; i<=n; i++) [a,b] = [b, a+b]; return b; } // โโโ 0/1 Knapsack โ O(n*W) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ function knapsack(weights, values, W) { const n = weights.length; const dp = new Array(W+1).fill(0); // space optimized to 1D for (let i=0; i<n; i++) for (let w=W; w>=weights[i]; w--) // iterate BACKWARDS for 0/1 dp[w] = Math.max(dp[w], dp[w-weights[i]] + values[i]); return dp[W]; } // โโโ Longest Common Subsequence O(m*n) โโโโโโโโโโโโโโโโโโโ function lcs(s1, s2) { const m=s1.length, n=s2.length; const dp = Array.from({length:m+1}, () => new Array(n+1).fill(0)); for (let i=1; i<=m; i++) for (let j=1; j<=n; j++) dp[i][j] = s1[i-1]===s2[j-1] ? dp[i-1][j-1]+1 : Math.max(dp[i-1][j],dp[i][j-1]); return dp[m][n]; } // โโโ Coin Change โ minimum coins O(amount * coins) โโโโโโโโ function coinChange(coins, amount) { const dp = new Array(amount+1).fill(Infinity); dp[0] = 0; for (const coin of coins) for (let i=coin; i<=amount; i++) dp[i] = Math.min(dp[i], dp[i-coin]+1); return dp[amount] === Infinity ? -1 : dp[amount]; }
Greedy Algorithms
Greedy makes the locally optimal choice at each step hoping to reach the global optimum. Proving a greedy works requires an exchange argument. Classic greedy problems appear in every interview.
// โโโ Activity Selection / Non-overlapping Intervals โโโโโโโ function eraseOverlapIntervals(intervals) { intervals.sort((a,b) => a[1]-b[1]); // sort by END time let removed = 0, prevEnd = -Infinity; for (const [start, end] of intervals) { if (start < prevEnd) removed++; // overlap โ remove else prevEnd = end; // no overlap โ keep } return removed; } // โโโ Jump Game II โ minimum jumps O(n) โโโโโโโโโโโโโโโโโโโ function jump(nums) { let jumps = 0, curEnd = 0, farthest = 0; for (let i = 0; i < nums.length - 1; i++) { farthest = Math.max(farthest, i + nums[i]); if (i === curEnd) { jumps++; curEnd = farthest; } } return jumps; } // โโโ Task Scheduler โ greedy with counting โโโโโโโโโโโโโโโโ // Sort tasks by frequency. Most frequent fills the "frames". // Answer = max(tasks.length, (maxFreq-1) * (n+1) + tasksWithMaxFreq)
Problem Patterns & Recognition Guide
| If the problem says... | Think... | Complexity target |
|---|---|---|
| Find pair that sums to X | HashMap / Two Pointers (sorted) | O(n) |
| Subarray with max/min sum | Kadane's / Prefix Sum / Sliding Window | O(n) |
| Find duplicates / unique | HashSet / XOR trick | O(n) |
| Search in sorted array | Binary Search | O(log n) |
| Kth largest/smallest | Min-Heap of size k / QuickSelect | O(n log k) |
| All permutations/subsets | Backtracking | O(n! or 2โฟ) |
| Count ways / min steps | Dynamic Programming | O(nยฒ) typical |
| Tree path / structure | DFS recursion | O(n) |
| Shortest path (unweighted) | BFS | O(V+E) |
| Shortest path (weighted) | Dijkstra's (min-heap) | O(E log V) |
| Next greater element | Monotonic Stack | O(n) |
| Longest substring with K chars | Sliding Window | O(n) |
| Connected components | Union-Find / BFS/DFS | O(n ฮฑ(n)) |
| Schedule tasks / ordering | Topological Sort | O(V+E) |
You're ready when you can solve these in 20-30 minutes:
- State time AND space complexity for every solution you write
- Two Sum in O(n) with HashMap (not brute force O(nยฒ))
- Reverse a linked list, detect a cycle, find middle node
- BFS/DFS on a graph, count islands (grid problem)
- Binary search on sorted array AND on answer space
- Maximum subarray using Kadane's algorithm
- Valid parentheses and next greater element (monotonic stack)
- Write both top-down and bottom-up DP for coin change / climbing stairs
- Generate all subsets and permutations using backtracking
- Sliding window for longest substring without repeating chars
- Level-order traversal of binary tree using BFS