📐 Complexity Analysis Varies / Varies
// Analyze nested loops
int total = 0;
for (int i = 0; i < n; i++) {        // O(n)
    for (int j = i; j < n; j++) {     // O(n - i)
        total++;                       // O(1)
    }
}
// Total: sum(n-i) for i=0..n-1 = O(n^2)
// Drop constants and lower-order terms
Use when
  • Analyzing algorithm performance
  • Comparing approaches
  • Interview complexity questions
  • Nested loop analysis
Watch out
  • Confusing average and worst case
  • Forgetting hidden costs (e.g., string concatenation is O(n))
  • Not accounting for space used by recursion stack
👉 Two Pointers O(n) / O(1)
int[] twoPointers(int[] arr, int target) {
    int left = 0, right = arr.length - 1;
    while (left < right) {
        int curr = arr[left] + arr[right];
        if (curr == target) return new int[]{left, right};
        else if (curr < target) left++;
        else right--;
    }
    return new int[]{};
}
Use when
  • Sorted array or linked list
  • Find a pair that satisfies a condition
  • Compare elements from both ends
  • In-place operation without extra space
Watch out
  • Forgetting to handle duplicates
  • Off-by-one with < vs <=
  • Assuming input is sorted
🪟 Sliding Window O(n) / O(k)
int slidingWindow(String s, int k) {
    Map<Character, Integer> counts = new HashMap<>();
    int left = 0, result = 0;
    for (int right = 0; right < s.length(); right++) {
        counts.merge(s.charAt(right), 1, Integer::sum);
        while (counts.size() > k) {
            char c = s.charAt(left);
            counts.merge(c, -1, Integer::sum);
            if (counts.get(c) == 0) counts.remove(c);
            left++;
        }
        result = Math.max(result, right - left + 1);
    }
    return result;
}
Use when
  • Contiguous subarray or substring
  • Longest/shortest with constraint
  • Fixed or variable window size
  • 'At most K distinct' or 'sum <= target'
Watch out
  • Shrinking too aggressively (while not if)
  • Not cleaning up map when count reaches 0
  • Confusing fixed-size vs variable-size windows
📊 Dynamic Programming O(n^2) typical / O(n)
int dpSolve(int[] nums) {
    int n = nums.length;
    int[] dp = new int[n];
    dp[0] = nums[0];
    for (int i = 1; i < n; i++) {
        dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
    }
    return Arrays.stream(dp).max().getAsInt();
}
Use when
  • Overlapping subproblems
  • Optimal substructure
  • 'Minimum cost' / 'maximum value' / 'number of ways'
  • Can express answer recursively
Watch out
  • Not identifying the right state
  • Missing base cases
  • Not considering space optimization (rolling array)
🔀 Backtracking O(2^n) or O(n!) / O(n)
void backtrack(List<Integer> path, int[] choices,
               List<List<Integer>> result) {
    if (isSolution(path)) {
        result.add(new ArrayList<>(path));
        return;
    }
    for (int choice : choices) {
        if (isValid(choice)) {
            path.add(choice);
            backtrack(path, nextChoices, result);
            path.remove(path.size() - 1); // undo
        }
    }
}
Use when
  • Generate all valid combinations/permutations
  • 'Find all solutions'
  • Constraint satisfaction
  • Decision tree exploration
Watch out
  • Forgetting to undo the choice (backtrack step)
  • Not pruning invalid branches early
  • Generating duplicates without sorting+skipping
🌳 Tree Traversal O(n) / O(h) DFS / O(w) BFS
void dfs(TreeNode node) {
    if (node == null) return;
    // preorder: process here
    dfs(node.left);
    // inorder: process here
    dfs(node.right);
    // postorder: process here
}
void bfs(TreeNode root) {
    Queue<TreeNode> queue = new LinkedList<>();
    queue.add(root);
    while (!queue.isEmpty()) {
        TreeNode node = queue.poll();
        if (node.left != null)  queue.add(node.left);
        if (node.right != null) queue.add(node.right);
    }
}
Use when
  • Process all nodes in a tree
  • Level-order traversal needed
  • Path from root to leaf
  • Compare left and right subtrees
Watch out
  • Not handling null nodes
  • Confusing pre/in/post-order
  • Using DFS when BFS is needed (shortest path in tree)
