Algorithm Prep · Philip Olaomo · 2026

Hard 75 sample
+ patterns.

A curated ~25 problems across the 12 patterns that cover ~80% of interview questions. Every problem has a Java solution, a complexity tag, and one sentence on why the trick works.

Patterns covered

  1. Hash map & counting
  2. Two pointers
  3. Sliding window
  4. Prefix sums
  5. Binary search (sorted & on answer)
  6. Stack & monotonic stack
  7. Heap / priority queue
  8. BFS / DFS on graphs
  9. Topological sort
  10. Union-Find
  11. Dynamic programming (1D, 2D, knapsack)
  12. Backtracking

1. Hash map & counting

P01 EASY HASH Two Sum (LC 1)

Given an array nums and target t, return indices i, j with nums[i]+nums[j]=t.

O(n) timeO(n) space
As we walk the array, store each num→index in a map. For each new num, ask the map if t-num was seen. Single pass.
public int[] twoSum(int[] nums, int t) {
  Map<Integer,Integer> seen = new HashMap<>();
  for (int i = 0; i < nums.length; i++) {
    Integer j = seen.get(t - nums[i]);
    if (j != null) return new int[]{j, i};
    seen.put(nums[i], i);
  }
  throw new IllegalArgumentException();
}
P02 MEDIUM HASH Group Anagrams (LC 49)

Group strings that are anagrams of each other.

O(n·k log k) timeO(n·k) space
Canonicalize each string by sorting its characters; use that as the map key.
public List<List<String>> groupAnagrams(String[] strs) {
  Map<String, List<String>> m = new HashMap<>();
  for (String s : strs) {
    char[] c = s.toCharArray();
    Arrays.sort(c);
    m.computeIfAbsent(new String(c), k -> new ArrayList<>()).add(s);
  }
  return new ArrayList<>(m.values());
}

2. Two pointers

P03 MEDIUM 2-PTR Container With Most Water (LC 11)

Given heights, find two lines that with the x-axis form the largest container.

O(n) timeO(1) space
Two pointers at the ends. Always move the shorter wall inward — moving the taller wall can only shrink the area.
public int maxArea(int[] h) {
  int l = 0, r = h.length - 1, best = 0;
  while (l < r) {
    int area = (r - l) * Math.min(h[l], h[r]);
    best = Math.max(best, area);
    if (h[l] < h[r]) l++; else r--;
  }
  return best;
}
P04 HARD 2-PTR Trapping Rain Water (LC 42)

Given an elevation map, compute how much rainwater it can trap.

O(n) timeO(1) space
Two pointers tracking leftMax and rightMax. The water on each side is bounded by the smaller wall, so move the side with the smaller max.
public int trap(int[] h) {
  int l = 0, r = h.length - 1, lm = 0, rm = 0, water = 0;
  while (l < r) {
    if (h[l] < h[r]) {
      lm = Math.max(lm, h[l]);
      water += lm - h[l++];
    } else {
      rm = Math.max(rm, h[r]);
      water += rm - h[r--];
    }
  }
  return water;
}

3. Sliding window

P05 MEDIUM WINDOW Longest Substring Without Repeating Characters (LC 3)

Length of the longest substring with all distinct chars.

O(n) timeO(min(n,Σ))
Window [l..r]; when a duplicate enters, jump l past the last occurrence.
public int lengthOfLongestSubstring(String s) {
  Map<Character,Integer> last = new HashMap<>();
  int best = 0, l = 0;
  for (int r = 0; r < s.length(); r++) {
    char c = s.charAt(r);
    if (last.containsKey(c) && last.get(c) >= l) l = last.get(c) + 1;
    last.put(c, r);
    best = Math.max(best, r - l + 1);
  }
  return best;
}
P06 HARD WINDOW Minimum Window Substring (LC 76)

Smallest window in s containing all characters of t with multiplicity.

O(n+m) time
Two counters, need and have. Expand right, contract left while window is valid.
public String minWindow(String s, String t) {
  int[] need = new int[128];
  for (char c : t.toCharArray()) need[c]++;
  int required = t.length(), l = 0, best = Integer.MAX_VALUE, bs = 0;
  for (int r = 0; r < s.length(); r++) {
    if (need[s.charAt(r)]-- > 0) required--;
    while (required == 0) {
      if (r - l + 1 < best) { best = r - l + 1; bs = l; }
      if (++need[s.charAt(l++)] > 0) required++;
    }
  }
  return best == Integer.MAX_VALUE ? "" : s.substring(bs, bs + best);
}

