// senior_full_stack_interview_prep.cs

Recognize the pattern.
Skip the memorization.

20+ algorithmic patterns, tiered by how often they actually show up in Senior / Lead .NET Full Stack loops — each with identification signals, a C# template, and the pitfalls that separate a mid-level answer from a senior one.

9 Must-Know Patterns
8 Very Common
3 Niche / Hard-Round
4 Week Sprint

How to Use This Guide

Each pattern is tagged with a priority level so you can allocate study time efficiently:

  • High Priority (Must Know) — Shows up in nearly every senior/lead loop. Master these first; you cannot skip these.
  • Medium Priority (Very Common) — Frequently asked, especially in full-stack/backend-heavy interviews. Strong ROI.
  • Niche (Nice to Have) — Occasionally appears in "hard round" or FAANG-style loops. Cram only after the above are solid.

Given senior/lead expectations, Easy problems are listed only as optional 5-minute warmups — the real interview signal for your level is in the Medium/Hard tier, so benchmarks below skew heavily toward those.


4-Week Study Plan (Priority-Ordered)

Week Focus Patterns Covered Priority Mix
Week 1 Pointers & Search Space Dynamic Sliding Window, Opposite-Direction Two Pointers, Fixed Window, Fast & Slow, Binary Search on Answer, Boundary Binary Search
Week 2 Stacks, Queues & Graphs Monotonic Stack/Deque, Grid BFS/DFS, Tree Traversal, Topological Sort
Week 3 Heaps & Core DP Top-K Heap, Two Heaps, 0/1 & Unbounded Knapsack, LCS Family, LIS
Week 4 Backtracking, DSU, Consolidation Backtracking, Union-Find, Prefix Sums, Interval DP, mixed mock drills on Weeks 1–3

Suggested daily rhythm: 1 pattern review (30 min) → 2 Medium problems (untimed, focus on correctness) → 1 Medium/Hard problem (timed, 25–35 min, simulate interview pressure). Saturdays: mixed mock interview pulling randomly from prior weeks. Sundays: review pitfalls list and redo any problem you got wrong.


Quick Reference: Priority Snapshot

Pattern Priority Why
Dynamic Sliding Window High Extremely common; tests correctness under shrinking/expanding logic
Binary Search on Answer High Senior-level signal — requires proving monotonicity, not memorization
Monotonic Stack High Classic "next greater/smaller" family, very common in mediums
Graph BFS/DFS + Topological Sort High Dependency resolution problems are a senior-interview staple
Heap Top-K High Foundational; combines with almost everything (streaming, scheduling)
0/1 & Unbounded Knapsack High The DP "gateway" pattern — most other DP questions build on this
LCS Family High Edit distance / diffing logic shows up in real systems too
Backtracking High Tests recursive design and pruning — a senior differentiator
Union-Find (DSU) High Connectivity questions are common and often surprise mid-levels
Fixed Sliding Window Medium Simpler variant, good warmup before dynamic window
Opposite-Direction Two Pointers Medium Common but usually more mechanical than dynamic window
Fast & Slow Pointers Medium Linked-list specific; smaller surface area
Standard / Boundary Binary Search Medium Prerequisite for "Binary Search on Answer"
Grid BFS/DFS Medium Common but often simpler once graph BFS is solid
Tree Traversal (Pre/In/Post/Level) Medium Usually a stepping stone to harder tree/graph problems
LIS Medium Less frequent than knapsack/LCS but still recurring
Prefix Sums Medium Quick to learn, high leverage for array problems
Monotonic Deque (Sliding Window Max) Niche Powerful but narrower problem surface
Two Heaps (Median Maintenance) Niche Specific to streaming median problems
Interval DP Niche Appears mostly in "hard" rounds; lower frequency overall

Quick Reference: Pattern → Signal Cheat Sheet

Signal in Problem Statement Likely Pattern Priority
"sorted array" + pair/triplet sum Two Pointers (opposite direction)
"subarray/substring of size K" Fixed Sliding Window
"longest/shortest ... with condition" Dynamic Sliding Window
"linked list cycle" / "middle of list" Fast & Slow Pointers
"minimize the maximum" / "maximize the minimum" Binary Search on Answer
"first/last occurrence" Boundary Binary Search
"next greater/smaller element" Monotonic Stack
"sliding window maximum" Monotonic Deque
"number of islands" / grid connectivity Grid BFS/DFS
"course schedule" / "dependencies" Topological Sort
"k-th largest/smallest" / "top K" Heap (Top-K)
"median of a stream" Two Heaps
"each item used once" + capacity 0/1 Knapsack
"unlimited supply" + capacity Unbounded Knapsack
"two strings" comparison LCS Family
"longest increasing subsequence" LIS
"matrix chain" / "burst balloons" Interval DP
"generate all subsets/permutations" Backtracking
"connected components" / "redundant connection" Union-Find
"range sum query" Prefix Sums

Study guide generated for Senior/Lead Engineer interview preparation — C# pseudo-code templates throughout, prioritized for a 4-week sprint. Pair each High Priority pattern with 3-4 timed Medium/Hard mock drills before moving on.

01

1. Two Pointers / Sliding Window

1.1 Fixed-Size Sliding Window

Very Common

Summary: Maintain a window of exactly k elements, sliding it one step at a time while updating an aggregate (sum, max, count) in O(1) per step instead of recomputing from scratch.

Identification Signals - "Subarray/substring of size exactly K" - "Maximum/average/sum of every window of size k" - Fixed contiguous range, no shrinking/growing logic needed