📚 Monotonic Stack O(n) / O(n)
int[] nextGreater(int[] nums) {
    int n = nums.length;
    int[] result = new int[n];
    Arrays.fill(result, -1);
    Deque<Integer> stack = new ArrayDeque<>();
    for (int i = 0; i < n; i++) {
        while (!stack.isEmpty() && nums[stack.peek()] < nums[i]) {
            result[stack.pop()] = nums[i];
        }
        stack.push(i);
    }
    return result;
}
Use when
  • Next greater/smaller element
  • Histogram/rectangle problems
  • Temperature/stock span problems
  • Removing elements that can't be optimal
Watch out
  • Wrong monotonic direction (increasing vs decreasing)
  • Forgetting to process remaining stack elements
  • Not storing indices (just values) when positions matter
🔄 In-Place Reversal O(n) / O(1)
ListNode reverseList(ListNode head) {
    ListNode prev = null, curr = head;
    while (curr != null) {
        ListNode next = curr.next;
        curr.next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
}
Use when
  • Reverse a linked list or sublist
  • Rearrange list nodes
  • Palindrome linked list check
  • Group reversal (k-group)
Watch out
  • Losing reference to next node before rewiring
  • Not handling head/tail connections for sublists
  • Off-by-one in k-group counting
🐢 Fast & Slow Pointers O(n) / O(1)
boolean hasCycle(ListNode head) {
    ListNode slow = head, fast = head;
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
        if (slow == fast) return true;
    }
    return false;
}
Use when
  • Detect cycle in linked list
  • Find middle of linked list
  • Find start of cycle
  • Happy number problem
Watch out
  • Not checking both fast and fast.next for null
  • Forgetting slow starts at head (not head.next)
  • Wrong meeting point logic for cycle start
Prefix Sum O(n) build, O(1) query / O(n)
int[] buildPrefix(int[] arr) {
    int[] prefix = new int[arr.length + 1];
    for (int i = 0; i < arr.length; i++) {
        prefix[i + 1] = prefix[i] + arr[i];
    }
    return prefix;
}
int rangeSum(int[] prefix, int l, int r) {
    return prefix[r + 1] - prefix[l];
}
Use when
  • Range sum queries
  • Subarray sum equals K
  • Count subarrays with given sum
  • Difference array for range updates
Watch out
  • Off-by-one in prefix indexing
  • Forgetting prefix[0] = 0
  • Not using hash map for subarray sum problems
🗺 Matrix Traversal O(m * n) / O(m * n)
void bfsGrid(int[][] grid, int sr, int sc) {
    int rows = grid.length, cols = grid[0].length;
    boolean[][] visited = new boolean[rows][cols];
    visited[sr][sc] = true;
    Queue<int[]> queue = new LinkedList<>();
    queue.add(new int[]{sr, sc});
    int[][] dirs = {{0,1},{1,0},{0,-1},{-1,0}};
    while (!queue.isEmpty()) {
        int[] cell = queue.poll();
        for (int[] d : dirs) {
            int nr = cell[0]+d[0], nc = cell[1]+d[1];
            if (nr >= 0 && nr < rows && nc >= 0
                && nc < cols && !visited[nr][nc]) {
                visited[nr][nc] = true;
                queue.add(new int[]{nr, nc});
            }
        }
    }
}
Use when
  • Count islands/regions
  • Shortest path in grid
  • Flood fill
  • Connected components in 2D
Watch out
  • Not marking visited before adding to queue (duplicate visits)
  • Forgetting diagonal directions when needed
  • Modifying input grid vs using visited set
🕸 DFS & BFS O(V + E) / O(V)
void bfs(Map<Integer, List<Integer>> graph, int start) {
    Set<Integer> visited = new HashSet<>();
    visited.add(start);
    Queue<Integer> queue = new LinkedList<>();
    queue.add(start);
    while (!queue.isEmpty()) {
        int node = queue.poll();
        for (int nb : graph.getOrDefault(node, List.of())) {
            if (visited.add(nb)) queue.add(nb);
        }
    }
}
void dfs(Map<Integer, List<Integer>> graph,
         int node, Set<Integer> visited) {
    visited.add(node);
    for (int nb : graph.getOrDefault(node, List.of())) {
        if (!visited.contains(nb)) dfs(graph, nb, visited);
    }
}
Use when
  • Find path between nodes
  • Connected components
  • Shortest path (unweighted)
  • Detect cycle in graph