4. Prefix sums

P07 MEDIUM PREFIX Subarray Sum Equals K (LC 560)

How many contiguous subarrays sum to k?

O(n) timeO(n) space
Track running prefix sum; for each, ask the map “how often have I seen prefix - k before?”
public int subarraySum(int[] nums, int k) {
  Map<Integer,Integer> freq = new HashMap<>();
  freq.put(0, 1);
  int sum = 0, count = 0;
  for (int n : nums) {
    sum += n;
    count += freq.getOrDefault(sum - k, 0);
    freq.merge(sum, 1, Integer::sum);
  }
  return count;
}

5. Binary search

P08 MEDIUM BSEARCH Search in Rotated Sorted Array (LC 33)

Find a target in a rotated sorted array.

O(log n)
At each step exactly one half is sorted — check which, then decide which half can possibly contain the target.
public int search(int[] nums, int t) {
  int l = 0, r = nums.length - 1;
  while (l <= r) {
    int m = (l + r) >>> 1;
    if (nums[m] == t) return m;
    if (nums[l] <= nums[m]) {
      if (t >= nums[l] && t < nums[m]) r = m - 1; else l = m + 1;
    } else {
      if (t > nums[m] && t <= nums[r]) l = m + 1; else r = m - 1;
    }
  }
  return -1;
}
P09 HARD BSEARCH Median of Two Sorted Arrays (LC 4)

Median of two sorted arrays in O(log(min(m,n))).

O(log(min(m,n)))
Binary-search the partition of the smaller array such that maxLeft ≤ minRight across both arrays.
public double findMedianSortedArrays(int[] a, int[] b) {
  if (a.length > b.length) return findMedianSortedArrays(b, a);
  int m = a.length, n = b.length, half = (m + n + 1) / 2;
  int lo = 0, hi = m;
  while (lo <= hi) {
    int i = (lo + hi) / 2, j = half - i;
    int al = i == 0 ? Integer.MIN_VALUE : a[i-1];
    int ar = i == m ? Integer.MAX_VALUE : a[i];
    int bl = j == 0 ? Integer.MIN_VALUE : b[j-1];
    int br = j == n ? Integer.MAX_VALUE : b[j];
    if (al <= br && bl <= ar) {
      if (((m + n) & 1) == 1) return Math.max(al, bl);
      return (Math.max(al, bl) + Math.min(ar, br)) / 2.0;
    } else if (al > br) hi = i - 1;
    else lo = i + 1;
  }
  throw new IllegalStateException();
}
P10 HARD BSEARCH-ANS Capacity to Ship Packages in D Days (LC 1011)

Find the minimum ship capacity that delivers all packages within D days.

O(n log S)
Binary-search on the answer in [max(weights), sum(weights)]. Feasibility check is greedy.
public int shipWithinDays(int[] w, int days) {
  int lo = 0, hi = 0;
  for (int x : w) { lo = Math.max(lo, x); hi += x; }
  while (lo < hi) {
    int m = (lo + hi) >>> 1;
    int d = 1, sum = 0;
    for (int x : w) {
      if (sum + x > m) { d++; sum = x; } else sum += x;
    }
    if (d <= days) hi = m; else lo = m + 1;
  }
  return lo;
}

6. Stack & monotonic stack

P11 MEDIUM MONO-STK Daily Temperatures (LC 739)

For each day, how many days until a warmer one?

O(n)
Stack of decreasing temperatures (indices). Pop while current is warmer; record gap.
public int[] dailyTemperatures(int[] t) {
  int[] ans = new int[t.length];
  Deque<Integer> stk = new ArrayDeque<>();
  for (int i = 0; i < t.length; i++) {
    while (!stk.isEmpty() && t[stk.peek()] < t[i]) {
      int j = stk.pop();
      ans[j] = i - j;
    }
    stk.push(i);
  }
  return ans;
}
P12 HARD MONO-STK Largest Rectangle in Histogram (LC 84)