Core Strategy 1. Compute the aggregate for the first k elements. 2. Slide right by one: add incoming element, remove outgoing (leftmost) element. 3. Update the running best answer at each slide.

C# Pseudo-code Template

// PSEUDO-CODE: Fixed Window Aggregate
public int MaxSumFixedWindow(int[] nums, int k)
{
    int windowSum = 0;
    for (int i = 0; i < k; i++) windowSum += nums[i];   // seed first window

    int maxSum = windowSum;
    for (int right = k; right < nums.Length; right++)
    {
        windowSum += nums[right] - nums[right - k];      // add new, drop old
        maxSum = Math.Max(maxSum, windowSum);
    }
    return maxSum;
}

Benchmarks (Medium/Hard-weighted) - Warmup (optional): 643. Maximum Average Subarray I - Medium: 2461. Maximum Sum of Distinct Subarrays With Length K, 1052. Grumpy Bookstore Owner - Hard: 239. Sliding Window Maximum (combines with Monotonic Deque)

Pitfalls - Off-by-one when removing the outgoing element: it's nums[right - k], not nums[right - k - 1]. - Forgetting to initialize the first window before the loop starts.


1.2 Dynamic-Size Sliding Window

Must Know

Summary: Expand the right pointer to grow the window; shrink from the left when a constraint is violated. Total work is still O(n) because each pointer moves forward at most n times.

Identification Signals - "Longest/shortest substring/subarray with condition X" (no repeats, at most K distinct, sum ≤ target) - Condition is monotonic — once violated, shrinking only helps

Core Strategy 1. Expand right, update window state (hashmap/count/sum). 2. While the window violates the constraint, shrink left and update state. 3. Update the answer (usually right - left + 1) once the window is valid.

C# Pseudo-code Template

// PSEUDO-CODE: Dynamic Window with Constraint
public int LengthOfLongestSubstringKDistinct(string s, int k)
{
    var freq = new Dictionary<char, int>();
    int left = 0, best = 0;

    for (int right = 0; right < s.Length; right++)
    {
        char c = s[right];
        freq[c] = freq.GetValueOrDefault(c, 0) + 1;      // expand

        while (freq.Count > k)                            // shrink while invalid
        {
            char leftChar = s[left];
            freq[leftChar]--;
            if (freq[leftChar] == 0) freq.Remove(leftChar);
            left++;
        }
        best = Math.Max(best, right - left + 1);          // record valid window
    }
    return best;
}

Benchmarks (Medium/Hard-weighted) - Warmup (optional): 1876. Substrings of Size Three with Distinct Characters - Medium: 3. Longest Substring Without Repeating Characters, 209. Minimum Size Subarray Sum, 424. Longest Repeating Character Replacement, 1004. Max Consecutive Ones III - Hard: 76. Minimum Window Substring, 992. Subarrays with K Different Integers

Pitfalls - Forgetting to fully clean up map entries (leaving zero-count keys skews freq.Count). - Using while vs if for shrinking — must be while since one expansion can require multiple shrinks. - Confusing "at most K" with "exactly K" (exactly K = atMost(K) - atMost(K-1) trick — this itself is a common senior-level follow-up).


1.3 Fast & Slow Pointers (Tortoise and Hare)

Very Common

Summary: Two pointers move through a sequence/linked list at different speeds to detect cycles, find midpoints, or find cycle-entry points without extra memory.

Identification Signals - Linked list problems mentioning cycles - "Find the middle of a linked list" - "Detect if a sequence loops" (including array-as-graph problems like Find the Duplicate Number)

Core Strategy 1. slow moves 1 step, fast moves 2 steps per iteration. 2. If there's a cycle, they eventually meet. 3. For cycle-start detection: reset one pointer to head, move both 1 step at a time — they meet at the cycle entry (Floyd's algorithm).

C# Pseudo-code Template

// PSEUDO-CODE: Floyd's Cycle Detection
public bool 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;   // pointers met -> cycle exists
    }
    return false;
}

Benchmarks (Medium/Hard-weighted) - Warmup (optional): 876. Middle of the Linked List - Medium: 142. Linked List Cycle II, 287. Find the Duplicate Number, 202. Happy Number - Hard: 25. Reverse Nodes in k-Group (combined technique)

Pitfalls - Null-checking fast.next before fast.next.next to avoid NullReferenceException. - Off-by-one in "find middle" when list length is even (decide upper vs lower middle) — interviewers often probe this explicitly.


1.4 Opposite-Direction Two Pointers

Very Common

Summary: One pointer starts at each end of a sorted array/string and they move toward each other based on a comparison, avoiding O(n²) nested loops.

Identification Signals - "Sorted array," "palindrome check," "pair that sums to target" - "Container with most water," "trapping rain water"

Core Strategy 1. Initialize left = 0, right = n - 1. 2. Compare nums[left] + nums[right] (or equivalent) to target. 3. Move left++ or right-- depending on which direction reduces/increases the value needed.

C# Pseudo-code Template

// PSEUDO-CODE: Opposite Direction Two Sum
public int[] TwoSumSorted(int[] nums, int target)
{
    int left = 0, right = nums.Length - 1;
    while (left < right)
    {
        int sum = nums[left] + nums[right];
        if (sum == target) return new int[] { left, right };
        if (sum < target) left++;    // need bigger sum
        else right--;                 // need smaller sum
    }
    return Array.Empty<int>();
}