Watch out
  • Not tracking visited nodes (infinite loop)
  • Using DFS for shortest path (use BFS)
  • Not handling disconnected components
📅 Overlapping Intervals O(n log n) / O(n)
int[][] mergeIntervals(int[][] intervals) {
    Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
    List<int[]> merged = new ArrayList<>();
    merged.add(intervals[0]);
    for (int i = 1; i < intervals.length; i++) {
        int[] last = merged.get(merged.size() - 1);
        if (intervals[i][0] <= last[1]) {
            last[1] = Math.max(last[1], intervals[i][1]);
        } else {
            merged.add(intervals[i]);
        }
    }
    return merged.toArray(new int[0][]);
}
Use when
  • Merge overlapping intervals
  • Insert interval into sorted list
  • Find gaps between intervals
  • Meeting rooms / minimum platforms
Watch out
  • Forgetting to sort first
  • Wrong merge condition (< vs <=)
  • Not updating end with max(prev.end, curr.end)
🏆 Top K Elements O(n log k) / O(k)
int[] topK(int[] nums, int k) {
    PriorityQueue<Integer> heap = new PriorityQueue<>();
    for (int num : nums) {
        heap.add(num);
        if (heap.size() > k) heap.poll();
    }
    return heap.stream().mapToInt(i -> i).toArray();
}
Use when
  • K largest/smallest elements
  • K most frequent
  • K closest points
  • Sort by frequency
Watch out
  • Using max-heap when min-heap needed (and vice versa)
  • Not handling ties correctly
  • Forgetting heapq is min-heap in Python
🔤 Trie O(m) per operation / O(m) per word
class TrieNode {
    Map<Character, TrieNode> children = new HashMap<>();
    boolean isEnd = false;
}
class Trie {
    TrieNode root = new TrieNode();
    void insert(String word) {
        TrieNode node = root;
        for (char ch : word.toCharArray()) {
            node.children.putIfAbsent(ch, new TrieNode());
            node = node.children.get(ch);
        }
        node.isEnd = true;
    }
    boolean search(String word) {
        TrieNode node = root;
        for (char ch : word.toCharArray()) {
            if (!node.children.containsKey(ch)) return false;
            node = node.children.get(ch);
        }
        return node.isEnd;
    }
}
Use when
  • Prefix-based search
  • Autocomplete / word dictionary
  • Word search in grid
  • Longest common prefix
Watch out
  • Not marking end of word
  • Not handling deletion properly
  • Using trie when hash set suffices (no prefix queries needed)