Largest rectangle area in a histogram.

O(n)
Monotonic increasing stack of bar indices. When a shorter bar arrives, pop and compute width using the previous remaining stack entry as the left bound.
public int largestRectangleArea(int[] h) {
  Deque<Integer> stk = new ArrayDeque<>();
  int best = 0;
  for (int i = 0; i <= h.length; i++) {
    int cur = i == h.length ? 0 : h[i];
    while (!stk.isEmpty() && h[stk.peek()] > cur) {
      int top = stk.pop();
      int left = stk.isEmpty() ? -1 : stk.peek();
      best = Math.max(best, h[top] * (i - left - 1));
    }
    stk.push(i);
  }
  return best;
}

7. Heap / priority queue

P13 MEDIUM HEAP Top K Frequent Elements (LC 347)

Return the k most frequent elements.

O(n log k)
Count, then min-heap of size k on frequency.
public int[] topKFrequent(int[] nums, int k) {
  Map<Integer,Integer> freq = new HashMap<>();
  for (int n : nums) freq.merge(n, 1, Integer::sum);
  PriorityQueue<int[]> pq = new PriorityQueue<>(Comparator.comparingInt(a -> a[1]));
  for (var e : freq.entrySet()) {
    pq.offer(new int[]{e.getKey(), e.getValue()});
    if (pq.size() > k) pq.poll();
  }
  int[] ans = new int[k];
  for (int i = k - 1; i >= 0; i--) ans[i] = pq.poll()[0];
  return ans;
}
P14 HARD HEAP Find Median from Data Stream (LC 295)

Streaming median.

O(log n) per insert
Two heaps: max-heap for the lower half, min-heap for the upper. Keep sizes balanced; median is the root(s).
class MedianFinder {
  PriorityQueue<Integer> lo = new PriorityQueue<>(Comparator.reverseOrder());
  PriorityQueue<Integer> hi = new PriorityQueue<>();
  public void addNum(int n) {
    lo.offer(n);
    hi.offer(lo.poll());
    if (hi.size() > lo.size()) lo.offer(hi.poll());
  }
  public double findMedian() {
    return lo.size() > hi.size() ? lo.peek() : (lo.peek() + hi.peek()) / 2.0;
  }
}

8. BFS / DFS on graphs

P15 MEDIUM BFS Number of Islands (LC 200)

Count connected components of land in a grid.

O(rows·cols)
For each unvisited land cell, BFS/DFS to flood-fill and increment count.
public int numIslands(char[][] g) {
  int rows = g.length, cols = g[0].length, count = 0;
  int[][] dirs = {{0,1},{1,0},{0,-1},{-1,0}};
  for (int i = 0; i < rows; i++) for (int j = 0; j < cols; j++) {
    if (g[i][j] != '1') continue;
    count++;
    Deque<int[]> q = new ArrayDeque<>();
    q.offer(new int[]{i, j}); g[i][j] = '0';
    while (!q.isEmpty()) {
      int[] c = q.poll();
      for (int[] d : dirs) {
        int x = c[0]+d[0], y = c[1]+d[1];
        if (x>=0&&y>=0&&x<rows&&y<cols&&g[x][y]=='1') {
          g[x][y]='0'; q.offer(new int[]{x,y});
        }
      }
    }
  }
  return count;
}
P16 HARD BFS Word Ladder (LC 127)

Shortest transformation sequence from begin to end, changing one letter per step using only words in the dictionary.

O(N·L²)
BFS where neighbours are computed by trying every letter at every position. Use a visited set against re-traversal.
public int ladderLength(String begin, String end, List<String> words) {
  Set<String> dict = new HashSet<>(words);
  if (!dict.contains(end)) return 0;
  Deque<String> q = new ArrayDeque<>();
  q.offer(begin);
  int steps = 1;
  while (!q.isEmpty()) {
    for (int sz = q.size(); sz > 0; sz--) {
      char[] cur = q.poll().toCharArray();
      if (new String(cur).equals(end)) return steps;
      for (int i = 0; i < cur.length; i++) {
        char old = cur[i];
        for (char c = 'a'; c <= 'z'; c++) {
          cur[i] = c;
          String s = new String(cur);
          if (dict.remove(s)) q.offer(s);
        }
        cur[i] = old;
      }
    }
    steps++;
  }
  return 0;
}