Benchmarks (Medium/Hard-weighted) - Warmup (optional): 167. Two Sum II - Input Array Is Sorted - Medium: 15. 3Sum, 11. Container With Most Water, 16. 3Sum Closest, 18. 4Sum - Hard: 42. Trapping Rain Water

Pitfalls - Forgetting the array must be sorted first (or that sorting changes original indices — a common trap when the problem also asks you to return original indices). - Duplicate handling in 3Sum/4Sum — must skip duplicate values at both pointers to avoid duplicate triplets/quadruplets.


02

2. Binary Search Variants

2.1 Standard Binary Search

Very Common

Summary: Repeatedly halve a sorted search space to find a target in O(log n).

Identification Signals - Explicitly sorted array - "Find target," "search in rotated sorted array"

Core Strategy 1. left = 0, right = n - 1. 2. mid = left + (right - left) / 2 (avoids overflow). 3. Compare nums[mid] to target and shrink the appropriate half.

C# Pseudo-code Template

// PSEUDO-CODE: Standard Binary Search
public int BinarySearch(int[] nums, int target)
{
    int left = 0, right = nums.Length - 1;
    while (left <= right)
    {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) return mid;
        if (nums[mid] < target) left = mid + 1;
        else right = mid - 1;
    }
    return -1;
}

Benchmarks (Medium/Hard-weighted) - Warmup (optional): 704. Binary Search - Medium: 33. Search in Rotated Sorted Array, 81. Search in Rotated Sorted Array II, 74. Search a 2D Matrix - Hard: 4. Median of Two Sorted Arrays

Pitfalls - left + (right - left) / 2 vs (left + right) / 2 — good overflow-avoidance habit even though less critical in C#'s int range. - Infinite loops from incorrect boundary updates (mid instead of mid ± 1).


2.2 Boundary Finding (Lower/Upper Bound)

Very Common

Summary: Find the first or last position where a condition becomes true/false — used for "first occurrence," "last occurrence," or "insertion point" problems.

Identification Signals - "First/last position of target" - "Find the smallest index such that condition holds" - Sorted array with duplicates

Core Strategy 1. Use a half-open [left, right) convention. 2. Instead of returning immediately on match, record the answer and keep narrowing in the direction of the boundary.

C# Pseudo-code Template

// PSEUDO-CODE: Leftmost Boundary Search
public int LeftMostInsertionPoint(int[] nums, int target)
{
    int left = 0, right = nums.Length; // half-open [left, right)
    while (left < right)
    {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) left = mid + 1;
        else right = mid;
    }
    return left; // first index where nums[index] >= target
}

Benchmarks (Medium/Hard-weighted) - Warmup (optional): 35. Search Insert Position - Medium: 34. Find First and Last Position of Element in Sorted Array, 1608. Special Array With X Elements Greater Than or Equal X - Hard: 4. Median of Two Sorted Arrays (partition-based boundary search)

Pitfalls - Mixing [left, right) half-open convention with [left, right] closed convention mid-solution — pick one and stay consistent. - Forgetting that finding "last occurrence" requires searching for LeftMostInsertionPoint(target + 1) - 1.


2.3 Binary Search on Answer / Search Space Reduction

Must Know

Summary: When the answer itself is monotonic (if X works, everything "easier" than X also works), binary search over the range of possible answers, not the array indices. This is the variant senior interviewers use to test whether you can recognize a problem as binary-searchable rather than just execute a memorized template.

Identification Signals - "Minimize the maximum," "maximize the minimum" - "Find the smallest value such that a feasibility check passes" - Problem mentions capacity, speed, days, or distance thresholds

Core Strategy 1. Define low and high as the plausible answer range. 2. Write a Feasible(mid) predicate (usually greedy or simulation, O(n)). 3. Binary search: if feasible, try smaller (high = mid); else low = mid + 1.

C# Pseudo-code Template

// PSEUDO-CODE: Binary Search on Answer Space
public int MinEatingSpeed(int[] piles, int h)
{
    int low = 1, high = piles.Max();
    while (low < high)
    {
        int mid = low + (high - low) / 2;
        if (CanFinish(piles, h, mid)) high = mid;   // feasible -> try smaller
        else low = mid + 1;                          // infeasible -> need bigger
    }
    return low;
}

private bool CanFinish(int[] piles, int h, int speed)
{
    long hours = 0;
    foreach (var p in piles) hours += (p + speed - 1) / speed;  // ceil division
    return hours <= h;
}

Benchmarks (Medium/Hard-weighted) - Warmup (optional): 1283. Find the Smallest Divisor Given a Threshold - Medium: 875. Koko Eating Bananas, 1011. Capacity To Ship Packages Within D Days, 1552. Magnetic Force Between Two Balls - Hard: 410. Split Array Largest Sum, 1231. Divide Chocolate

Pitfalls - Failing to prove monotonicity before applying this pattern — articulate this proof out loud in interviews, it's a strong signal. - Integer overflow in the feasibility check (use long for accumulations).


03

3. Monotonic Stack / Queue

3.1 Monotonic Stack

Must Know

Summary: Maintain a stack where elements are kept in strictly increasing or decreasing order, popping elements that violate the order to answer "next greater/smaller element" queries in O(n) total.

Identification Signals - "Next greater/smaller element," "daily temperatures" - "Largest rectangle," "trapping rain water" (stack variant)

Core Strategy 1. Iterate through the array, maintaining a stack of indices. 2. While the current element violates the stack's order, pop and resolve the answer for the popped index. 3. Push the current index.

C# Pseudo-code Template