🔗 Union-Find O(alpha(n)) ~ O(1) amortized / O(n)
class UnionFind {
    int[] parent, rank;
    UnionFind(int n) {
        parent = new int[n]; rank = new int[n];
        for (int i = 0; i < n; i++) parent[i] = i;
    }
    int find(int x) {
        if (parent[x] != x) parent[x] = find(parent[x]);
        return parent[x];
    }
    boolean union(int x, int y) {
        int px = find(x), py = find(y);
        if (px == py) return false;
        if (rank[px] < rank[py]) { int t = px; px = py; py = t; }
        parent[py] = px;
        if (rank[px] == rank[py]) rank[px]++;
        return true;
    }
}
Use when
  • Group/cluster detection
  • Connected components dynamically
  • 'Are X and Y connected?'
  • Minimum spanning tree (Kruskal's)
Watch out
  • Forgetting path compression (degrades to O(n))
  • Not using union by rank
  • Not counting components correctly
📋 Topological Sort O(V + E) / O(V + E)
List<Integer> topoSort(int n, int[][] edges) {
    int[] inDeg = new int[n];
    Map<Integer, List<Integer>> g = new HashMap<>();
    for (int[] e : edges) {
        g.computeIfAbsent(e[0], k -> new ArrayList<>()).add(e[1]);
        inDeg[e[1]]++;
    }
    Queue<Integer> q = new LinkedList<>();
    for (int i = 0; i < n; i++) if (inDeg[i] == 0) q.add(i);
    List<Integer> order = new ArrayList<>();
    while (!q.isEmpty()) {
        int node = q.poll();
        order.add(node);
        for (int nb : g.getOrDefault(node, List.of()))
            if (--inDeg[nb] == 0) q.add(nb);
    }
    return order.size() == n ? order : List.of();
}
Use when
  • Task scheduling with prerequisites
  • Course ordering
  • Build order / dependency resolution
  • Detect cycle in directed graph
Watch out
  • Forgetting to check for cycles (result size < V)
  • Wrong in-degree initialization
  • Using DFS topo sort but forgetting reverse at end
🎯 Greedy O(n log n) / O(1)
int greedySchedule(int[][] intervals) {
    Arrays.sort(intervals, (a, b) -> a[1] - b[1]);
    int count = 0, end = Integer.MIN_VALUE;
    for (int[] iv : intervals) {
        if (iv[0] >= end) {
            count++;
            end = iv[1];
        }
    }
    return count;
}
Use when
  • Activity selection / scheduling
  • Minimum coins / jumps
  • Locally optimal leads to globally optimal
  • Proof by exchange argument
Watch out
  • Assuming greedy works without proof
  • Wrong sorting criterion
  • Not handling edge cases (empty input, single element)
Bit Manipulation O(n) or O(1) / O(1)
int singleNumber(int[] nums) {
    int result = 0;
    for (int num : nums) result ^= num;
    return result;
}
// Useful bit tricks:
// n & (n - 1)   -> clear lowest set bit
// n & (-n)      -> isolate lowest set bit
// n >> 1        -> divide by 2
// 1 << k        -> 2^k / check kth bit
Use when
  • Find single/unique number
  • Power of 2 check
  • Counting bits
  • Subset enumeration with bitmask
Watch out
  • Signed vs unsigned integer issues
  • Operator precedence (& before ==)
  • Forgetting n & (n-1) clears lowest set bit
📈 Monotonic Queue O(n) / O(k)
int[] maxSlidingWindow(int[] nums, int k) {
    Deque<Integer> dq = new ArrayDeque<>();
    int[] result = new int[nums.length - k + 1];
    for (int i = 0; i < nums.length; i++) {
        while (!dq.isEmpty() && dq.peekFirst() < i - k + 1)
            dq.pollFirst();
        while (!dq.isEmpty() && nums[dq.peekLast()] < nums[i])
            dq.pollLast();
        dq.addLast(i);
        if (i >= k - 1) result[i - k + 1] = nums[dq.peekFirst()];
    }
    return result;
}
Use when
  • Sliding window maximum/minimum
  • Fixed window with min/max query
  • Subarray with bounded difference
  • Jump game with constraints
Watch out
  • Not removing expired elements from front
  • Wrong deque direction (max vs min)
  • Storing values instead of indices
Two Heaps O(log n) per insert / O(n)
class MedianFinder {
    PriorityQueue<Integer> lo = // max-heap
        new PriorityQueue<>(Collections.reverseOrder());
    PriorityQueue<Integer> hi = new PriorityQueue<>();
    void add(int num) {
        lo.add(num);
        hi.add(lo.poll());
        if (hi.size() > lo.size()) lo.add(hi.poll());
    }
    double median() {
        if (lo.size() > hi.size()) return lo.peek();
        return (lo.peek() + hi.peek()) / 2.0;
    }
}
Use when
  • Find median from stream
  • Balance two groups
  • Sliding window median
  • Maximize capital (IPO problem)
Watch out
  • Not balancing heap sizes after each insert
  • Wrong heap for wrong half
  • Python heapq is min-heap, negate for max-heap
🔄 Cyclic Sort O(n) / O(1)
List<Integer> cyclicSort(int[] nums) {
    int i = 0;
    while (i < nums.length) {
        int correct = nums[i] - 1;
        if (nums[i] != nums[correct]) {
            int tmp = nums[i];
            nums[i] = nums[correct];
            nums[correct] = tmp;
        } else i++;
    }
    List<Integer> missing = new ArrayList<>();
    for (int j = 0; j < nums.length; j++)
        if (nums[j] != j + 1) missing.add(j + 1);
    return missing;
}
Use when
  • Array contains numbers in range [1, n]
  • Find missing/duplicate number
  • Find all missing numbers
  • First missing positive
Watch out
  • Infinite loop when duplicate exists (check before swap)
  • Off-by-one between 0-indexed and 1-indexed
  • Not handling numbers outside valid range
🧩 Subsets & Combinations O(2^n) / O(n)
List<List<Integer>> subsets(int[] nums) {
    List<List<Integer>> result = new ArrayList<>();
    result.add(new ArrayList<>());
    for (int num : nums) {
        int size = result.size();
        for (int i = 0; i < size; i++) {
            List<Integer> copy = new ArrayList<>(result.get(i));
            copy.add(num);
            result.add(copy);
        }
    }
    return result;
}
void combine(int[] nums, int start,
    List<Integer> path, List<List<Integer>> result) {
    result.add(new ArrayList<>(path));
    for (int i = start; i < nums.length; i++) {
        path.add(nums[i]);
        combine(nums, i + 1, path, result);
        path.remove(path.size() - 1);
    }
}
Use when
  • Generate all subsets/combinations
  • Power set
  • Combination sum
  • Letter case permutation
Watch out
  • Not using start index (generating permutations instead)
  • Not sorting + skipping for duplicate handling
  • Not making a copy of current path before adding to result
Linked List Techniques O(n) / O(1)
ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    ListNode dummy = new ListNode(0), curr = dummy;
    while (l1 != null && l2 != null) {
        if (l1.val <= l2.val) {
            curr.next = l1; l1 = l1.next;
        } else {
            curr.next = l2; l2 = l2.next;
        }
        curr = curr.next;
    }
    curr.next = (l1 != null) ? l1 : l2;
    return dummy.next;
}
Use when
  • Merge sorted lists
  • Partition list
  • Remove duplicates
  • Reorder or rearrange list