9. Topological sort

P17 MEDIUM TOPO Course Schedule II (LC 210)

Return any valid order to finish all courses given prerequisites; [] if impossible.

O(V+E)
Kahn’s: start with all zero-indegree nodes, peel them, decrement neighbours. If you can’t process all nodes there’s a cycle.
public int[] findOrder(int n, int[][] prereq) {
  List<List<Integer>> g = new ArrayList<>();
  for (int i = 0; i < n; i++) g.add(new ArrayList<>());
  int[] in = new int[n];
  for (int[] p : prereq) { g.get(p[1]).add(p[0]); in[p[0]]++; }
  Deque<Integer> q = new ArrayDeque<>();
  for (int i = 0; i < n; i++) if (in[i] == 0) q.offer(i);
  int[] order = new int[n]; int k = 0;
  while (!q.isEmpty()) {
    int u = q.poll();
    order[k++] = u;
    for (int v : g.get(u)) if (--in[v] == 0) q.offer(v);
  }
  return k == n ? order : new int[0];
}

10. Union-Find

P18 MEDIUM DSU Number of Connected Components (LC 323)

Count components in an undirected graph given edges.

O((V+E)·α(V))
DSU with path compression + union by rank — near O(1) amortized per op.
public int countComponents(int n, int[][] edges) {
  int[] p = new int[n], r = new int[n];
  for (int i = 0; i < n; i++) p[i] = i;
  int comps = n;
  for (int[] e : edges) {
    int a = find(p, e[0]), b = find(p, e[1]);
    if (a == b) continue;
    if (r[a] < r[b]) { int t=a; a=b; b=t; }
    p[b] = a;
    if (r[a] == r[b]) r[a]++;
    comps--;
  }
  return comps;
}
int find(int[] p, int x) {
  while (p[x] != x) { p[x] = p[p[x]]; x = p[x]; }
  return x;
}

11. Dynamic programming

P19 MEDIUM DP-1D House Robber II (LC 213)

Robber on a circular street; max loot without robbing adjacent houses.

O(n)
Run linear House Robber twice — once excluding first, once excluding last — and take the max.
public int rob(int[] nums) {
  if (nums.length == 1) return nums[0];
  return Math.max(robLine(nums, 0, nums.length - 2),
                  robLine(nums, 1, nums.length - 1));
}
int robLine(int[] a, int l, int r) {
  int prev = 0, cur = 0;
  for (int i = l; i <= r; i++) {
    int t = Math.max(cur, prev + a[i]);
    prev = cur; cur = t;
  }
  return cur;
}
P20 HARD DP-2D Edit Distance (LC 72)

Minimum operations to transform w1 into w2 (insert/delete/replace).

O(m·n)
dp[i][j] = edit distance between prefixes of length i, j. Match → diagonal; else 1 + min of three.
public int minDistance(String w1, String w2) {
  int m = w1.length(), n = w2.length();
  int[][] dp = new int[m+1][n+1];
  for (int i = 0; i <= m; i++) dp[i][0] = i;
  for (int j = 0; j <= n; j++) dp[0][j] = j;
  for (int i = 1; i <= m; i++) for (int j = 1; j <= n; j++) {
    if (w1.charAt(i-1) == w2.charAt(j-1)) dp[i][j] = dp[i-1][j-1];
    else dp[i][j] = 1 + Math.min(dp[i-1][j-1], Math.min(dp[i-1][j], dp[i][j-1]));
  }
  return dp[m][n];
}
P21 MEDIUM DP-KNAP Coin Change (LC 322)

Fewest coins to make amount amt.

O(amt·n)
Unbounded knapsack: dp[a] = min(dp[a-c]+1) over each coin c.
public int coinChange(int[] coins, int amt) {
  int[] dp = new int[amt + 1];
  Arrays.fill(dp, amt + 1);
  dp[0] = 0;
  for (int a = 1; a <= amt; a++) for (int c : coins) {
    if (c <= a) dp[a] = Math.min(dp[a], dp[a-c] + 1);
  }
  return dp[amt] > amt ? -1 : dp[amt];
}
P22 HARD DP-2D Longest Increasing Subsequence (LC 300)