// PSEUDO-CODE: Monotonic Decreasing Stack
public int[] DailyTemperatures(int[] temperatures)
{
    int n = temperatures.Length;
    int[] result = new int[n];
    var stack = new Stack<int>(); // stores indices, decreasing temps

    for (int i = 0; i < n; i++)
    {
        while (stack.Count > 0 && temperatures[i] > temperatures[stack.Peek()])
        {
            int idx = stack.Pop();
            result[idx] = i - idx;   // resolve distance for popped index
        }
        stack.Push(i);
    }
    return result;
}

Benchmarks (Medium/Hard-weighted) - Warmup (optional): 496. Next Greater Element I - Medium: 739. Daily Temperatures, 503. Next Greater Element II, 907. Sum of Subarray Minimums, 1130. Minimum Cost Tree From Leaf Values - Hard: 84. Largest Rectangle in Histogram, 85. Maximal Rectangle

Pitfalls - Pushing values instead of indices — indices let you compute distances and check window bounds. - Choosing the wrong strict/non-strict inequality — affects handling of duplicate values (a common follow-up question).


3.2 Monotonic Deque (Sliding Window Max)

Nice to Have

Summary: A deque variant of the monotonic stack that supports removal from both ends, used to track the max/min of a sliding window in O(n) total.

Identification Signals - "Sliding window maximum/minimum" - Window constraints combined with order-tracking

Core Strategy 1. Maintain indices in the deque in decreasing value order. 2. Pop from the back while the new element is larger (for max). 3. Pop from the front when the front index falls outside the window.

C# Pseudo-code Template

// PSEUDO-CODE: Monotonic Deque for Sliding Window Max
public int[] MaxSlidingWindow(int[] nums, int k)
{
    var deque = new LinkedList<int>(); // stores indices, decreasing values
    var result = new List<int>();

    for (int i = 0; i < nums.Length; i++)
    {
        while (deque.Count > 0 && nums[deque.Last.Value] < nums[i])
            deque.RemoveLast();                       // maintain decreasing order
        deque.AddLast(i);

        if (deque.First.Value <= i - k) deque.RemoveFirst();  // expire out-of-window
        if (i >= k - 1) result.Add(nums[deque.First.Value]);
    }
    return result.ToArray();
}

Benchmarks (Medium/Hard-weighted) - Medium: 1438. Longest Continuous Subarray With Absolute Diff Less Than or Equal to Limit - Hard: 239. Sliding Window Maximum, 480. Sliding Window Median

Pitfalls - Forgetting to pop expired indices (outside the window) in the deque variant. - Confusing this with the plain monotonic stack — the deque needs front-removal capability the stack doesn't have.


04

4. BFS & DFS Traversal Patterns

4.1 Grid BFS/DFS (Islands, Shortest Path)

Very Common

Summary: Treat a 2D grid as an implicit graph where each cell connects to its 4 (or 8) neighbors; BFS gives shortest path in unweighted grids, DFS is simpler for connectivity/counting.

Identification Signals - "Number of islands," "rotting oranges," "shortest path in a grid/maze" - Matrix of 0s/1s representing land/water or walls/open cells

Core Strategy 1. Iterate all cells; on an unvisited "land" cell, trigger BFS/DFS to mark the whole connected component visited. 2. For shortest path, use BFS with a queue (level-by-level = distance). 3. Mark visited in-place (flip value) or use a separate visited matrix.

C# Pseudo-code Template

// PSEUDO-CODE: Grid BFS for Connected Components
public int NumIslands(char[][] grid)
{
    if (grid == null || grid.Length == 0) return 0;
    int rows = grid.Length, cols = grid[0].Length, islands = 0;

    void Bfs(int r, int c)
    {
        var queue = new Queue<(int, int)>();
        queue.Enqueue((r, c));
        grid[r][c] = '0';                              // mark visited on enqueue
        int[][] dirs = { new[]{1,0}, new[]{-1,0}, new[]{0,1}, new[]{0,-1} };

        while (queue.Count > 0)
        {
            var (cr, cc) = queue.Dequeue();
            foreach (var d in dirs)
            {
                int nr = cr + d[0], nc = cc + d[1];
                if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] == '1')
                {
                    grid[nr][nc] = '0';
                    queue.Enqueue((nr, nc));
                }
            }
        }
    }

    for (int r = 0; r < rows; r++)
        for (int c = 0; c < cols; c++)
            if (grid[r][c] == '1') { islands++; Bfs(r, c); }

    return islands;
}

Benchmarks (Medium/Hard-weighted) - Warmup (optional): 733. Flood Fill - Medium: 200. Number of Islands, 994. Rotting Oranges, 542. 01 Matrix, 130. Surrounded Regions - Hard: 1293. Shortest Path in a Grid with Obstacles Elimination

Pitfalls - Not marking visited at enqueue time (leads to duplicate enqueues and TLE). - Using DFS recursion on very large grids can cause stack overflow — prefer BFS or iterative DFS for huge inputs (worth mentioning proactively in interviews to show production awareness).


4.2 Tree Traversal (Pre/In/Post-order, Level Order)

Very Common

Summary: Systematically visit every node in a tree; the traversal order (or BFS-by-level) determines what problems it solves — e.g., in-order gives sorted output for BSTs.

Identification Signals - Any binary tree/BST problem - "Level order," "zigzag," "top view/bottom view" - "Validate BST," "serialize/deserialize"

Core Strategy - DFS (recursive): base case on null node, recurse left/right, combine results. - BFS (level order): use a queue, process one full level at a time using the current queue size as the level boundary.