Watch out
  • Not using dummy head (complicates edge cases)
  • Losing tail pointer during merge
  • Not handling empty list / single node
🏗 Design Patterns Varies (O(1) per op for LRU) / Varies
class LRUCache {
    int cap;
    LinkedHashMap<Integer, Integer> cache = new LinkedHashMap<>();
    LRUCache(int capacity) { cap = capacity; }
    int get(int key) {
        if (!cache.containsKey(key)) return -1;
        int val = cache.remove(key);
        cache.put(key, val); // move to end
        return val;
    }
    void put(int key, int value) {
        cache.remove(key);
        cache.put(key, value);
        if (cache.size() > cap)
            cache.remove(cache.keySet().iterator().next());
    }
}
Use when
  • LRU/LFU cache
  • Min stack / max stack
  • Iterator design
  • Custom data structure combining primitives
Watch out
  • Not maintaining invariants across operations
  • Forgetting edge cases (capacity=0, empty state)
  • Not achieving required time complexity for each operation
📐 Segment Tree O(log n) query/update / O(n)
class SegTree {
    int n;
    int[] tree;
    SegTree(int n) { this.n = n; tree = new int[2 * n]; }
    void update(int i, int val) {
        i += n; tree[i] = val;
        for (i /= 2; i > 0; i /= 2)
            tree[i] = tree[2*i] + tree[2*i+1];
    }
    int query(int l, int r) { // [l, r)
        int res = 0;
        for (l += n, r += n; l < r; l /= 2, r /= 2) {
            if ((l & 1) == 1) res += tree[l++];
            if ((r & 1) == 1) res += tree[--r];
        }
        return res;
    }
}
Use when
  • Range sum/min/max with updates
  • Count in range queries
  • Interval scheduling
  • Need better than O(n) per query
Watch out
  • Off-by-one in tree indexing
  • Not propagating lazy updates
  • Using segment tree when prefix sum or BIT suffices
🔎 String Matching O(n + m) / O(m)
int kmpSearch(String text, String pattern) {
    int[] lps = new int[pattern.length()];
    for (int i = 1, len = 0; i < pattern.length(); ) {
        if (pattern.charAt(i) == pattern.charAt(len))
            lps[i++] = ++len;
        else if (len > 0) len = lps[len - 1];
        else i++;
    }
    for (int i = 0, j = 0; i < text.length(); ) {
        if (text.charAt(i) == pattern.charAt(j)) { i++; j++; }
        if (j == pattern.length()) return i - j;
        if (i < text.length()
            && text.charAt(i) != pattern.charAt(j)) {
            j = j > 0 ? lps[j - 1] : 0;
            if (j == 0 && text.charAt(i) != pattern.charAt(0)) i++;
        }
    }
    return -1;
}
Use when
  • Find pattern in text
  • Count pattern occurrences
  • Multiple pattern search
  • Longest prefix which is also suffix
Watch out
  • Not handling hash collisions (Rabin-Karp)
  • Wrong failure function construction (KMP)
  • Using naive O(nm) when O(n+m) is needed