Length of LIS in O(n log n).

O(n log n)
Maintain an array where tails[k] is the smallest tail of any increasing subseq of length k+1. Binary-search insert each new value.
public int lengthOfLIS(int[] nums) {
  int[] tails = new int[nums.length];
  int len = 0;
  for (int n : nums) {
    int i = Arrays.binarySearch(tails, 0, len, n);
    if (i < 0) i = -(i + 1);
    tails[i] = n;
    if (i == len) len++;
  }
  return len;
}

12. Backtracking

P23 MEDIUM BACKTRK Subsets (LC 78)

Generate all subsets of a unique-element array.

O(n·2ⁿ)
Decision tree: at each index, include or exclude.
public List<List<Integer>> subsets(int[] nums) {
  List<List<Integer>> res = new ArrayList<>();
  dfs(nums, 0, new ArrayList<>(), res);
  return res;
}
void dfs(int[] a, int i, List<Integer> cur, List<List<Integer>> res) {
  if (i == a.length) { res.add(new ArrayList<>(cur)); return; }
  dfs(a, i+1, cur, res);
  cur.add(a[i]);
  dfs(a, i+1, cur, res);
  cur.remove(cur.size()-1);
}
P24 HARD BACKTRK N-Queens (LC 51)

Place n queens on an n×n board with no attacks.

O(n!)
Place row-by-row. Track three sets: occupied columns, “/” diagonals (r+c), “\” diagonals (r-c).
public List<List<String>> solveNQueens(int n) {
  List<List<String>> res = new ArrayList<>();
  int[] q = new int[n];
  boolean[] col = new boolean[n], d1 = new boolean[2*n], d2 = new boolean[2*n];
  bt(0, n, q, col, d1, d2, res);
  return res;
}
void bt(int r, int n, int[] q, boolean[] col, boolean[] d1, boolean[] d2, List<List<String>> res) {
  if (r == n) { res.add(render(q, n)); return; }
  for (int c = 0; c < n; c++) {
    if (col[c] || d1[r+c] || d2[r-c+n]) continue;
    q[r] = c; col[c] = d1[r+c] = d2[r-c+n] = true;
    bt(r+1, n, q, col, d1, d2, res);
    col[c] = d1[r+c] = d2[r-c+n] = false;
  }
}
List<String> render(int[] q, int n) {
  List<String> b = new ArrayList<>();
  for (int r = 0; r < n; r++) {
    char[] row = new char[n];
    Arrays.fill(row, '.');
    row[q[r]] = 'Q';
    b.add(new String(row));
  }
  return b;
}
P25 HARD BACKTRK Word Search II (LC 212)

Find all dictionary words present on the board (with reuse rules).

O(W·L + cells·4^L)
Build a Trie of words; DFS each cell carrying the trie node — instant prune when no child matches.
class Trie { Trie[] kids = new Trie[26]; String word; }

public List<String> findWords(char[][] board, String[] words) {
  Trie root = new Trie();
  for (String w : words) {
    Trie n = root;
    for (char c : w.toCharArray()) {
      if (n.kids[c-'a'] == null) n.kids[c-'a'] = new Trie();
      n = n.kids[c-'a'];
    }
    n.word = w;
  }
  List<String> res = new ArrayList<>();
  for (int i = 0; i < board.length; i++)
    for (int j = 0; j < board[0].length; j++)
      dfs(board, i, j, root, res);
  return res;
}
void dfs(char[][] b, int i, int j, Trie n, List<String> res) {
  if (i<0||j<0||i>=b.length||j>=b[0].length) return;
  char c = b[i][j];
  if (c == '#' || n.kids[c-'a'] == null) return;
  n = n.kids[c-'a'];
  if (n.word != null) { res.add(n.word); n.word = null; }
  b[i][j] = '#';
  dfs(b, i+1, j, n, res); dfs(b, i-1, j, n, res);
  dfs(b, i, j+1, n, res); dfs(b, i, j-1, n, res);
  b[i][j] = c;
}

Built by Philip Olaomo · 2026 · github.com/lastiroko · philipoluwalani@gmail.com