C# Pseudo-code Template

// PSEUDO-CODE: Level Order BFS
public IList<IList<int>> LevelOrder(TreeNode root)
{
    var result = new List<IList<int>>();
    if (root == null) return result;

    var queue = new Queue<TreeNode>();
    queue.Enqueue(root);

    while (queue.Count > 0)
    {
        int levelSize = queue.Count;    // snapshot BEFORE enqueuing children
        var level = new List<int>();
        for (int i = 0; i < levelSize; i++)
        {
            var node = queue.Dequeue();
            level.Add(node.val);
            if (node.left != null) queue.Enqueue(node.left);
            if (node.right != null) queue.Enqueue(node.right);
        }
        result.Add(level);
    }
    return result;
}

Benchmarks (Medium/Hard-weighted) - Warmup (optional): 104. Maximum Depth of Binary Tree - Medium: 102. Binary Tree Level Order Traversal, 98. Validate Binary Search Tree, 236. Lowest Common Ancestor of a Binary Tree, 199. Binary Tree Right Side View - Hard: 297. Serialize and Deserialize Binary Tree, 124. Binary Tree Maximum Path Sum

Pitfalls - Capturing queue.Count before the inner loop starts (it changes as you enqueue children). - For BST validation, passing down (min, max) bounds correctly rather than only comparing to immediate parent.


4.3 Graph Traversal & Topological Sort

Must Know

Summary: Extend BFS/DFS to general graphs (adjacency lists) with explicit visited tracking; topological sort orders nodes in a DAG such that every edge goes from earlier to later in the order.

Identification Signals - "Course schedule," "build order," "dependencies" - "Detect cycle in directed graph" - Explicit graph given as edge list or adjacency list

Core Strategy (Kahn's Algorithm — BFS-based Topo Sort) 1. Compute in-degree for every node. 2. Push all zero in-degree nodes into a queue. 3. Pop a node, add to result, decrement in-degree of neighbors; enqueue any that hit zero. 4. If result size < total nodes, a cycle exists.

C# Pseudo-code Template

// PSEUDO-CODE: Kahn's Algorithm (BFS Topological Sort)
public int[] FindOrder(int numCourses, int[][] prerequisites)
{
    var graph = new List<int>[numCourses];
    var inDegree = new int[numCourses];
    for (int i = 0; i < numCourses; i++) graph[i] = new List<int>();

    foreach (var p in prerequisites)
    {
        graph[p[1]].Add(p[0]);
        inDegree[p[0]]++;
    }

    var queue = new Queue<int>();
    for (int i = 0; i < numCourses; i++)
        if (inDegree[i] == 0) queue.Enqueue(i);

    var order = new List<int>();
    while (queue.Count > 0)
    {
        int node = queue.Dequeue();
        order.Add(node);
        foreach (var next in graph[node])
        {
            inDegree[next]--;
            if (inDegree[next] == 0) queue.Enqueue(next);
        }
    }

    return order.Count == numCourses ? order.ToArray() : Array.Empty<int>();  // cycle check
}

Benchmarks (Medium/Hard-weighted) - Warmup (optional): 997. Find the Town Judge - Medium: 207. Course Schedule, 210. Course Schedule II, 133. Clone Graph, 785. Is Graph Bipartite? - Hard: 269. Alien Dictionary (premium), 1462. Course Schedule IV

Pitfalls - Forgetting the cycle check (order.Count == numCourses) — without it, a cyclic graph silently returns an incomplete order. - Using a visited set alone for DFS-based topo sort without distinguishing "currently in recursion stack" vs "fully processed" (needed to detect cycles correctly) — a frequent senior-level gotcha.


05

5. Heap / Priority Queue Patterns

5.1 Top-K Elements

Must Know

Summary: Use a min-heap of size K (for "top K largest") or max-heap of size K (for "top K smallest") to avoid sorting the entire dataset — O(n log k) instead of O(n log n).

Identification Signals - "K-th largest/smallest," "top K frequent elements" - "K closest points to origin"

Core Strategy 1. Maintain a heap of size K. 2. For each new element, push it; if heap size exceeds K, pop the "worst" element. 3. At the end, the heap contains exactly the top K elements.

C# Pseudo-code Template

// PSEUDO-CODE: Min-Heap for Top-K Largest
public int FindKthLargest(int[] nums, int k)
{
    var minHeap = new PriorityQueue<int, int>();
    foreach (var num in nums)
    {
        minHeap.Enqueue(num, num);
        if (minHeap.Count > k) minHeap.Dequeue();   // evict smallest, keep top k
    }
    return minHeap.Peek();
}

Benchmarks (Medium/Hard-weighted) - Warmup (optional): 1046. Last Stone Weight - Medium: 215. Kth Largest Element in an Array, 347. Top K Frequent Elements, 973. K Closest Points to Origin, 692. Top K Frequent Words - Hard: 23. Merge k Sorted Lists, 502. IPO

Pitfalls - C#'s PriorityQueue<TElement, TPriority> is a min-heap by default — for max-heap behavior, negate priorities or use a custom comparer (call this out proactively, interviewers like seeing awareness of language-specific gotchas). - Forgetting to pop when heap size exceeds K (defeats the O(log k) benefit).


5.2 Two Heaps (Median Maintenance)

Nice to Have

Summary: Split a data stream into a max-heap (lower half) and min-heap (upper half) kept balanced in size, so the median is always accessible in O(1).

Identification Signals - "Find median from data stream" - "Running median," "balance two halves of data"

Core Strategy 1. maxHeap (lower half) holds the smaller ~half of numbers; minHeap (upper half) holds the larger ~half. 2. Insert into one heap, then rebalance by moving the top element to the other heap if sizes differ by more than 1. 3. Median = top of larger heap, or average of both tops if equal size.

C# Pseudo-code Template

// PSEUDO-CODE: Two Heaps for Streaming Median
public class MedianFinder
{
    private PriorityQueue<int, int> maxHeap = new(); // lower half, negated priority
    private PriorityQueue<int, int> minHeap = new(); // upper half

    public void AddNum(int num)
    {
        maxHeap.Enqueue(num, -num);
        minHeap.Enqueue(maxHeap.Peek(), maxHeap.Dequeue());   // shuffle across

        if (minHeap.Count > maxHeap.Count)
        {
            int val = minHeap.Dequeue();
            maxHeap.Enqueue(val, -val);
        }
    }

    public double FindMedian()
    {
        if (maxHeap.Count > minHeap.Count) return maxHeap.Peek();
        return (maxHeap.Peek() + minHeap.Peek()) / 2.0;
    }
}

Benchmarks (Medium/Hard-weighted) - Medium: 480. Sliding Window Median (combines with sliding window) - Hard: 295. Find Median from Data Stream

Pitfalls - Letting the two heaps drift more than 1 element apart in size — breaks the O(1) median guarantee. - Off-by-one when computing median for even vs odd total count.


06

6. Dynamic Programming Archetypes

6.1 0/1 Knapsack

Must Know

Summary: Choose a subset of items (each used at most once) to maximize/minimize a value subject to a capacity constraint — classic "include or exclude" decision at each step.

Identification Signals - "Each item used at most once" - "Subset sum," "partition equal subset sum," "target sum" - Capacity/weight constraint explicitly given

Core Strategy 1. dp[i][w] = best value using first i items with capacity w. 2. Transition: dp[i][w] = max(dp[i-1][w], dp[i-1][w - weight[i]] + value[i]) if weight[i] <= w. 3. Optimize to 1D array, iterating w in reverse to avoid reusing an item twice.

C# Pseudo-code Template

// PSEUDO-CODE: 0/1 Knapsack (1D optimized)
public bool CanPartition(int[] nums)
{
    int sum = nums.Sum();
    if (sum % 2 != 0) return false;
    int target = sum / 2;

    bool[] dp = new bool[target + 1];
    dp[0] = true;

    foreach (var num in nums)
        for (int w = target; w >= num; w--)   // REVERSE iteration: no item reuse
            dp[w] = dp[w] || dp[w - num];

    return dp[target];
}

Benchmarks (Medium/Hard-weighted) - Warmup (optional): 70. Climbing Stairs (intro to DP thinking) - Medium: 416. Partition Equal Subset Sum, 494. Target Sum, 1049. Last Stone Weight II - Hard: 879. Profitable Schemes, 474. Ones and Zeroes (2D knapsack variant)

Pitfalls - Iterating the weight loop forward instead of backward for the 1D optimization — this accidentally allows item reuse (turns it into unbounded knapsack). This is the #1 senior-level DP bug to watch for. - Off-by-one on the capacity array size (target + 1, not target).


6.2 Unbounded Knapsack

Must Know

Summary: Same as 0/1 knapsack, but each item can be used unlimited times — coin change and rod cutting are canonical examples.

Identification Signals - "Unlimited supply," "as many times as needed" - "Coin change," "minimum number of coins/squares"

Core Strategy 1. dp[w] = best way to make amount w. 2. Iterate weight forward (not backward) since reuse is allowed.

C# Pseudo-code Template

// PSEUDO-CODE: Unbounded Knapsack
public int CoinChange(int[] coins, int amount)
{
    int[] dp = new int[amount + 1];
    Array.Fill(dp, amount + 1);   // sentinel for "unreachable"
    dp[0] = 0;

    foreach (var coin in coins)
        for (int w = coin; w <= amount; w++)   // FORWARD iteration: reuse allowed
            dp[w] = Math.Min(dp[w], dp[w - coin] + 1);

    return dp[amount] > amount ? -1 : dp[amount];
}

Benchmarks (Medium/Hard-weighted) - Warmup (optional): 70. Climbing Stairs - Medium: 322. Coin Change, 518. Coin Change II, 279. Perfect Squares - Hard: 983. Minimum Cost For Tickets

Pitfalls - Confusing this with 0/1 knapsack's reverse iteration — be ready to explain why the direction differs, interviewers frequently ask this directly. - Not initializing dp to a sentinel value representing "unreachable."


6.3 Longest Common Subsequence (LCS) Family

Must Know

Summary: Compare two sequences to find the longest subsequence (not necessarily contiguous) common to both, via a 2D grid of matches/mismatches.

Identification Signals - "Two strings," "edit distance," "longest common subsequence" - "Interleaving strings," "distinct subsequences"

Core Strategy 1. dp[i][j] = LCS length of text1[0..i) and text2[0..j). 2. If characters match: dp[i][j] = dp[i-1][j-1] + 1. 3. Else: dp[i][j] = max(dp[i-1][j], dp[i][j-1]).

C# Pseudo-code Template

// PSEUDO-CODE: LCS 2D Table
public int LongestCommonSubsequence(string text1, string text2)
{
    int m = text1.Length, n = text2.Length;
    int[,] dp = new int[m + 1, n + 1];

    for (int i = 1; i <= m; i++)
        for (int j = 1; j <= n; j++)
            dp[i, j] = text1[i - 1] == text2[j - 1]
                ? dp[i - 1, j - 1] + 1
                : Math.Max(dp[i - 1, j], dp[i, j - 1]);

    return dp[m, n];
}

Benchmarks (Medium/Hard-weighted) - Medium: 1143. Longest Common Subsequence, 583. Delete Operation for Two Strings, 97. Interleaving String - Hard: 72. Edit Distance, 115. Distinct Subsequences, 10. Regular Expression Matching

Pitfalls - Off-by-one: dp table is (m+1) x (n+1), and string indices are i-1, j-1 when comparing. - Forgetting the base case row/column (dp[0][*] = 0, dp[*][0] = 0) represents "empty string" comparisons.


6.4 Longest Increasing Subsequence (LIS)

Very Common

Summary: Find the longest strictly increasing subsequence in an array — solvable in O(n²) with straightforward DP, or O(n log n) using binary search with patience sorting.

Identification Signals - "Longest increasing subsequence," "longest chain," "box stacking" - "Maximum number of non-overlapping/nested elements sorted by one dimension"

Core Strategy (O(n log n)) 1. Maintain a tails array where tails[i] = smallest tail value of an increasing subsequence of length i+1. 2. For each number, binary search for its position in tails and replace (or append). 3. Final length of tails = LIS length.

C# Pseudo-code Template

// PSEUDO-CODE: LIS via Patience Sorting + Binary Search
public int LengthOfLIS(int[] nums)
{
    var tails = new List<int>();
    foreach (var num in nums)
    {
        int left = 0, right = tails.Count;
        while (left < right)
        {
            int mid = left + (right - left) / 2;
            if (tails[mid] < num) left = mid + 1;
            else right = mid;
        }
        if (left == tails.Count) tails.Add(num);   // extend LIS
        else tails[left] = num;                     // replace to keep tails minimal
    }
    return tails.Count;
}

Benchmarks (Medium/Hard-weighted) - Medium: 300. Longest Increasing Subsequence, 646. Maximum Length of Pair Chain, 673. Number of Longest Increasing Subsequence - Hard: 354. Russian Doll Envelopes, 1691. Maximum Height by Stacking Cuboids

Pitfalls - The tails array does not represent an actual valid subsequence — only its length is meaningful; reconstructing the actual sequence needs extra bookkeeping (a common "now modify your solution to..." follow-up). - Strictly increasing vs non-decreasing changes the binary search comparison (< vs <=).


6.5 Interval DP

Nice to Have

Summary: Solve problems over subranges [i, j] of an array/string by combining results from smaller sub-intervals — commonly solved by iterating over interval length.

Identification Signals - "Matrix chain multiplication," "burst balloons," "palindrome partitioning" - "Merge intervals to minimize/maximize cost"

Core Strategy 1. dp[i][j] = optimal answer for the interval from i to j. 2. Iterate by increasing interval length; for each [i, j], try every split point k and combine dp[i][k] and dp[k+1][j]. 3. Base case: intervals of length 1 (or 0) are trivially solved.

C# Pseudo-code Template

// PSEUDO-CODE: Interval DP Shape (iterate by length, not by index directly)
public int IntervalDpTemplate(int[] values)
{
    int n = values.Length;
    int[,] dp = new int[n, n];

    for (int len = 2; len <= n; len++)              // iterate by INTERVAL LENGTH
    {
        for (int i = 0; i + len - 1 < n; i++)
        {
            int j = i + len - 1;
            dp[i, j] = int.MaxValue;
            for (int mid = i; mid < j; mid++)
            {
                int cost = dp[i, mid] + dp[mid + 1, j]; // + problem-specific merge cost
                dp[i, j] = Math.Min(dp[i, j], cost);
            }
        }
    }
    return dp[0, n - 1];
}

Benchmarks (Medium/Hard-weighted) - Medium: 647. Palindromic Substrings, 5. Longest Palindromic Substring - Hard: 312. Burst Balloons, 1000. Minimum Cost to Merge Stones, 1130. Minimum Cost Tree From Leaf Values

Pitfalls - Iterating i and j directly instead of by interval length — leads to using un-computed sub-results (dependency order matters a lot here). - Forgetting to handle the "merge cost" term that's added on top of the two sub-interval results (problem-specific).


07

7. Backtracking & Subsets/Permutations

Summary: Explore all candidate solutions by building them incrementally, abandoning ("backtracking" from) a path as soon as it's determined invalid — a DFS over the decision tree.

Identification Signals - "Generate all subsets/permutations/combinations" - "N-Queens," "Sudoku solver," "word search" - "Return all valid arrangements satisfying constraints"

Core Strategy 1. Define a recursive function that takes the current partial solution. 2. At each step, iterate over choices; make a choice, recurse, then undo the choice (backtrack). 3. Add pruning conditions early to cut off invalid branches (constraint checks before recursing).

C# Pseudo-code Template

// PSEUDO-CODE: Subsets Backtracking
public IList<IList<int>> Subsets(int[] nums)
{
    var result = new List<IList<int>>();
    var current = new List<int>();

    void Backtrack(int start)
    {
        result.Add(new List<int>(current));    // copy, don't reference
        for (int i = start; i < nums.Length; i++)
        {
            current.Add(nums[i]);              // choose
            Backtrack(i + 1);
            current.RemoveAt(current.Count - 1); // un-choose (backtrack)
        }
    }

    Backtrack(0);
    return result;
}

// PSEUDO-CODE: Permutations Backtracking
public IList<IList<int>> Permute(int[] nums)
{
    var result = new List<IList<int>>();
    var used = new bool[nums.Length];
    var current = new List<int>();

    void Backtrack()
    {
        if (current.Count == nums.Length)
        {
            result.Add(new List<int>(current));
            return;
        }
        for (int i = 0; i < nums.Length; i++)
        {
            if (used[i]) continue;
            used[i] = true;
            current.Add(nums[i]);
            Backtrack();
            current.RemoveAt(current.Count - 1);  // backtrack
            used[i] = false;
        }
    }

    Backtrack();
    return result;
}

Benchmarks (Medium/Hard-weighted) - Warmup (optional): 78. Subsets - Medium: 46. Permutations, 39. Combination Sum, 79. Word Search, 22. Generate Parentheses, 40. Combination Sum II - Hard: 51. N-Queens, 37. Sudoku Solver, 212. Word Search II

Pitfalls - Forgetting to undo the choice after recursing (mutating a shared list without removing the last addition corrupts subsequent branches). - Not skipping duplicates properly in inputs with repeated values (sort first, then skip nums[i] == nums[i-1] at the same recursion depth) — a very common interview trip-up. - Copying the list (new List<int>(current)) when adding to results — otherwise all results reference the same mutated list.


08

8. Disjoint Set Union (Union-Find) & Prefix Sums

8.1 Union-Find (DSU)

Must Know

Summary: A data structure that tracks a partition of elements into disjoint sets, supporting near-O(1) Find (which set does X belong to) and Union (merge two sets) via path compression and union by rank/size.

Identification Signals - "Number of connected components," "redundant connection" - "Accounts merge," "friend circles," "check if graph has a cycle (undirected)" - Dynamic connectivity queries (edges added over time)

Core Strategy 1. parent[i] initially points to itself; rank[i] (or size[i]) tracks tree depth/size for balancing. 2. Find(x): follow parent pointers to the root, compressing the path along the way. 3. Union(x, y): find both roots; if different, attach the smaller-rank tree under the larger one.

C# Pseudo-code Template

// PSEUDO-CODE: Union-Find with Path Compression + Union by Rank
public class UnionFind
{
    private int[] parent;
    private int[] rank;

    public UnionFind(int n)
    {
        parent = new int[n];
        rank = new int[n];
        for (int i = 0; i < n; i++) parent[i] = i;
    }

    public int Find(int x)
    {
        if (parent[x] != x)
            parent[x] = Find(parent[x]);  // path compression
        return parent[x];
    }

    public bool Union(int x, int y)
    {
        int rootX = Find(x), rootY = Find(y);
        if (rootX == rootY) return false;  // already connected -> would form a cycle

        if (rank[rootX] < rank[rootY]) (rootX, rootY) = (rootY, rootX);
        parent[rootY] = rootX;             // union by rank
        if (rank[rootX] == rank[rootY]) rank[rootX]++;
        return true;
    }
}

Benchmarks (Medium/Hard-weighted) - Warmup (optional): 1971. Find if Path Exists in Graph - Medium: 547. Number of Provinces, 684. Redundant Connection, 721. Accounts Merge, 200. Number of Islands (DSU variant) - Hard: 685. Redundant Connection II, 1319. Number of Operations to Make Network Connected, 928. Minimize Malware Spread

Pitfalls - Skipping path compression or union by rank — degrades to O(n) per operation on adversarial inputs (linear chains). Being able to articulate the amortized complexity (inverse Ackermann) is a strong senior signal. - Off-by-one when mapping problem-specific IDs (e.g., 1-indexed accounts) to 0-indexed array positions.


8.2 Prefix Sums (and Difference Arrays)

Very Common

Summary: Precompute cumulative sums so that any range-sum query resolves in O(1), turning an O(n) per-query problem into O(1) per-query after O(n) preprocessing.

Identification Signals - "Range sum query," "subarray sum equals K" - "Number of ways to split array," multiple range queries on a static array

Core Strategy 1. Build prefix[i] = sum(nums[0..i)), so prefix[0] = 0. 2. Range sum of [i, j] = prefix[j+1] - prefix[i]. 3. For "subarray sum equals K" style problems, combine with a hashmap of {prefixSum: count} seen so far.

C# Pseudo-code Template

// PSEUDO-CODE: Prefix Sum + Hashmap for Subarray Sum
public int SubarraySum(int[] nums, int k)
{
    var prefixCount = new Dictionary<int, int> { { 0, 1 } };  // seed: empty prefix
    int sum = 0, count = 0;

    foreach (var num in nums)
    {
        sum += num;
        if (prefixCount.TryGetValue(sum - k, out int freq))
            count += freq;
        prefixCount[sum] = prefixCount.GetValueOrDefault(sum, 0) + 1;
    }
    return count;
}

Benchmarks (Medium/Hard-weighted) - Warmup (optional): 303. Range Sum Query - Immutable - Medium: 560. Subarray Sum Equals K, 238. Product of Array Except Self, 1109. Corporate Flight Bookings (difference array), 974. Subarray Sums Divisible by K - Hard: 327. Count of Range Sums

Pitfalls - Forgetting the seed entry {0: 1} in the hashmap approach — this represents "the empty prefix," needed to correctly count subarrays starting at index 0. - Off-by-one between prefix[j+1] - prefix[i] vs prefix[j] - prefix[i] — always define clearly whether prefix[i] includes or excludes index i.