Neetcode 150 — Principal Engineer Solutions

Brute force + 2 optimal approaches per problem in both Java and Python, with trade-off tables and ship-it verdicts. Click any problem to expand. Use the sidebar search to jump.

Arrays & Hashing

#1 LC 217 Easy Contains Duplicate

Detect any repeated value in an array — the canonical hash-set warmup.

Problem Statement

Given an integer array nums, return true if any value appears at least twice, otherwise return false. This is the simplest membership-test problem and the gateway to every deduplication, sliding-window-uniqueness, and frequency-counting question that follows.

Signature: boolean containsDuplicate(int[] nums) / def contains_duplicate(nums: List[int]) -> bool

Examples

Input: nums = [1,2,3,1] Output: true (1 appears twice)
Input: nums = [1,2,3,4] Output: false (all distinct)
Input: nums = [1,1,1,3,3,4,3,2,4,2] Output: true

Constraints

  • 1 <= nums.length <= 10^5
  • -10^9 <= nums[i] <= 10^9

Approach Overview

Brute Force

Java: Nested loop pairwise comparison

Python: Nested loop pairwise comparison

O(n²) O(1)

Optimal #1

Java: HashSet membership probe

Python: set membership (one-liner or explicit loop)

O(n) O(n)

Optimal #2

Java: Sort + adjacent comparison

Python: Counter / dict frequency (or sort)

O(n log n) O(1) extra (Arrays.sort on int[] is in-place dual-pivot quicksort)

Java Solutions

Nested loop pairwise comparison O(n²) O(1)

Compare every pair (i, j). The textbook first attempt — useful baseline, never to be shipped.

Pseudo-code

for i in 0..n-1:
    for j in i+1..n-1:
        if nums[i] == nums[j]:
            return true
return false

Complexity

Time: O(n²) — Worst case (all distinct) does n(n-1)/2 comparisons.
Space: O(1) — Only two loop indices on the stack.

Edge cases & gotchas

  • n == 1 → no pairs to compare, return false.
  • All identical values → terminates on the very first inner-loop step (best case O(1)).
  • n == 10^5 → ~5·10^9 ops, will TLE on LeetCode.

Java code

import java.util.*;

class Solution {
    /**
     * Brute-force pairwise scan. Quadratic — useful only as a correctness baseline.
     * @param nums input array (non-null per problem constraints)
     * @return true iff any value repeats
     */
    public boolean containsDuplicate(final int[] nums) {
        if (nums == null || nums.length < 2) return false;
        final int n = nums.length;
        for (int i = 0; i < n - 1; i++) {
            for (int j = i + 1; j < n; j++) {
                if (nums[i] == nums[j]) return true;
            }
        }
        return false;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.containsDuplicate(new int[]{1,2,3,1}));      // true
        System.out.println(s.containsDuplicate(new int[]{1,2,3,4}));      // false
        System.out.println(s.containsDuplicate(new int[]{}));             // false (edge)
        System.out.println(s.containsDuplicate(new int[]{7}));            // false (edge)
    }
}
HashSet membership probe O(n) O(n)

Stream the array; first time we re-see a value we know it's a dup. Single pass, amortized O(1) per probe.

Pseudo-code

seen = empty hash set
for x in nums:
    if x in seen: return true
    seen.add(x)
return false

Complexity

Time: O(n) — Each element triggers one .add() — amortized O(1). HashMap rehashing is amortized away.
Space: O(n) — Up to n boxed Integers in the open-addressing table.

Edge cases & gotchas

  • Adversarial hash collisions: Java's HashSet uses tree-bucket fallback at load factor — still O(log k) worst case per probe, not O(k).
  • Autoboxing: every int becomes Integer; cache covers [-128,127] only, so heap pressure is real on large random arrays.
  • If the problem allowed mutation we could presize: new HashSet<>(n*4/3 + 1) avoids rehash churn.

Java code

import java.util.*;

class Solution {
    /**
     * Single-pass HashSet probe — the textbook ship-it answer.
     * Pre-sizing the set avoids rehash storms when n is known.
     */
    public boolean containsDuplicate(final int[] nums) {
        if (nums == null || nums.length < 2) return false;
        // Pre-size: load factor 0.75 → capacity = ceil(n / 0.75)
        final Set<Integer> seen = new HashSet<>((int) (nums.length / 0.75f) + 1);
        for (final int x : nums) {
            if (!seen.add(x)) return true;     // .add returns false on duplicate
        }
        return false;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.containsDuplicate(new int[]{1,2,3,1}));               // true
        System.out.println(s.containsDuplicate(new int[]{1,2,3,4}));               // false
        System.out.println(s.containsDuplicate(new int[]{1,1,1,3,3,4,3,2,4,2}));   // true
    }
}
Sort + adjacent comparison O(n log n) O(1) extra (Arrays.sort on int[] is in-place dual-pivot quicksort)

Sort, then any two equal values become adjacent. Trades hash-table space for O(n log n) time and the sort's cache-friendliness.

Pseudo-code

sort(nums)
for i in 1..n-1:
    if nums[i] == nums[i-1]: return true
return false

Complexity

Time: O(n log n) — Dominant cost is the sort. Adjacent scan is O(n).
Space: O(1) extra (Arrays.sort on int[] is in-place dual-pivot quicksort) — Java's int[] sort is in-place; on Integer[] it would be O(n) for TimSort merge buffers.

Edge cases & gotchas

  • Mutates the input — confirm the contract allows it. If not, copy first (O(n) extra).
  • Quicksort worst case is O(n²) on adversarial input, but Java's dual-pivot is robust in practice.
  • Cache-friendly: contiguous int[] sweep beats HashSet's pointer-chasing for large n in real benchmarks.

Java code

import java.util.*;

class Solution {
    /**
     * Sort-then-scan. O(n log n) but cache-friendly and zero auxiliary heap.
     * Mutates the input — caller must accept this or pass a copy.
     */
    public boolean containsDuplicate(final int[] nums) {
        if (nums == null || nums.length < 2) return false;
        Arrays.sort(nums);   // in-place dual-pivot quicksort on primitives
        for (int i = 1; i < nums.length; i++) {
            if (nums[i] == nums[i - 1]) return true;
        }
        return false;
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.containsDuplicate(new int[]{1,2,3,1}));   // true
        System.out.println(s.containsDuplicate(new int[]{1,2,3,4}));   // false
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
Time (worst case)O(n²)O(n log n)
Space (auxiliary)O(1)O(1) — sort is in-place on int[]
Implementation difficulty1/5 — two loops2/5 — Arrays.sort + sweep
Cache behaviorExcellent locality but quadratic work dominatesExcellent — sequential int[] scan
Adversarial inputAlways quadraticQuicksort worst-case rare but exists
Mutates input?NoYes (sort)
When it winsn ≤ ~50: simpler & fewer allocationsMemory-constrained or n very large with cache-bound hardware
PE VerdictShip the HashSet (Optimal #1). It early-exits the moment a duplicate is found, the code reviews itself, and pre-sizing kills rehash overhead. Reach for sort-and-scan only if the caller forbids extra allocation or n is large enough that L1 cache misses dominate hashing wins.

Python Solutions

Nested loop pairwise comparison O(n²) O(1)

Same idea as Java brute force. In Python the constant factor is brutal — interpreted loops of 10^5 elements is 10s of seconds.

Pseudo-code

for i in range(n):
    for j in range(i+1, n):
        if nums[i] == nums[j]:
            return True
return False

Complexity

Time: O(n²) — n(n-1)/2 Python-level comparisons, dominated by interpreter overhead.
Space: O(1) — No auxiliary structures.

Edge cases & gotchas

  • On large n this TLEs even harder than in Java because of Python loop overhead.
  • Use enumerate, never raw indexing — but for brute force the win is negligible.

Python code

from typing import List


class Solution:
    def containsDuplicate(self, nums: List[int]) -> bool:
        """Brute force pairwise check — baseline only."""
        n = len(nums)
        for i in range(n - 1):
            for j in range(i + 1, n):
                if nums[i] == nums[j]:
                    return True
        return False


if __name__ == "__main__":
    s = Solution()
    print(s.containsDuplicate([1, 2, 3, 1]))   # True
    print(s.containsDuplicate([1, 2, 3, 4]))   # False
    print(s.containsDuplicate([]))             # False
    print(s.containsDuplicate([7]))            # False
set membership (one-liner or explicit loop) O(n) O(n)

len(set(nums)) != len(nums) is the idiomatic Python answer. Explicit loop wins on early-exit when duplicates appear early.

Pseudo-code

# idiomatic
return len(set(nums)) != len(nums)
# OR with early-exit:
seen = set()
for x in nums:
    if x in seen: return True
    seen.add(x)
return False

Complexity

Time: O(n) — Each element hashed and inserted once — amortized O(1) per op.
Space: O(n) — Up to n distinct hashes stored.

Edge cases & gotchas

  • len(set(nums)) ALWAYS scans the entire array — no early exit. Use the explicit loop when the input is huge and dups are likely near the start.
  • Python ints are unbounded → no overflow concerns, but very large ints cost more to hash.
  • set() is implemented in C — the one-liner often beats the explicit Python loop on dup-free inputs even though it can't short-circuit.

Python code

from typing import List


class Solution:
    def containsDuplicate(self, nums: List[int]) -> bool:
        """
        Hash-set membership probe with early exit.
        Prefer this over `len(set(nums)) != len(nums)` when duplicates may
        appear early — the loop short-circuits, the one-liner does not.
        """
        seen: set[int] = set()
        for x in nums:
            if x in seen:
                return True
            seen.add(x)
        return False


if __name__ == "__main__":
    s = Solution()
    print(s.containsDuplicate([1, 2, 3, 1]))                       # True
    print(s.containsDuplicate([1, 2, 3, 4]))                       # False
    print(s.containsDuplicate([1, 1, 1, 3, 3, 4, 3, 2, 4, 2]))     # True
Counter / dict frequency (or sort) O(n) O(n)

collections.Counter makes the intent explicit and gives you the count for free if the next question asks 'which one?'. Slower constant factor than a set.

Pseudo-code

counts = Counter(nums)
return any(c >= 2 for c in counts.values())
# Equivalent / often faster:
return counts.most_common(1)[0][1] >= 2

Complexity

Time: O(n) — Counter() is O(n) construction; .most_common(1) is O(n).
Space: O(n) — Stores (value → count) for every distinct value.

Edge cases & gotchas

  • Counter creation always sees every element — no early exit. For 'is there ANY dup' the set approach is strictly better; Counter shines when you also need 'how many' or 'which one'.
  • Empty input → Counter is empty → most_common(1) returns [], guard with `if not nums: return False`.

Python code

from typing import List
from collections import Counter


class Solution:
    def containsDuplicate(self, nums: List[int]) -> bool:
        """
        Frequency-table approach. Slightly slower than a plain set for this
        specific yes/no question, but the natural choice when the follow-up
        is 'which value duplicated' or 'how many duplicates'.
        """
        if not nums:
            return False
        counts = Counter(nums)
        # most_common(1) is O(n) but C-implemented — beats `any()` in practice.
        return counts.most_common(1)[0][1] >= 2


if __name__ == "__main__":
    s = Solution()
    print(s.containsDuplicate([1, 2, 3, 1]))   # True
    print(s.containsDuplicate([1, 2, 3, 4]))   # False

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
Time (worst case)O(n²)O(n)
Space (auxiliary)O(1)O(n)
Implementation difficulty1/52/5
Constant factorWorst — pure Python loopSlightly worse than set — Counter has dict-of-int overhead
Early exit?Yes (in code)No — Counter scans all n
ExtensibilityNonePivots cleanly to 'top-k frequent', 'mode', etc.
When it winsOnly for n < 100 in interview demoWhen the next problem needs counts anyway
PE VerdictShip `len(set(nums)) != len(nums)` for clarity, or the explicit loop variant if early-exit on long arrays matters. Counter is the right answer only when the surrounding code already needs frequencies.

What the interviewer is really testing

On the surface this question tests whether you reach for a hash set instead of nested loops. The PE-level signal is whether you can articulate cost trade-offs without prompting: amortized vs worst-case behavior, cache effects of HashSet pointer-chasing vs sorted scan, autoboxing pressure in Java, and the GIL non-issue in Python. Bonus signal: can you propose Bloom filter / count-min sketch when n is gigantic and a small false-positive rate is acceptable?

Top gotchas

  • len(set(nums)) does not short-circuit — it always scans the full array. Use the explicit loop when early termination matters.
  • Java's HashSet rehashes when load factor (0.75) is exceeded; pre-size with `new HashSet<>((int)(n/0.75f)+1)` for hot paths.
  • Autoboxing in Java: int[] → Integer wrappers cost heap allocations; the int Integer cache only covers [-128, 127].
  • Sort-and-scan mutates the input — confirm with the caller, otherwise copy first (negating the O(1) extra-space win).

Ship-it

HashSet probe with early exit. In Java, pre-size and use `set.add()` returning false as the dup signal — no second `.contains()` call. In Python, the explicit loop with `seen.add()` if early-exit matters; otherwise the one-liner `len(set(nums)) != len(nums)`.

#2 LC 242 Easy Valid Anagram

Are two strings permutations of each other? Frequency-counting in disguise.

Problem Statement

Given two strings s and t, return true if t is an anagram of s. An anagram uses exactly the same multiset of characters in any order.

Signature: boolean isAnagram(String s, String t) / def is_anagram(s: str, t: str) -> bool

Examples

Input: s = "anagram", t = "nagaram" Output: true
Input: s = "rat", t = "car" Output: false

Constraints

  • 1 <= s.length, t.length <= 5 * 10^4
  • s and t consist of lowercase English letters (per the basic problem; Unicode follow-up below).

Approach Overview

Brute Force

Java: Sort both strings and compare

Python: sorted() comparison

O(n log n) O(n) for the char[] copies (Arrays.sort on char[] is in-place but we still allocated).

Optimal #1

Java: Fixed 26-bucket frequency array

Python: collections.Counter equality

O(n) O(1) — fixed alphabet

Optimal #2

Java: HashMap counter (Unicode-safe)

Python: Single-dict inc/dec with early exit

O(n) O(k) where k = distinct codepoints

Java Solutions

Sort both strings and compare O(n log n) O(n) for the char[] copies (Arrays.sort on char[] is in-place but we still allocated).

Sort each string into canonical form, compare. Trades extra time for one-line clarity.

Pseudo-code

if len(s) != len(t): return false
return sorted(s) == sorted(t)

Complexity

Time: O(n log n) — Dominated by two sorts.
Space: O(n) for the char[] copies (Arrays.sort on char[] is in-place but we still allocated). — Two char[] of length n.

Edge cases & gotchas

  • Different lengths short-circuit immediately — always check first.
  • Sort allocates a char[] copy — do not call .toCharArray() twice unnecessarily.

Java code

import java.util.Arrays;

class Solution {
    public boolean isAnagram(final String s, final String t) {
        if (s == null || t == null || s.length() != t.length()) return false;
        final char[] a = s.toCharArray();
        final char[] b = t.toCharArray();
        Arrays.sort(a);
        Arrays.sort(b);
        return Arrays.equals(a, b);
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.isAnagram("anagram", "nagaram"));   // true
        System.out.println(sol.isAnagram("rat", "car"));           // false
        System.out.println(sol.isAnagram("a", "ab"));              // false
    }
}
Fixed 26-bucket frequency array O(n) O(1) — fixed alphabet

Lowercase ASCII collapses to a 26-int array. One pass increments for s, decrements for t. Anagram iff all zero.

Pseudo-code

if len(s) != len(t): return false
counts = int[26]
for i in 0..n-1:
    counts[s[i] - 'a'] += 1
    counts[t[i] - 'a'] -= 1
return all c == 0

Complexity

Time: O(n) — Two linear passes; second can fuse into one with the inc/dec trick.
Space: O(1) — fixed alphabet — 26 ints regardless of input size.

Edge cases & gotchas

  • Only correct for the lowercase-ASCII contract. Unicode breaks this — see Optimal #2.
  • Cleaner than a HashMap because the alphabet is bounded and known.
  • Cache-friendly: 26 ints fit in a single cache line.

Java code

class Solution {
    public boolean isAnagram(final String s, final String t) {
        if (s == null || t == null || s.length() != t.length()) return false;
        final int[] counts = new int[26];   // lowercase ASCII alphabet
        for (int i = 0, n = s.length(); i < n; i++) {
            counts[s.charAt(i) - 'a']++;
            counts[t.charAt(i) - 'a']--;
        }
        for (final int c : counts) if (c != 0) return false;
        return true;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.isAnagram("anagram", "nagaram"));   // true
        System.out.println(sol.isAnagram("rat", "car"));           // false
    }
}
HashMap counter (Unicode-safe) O(n) O(k) where k = distinct codepoints

Generalizes to any alphabet — Unicode, emoji, mixed case. The right answer when the constraint widens.

Pseudo-code

if len(s) != len(t): return false
map = HashMap<codepoint,int>
for c in s: map[c] += 1
for c in t:
    map[c] -= 1
    if map[c] < 0: return false
return true

Complexity

Time: O(n) — Two passes over the strings; each map op is amortized O(1).
Space: O(k) where k = distinct codepoints — Up to min(n, |alphabet|) entries.

Edge cases & gotchas

  • Use codePointAt + iterate by Character.charCount(cp) for surrogate pairs (emoji, 𝕊).
  • Negative-count short-circuit lets us return early on any mismatch.
  • Initial-capacity hint: new HashMap<>(min(n, 128)) trims rehash storms.

Java code

import java.util.HashMap;
import java.util.Map;

class Solution {
    public boolean isAnagram(final String s, final String t) {
        if (s == null || t == null || s.length() != t.length()) return false;
        final Map<Character, Integer> counts = new HashMap<>(Math.min(s.length(), 128));
        for (final char c : s.toCharArray()) counts.merge(c, 1, Integer::sum);
        for (final char c : t.toCharArray()) {
            final Integer v = counts.get(c);
            if (v == null || v == 0) return false;
            counts.put(c, v - 1);
        }
        return true;
    }
    /* Unicode-safe variant: iterate s.codePoints().forEach(...) and use Map<Integer,Integer>. */

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.isAnagram("anagram", "nagaram"));   // true
        System.out.println(sol.isAnagram("rat", "car"));           // false
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n log n)O(n)
Space (auxiliary)O(n) char[]O(k) — k distinct chars
Difficulty1/5 — sort-and-compare2/5 — HashMap merge
Constant factor~3× slower than counter~3× slower than array — HashMap overhead
Alphabet supportAny (sort works on chars)Any (Unicode if codepoint-based)
Early exitNoYes (negative count fires immediately)
When it winsWhiteboard, no constraints statedUnicode, mixed case, follow-ups
PE VerdictFor the stated lowercase-ASCII contract, ship the 26-int counter — fastest, simplest, lockstep with the constraint. The instant the interviewer says 'what if the input is Unicode?' pivot to the HashMap (Optimal #2). Sort-and-compare is the answer when you have 30 seconds and need to be correct, not fast.

Python Solutions

sorted() comparison O(n log n) O(n)

One-liner: sorted(s) == sorted(t). C-implemented Timsort makes this surprisingly competitive in pure Python.

Pseudo-code

if len(s) != len(t): return False
return sorted(s) == sorted(t)

Complexity

Time: O(n log n) — Two Timsort passes.
Space: O(n) — Two list[str] of length n.

Edge cases & gotchas

  • sorted() on a str returns list[str], not str — comparison still works element-wise.
  • For very long strings, Timsort's run-detection on already-sorted prefixes is a bonus.

Python code

class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        """Sort and compare — clearest one-liner."""
        return len(s) == len(t) and sorted(s) == sorted(t)


if __name__ == "__main__":
    sol = Solution()
    print(sol.isAnagram("anagram", "nagaram"))   # True
    print(sol.isAnagram("rat", "car"))           # False
collections.Counter equality O(n) O(k) where k = distinct chars

Counter(s) == Counter(t) is the idiomatic Python answer — readable, Unicode-safe, C-level fast.

Pseudo-code

if len(s) != len(t): return False
return Counter(s) == Counter(t)

Complexity

Time: O(n) — Two C-level dict builds, single dict-equality check.
Space: O(k) where k = distinct chars — Two Counter objects sized by distinct chars.

Edge cases & gotchas

  • Counter equality treats missing keys as 0 — Counter(s) == Counter(t) handles 'extra char' correctly.
  • For short strings (n < 50) plain sorted() can actually be faster — Counter has constant overhead.
  • Naturally handles Unicode and any hashable element type.

Python code

from collections import Counter


class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        """
        Idiomatic Python: build two frequency tables, compare for equality.
        Unicode-safe out of the box. Counter is implemented in C.
        """
        if len(s) != len(t):
            return False
        return Counter(s) == Counter(t)


if __name__ == "__main__":
    sol = Solution()
    print(sol.isAnagram("anagram", "nagaram"))   # True
    print(sol.isAnagram("rat", "car"))           # False
    print(sol.isAnagram("résumé", "ésumér"))     # True (Unicode)
Single-dict inc/dec with early exit O(n) O(k)

Walk both strings together, increment for s, decrement for t. Bail out on first negative. Beats Counter on adversarial inputs that diverge early.

Pseudo-code

if len(s) != len(t): return False
counts = defaultdict(int)
for cs, ct in zip(s, t):
    counts[cs] += 1
    counts[ct] -= 1
return all(v == 0 for v in counts.values())

Complexity

Time: O(n) — Single pass; early exit on mismatch.
Space: O(k) — One dict, k distinct chars.

Edge cases & gotchas

  • all() over .values() short-circuits on first non-zero.
  • Does NOT short-circuit during the building phase — the inc/dec pattern must complete; if you want early exit, decrement first and bail when count goes below zero.

Python code

from typing import Dict


class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        """
        One-pass inc/dec counter. Marginally faster than two Counter() builds
        on long strings because it's a single iteration with early-exit
        potential.
        """
        if len(s) != len(t):
            return False
        counts: Dict[str, int] = {}
        for cs, ct in zip(s, t):
            counts[cs] = counts.get(cs, 0) + 1
            counts[ct] = counts.get(ct, 0) - 1
        # all() short-circuits — fast when most are zero by midpoint.
        return all(v == 0 for v in counts.values())


if __name__ == "__main__":
    sol = Solution()
    print(sol.isAnagram("anagram", "nagaram"))   # True
    print(sol.isAnagram("rat", "car"))           # False

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n log n)O(n)
SpaceO(n)O(k)
Difficulty1/52/5
Constant factorSurprisingly OK — Timsort in CSlightly worse than Counter for happy path
UnicodeWorksWorks
ReadabilityBest — sorted(s) == sorted(t)OK — looks engineered
When it winsTiny n; demo codeStreaming or memory-pressure scenarios where you want explicit control
PE VerdictShip Counter equality. It's the Python that Python wants. The single-dict inc/dec is a good answer when the interviewer asks 'show me how Counter would be implemented underneath'. sorted() is fine for reading aloud but you'd never put it in production for n in the millions.

What the interviewer is really testing

The trap here is choosing the right structure for the stated alphabet. PE-level signal is recognizing that 26 lowercase letters → bounded array, 'any string' → HashMap/dict. Bonus signal: you mention what changes when the input becomes Unicode (surrogate pairs, normalization forms — NFC vs NFD).

Top gotchas

  • Length-mismatch short-circuit must be first — saves doing any work for the easy 'no' case.
  • Java char vs codepoint: charAt(i) returns a UTF-16 code unit, not a codepoint. For 'café' (combining accent) or emoji, switch to codePointAt + Character.charCount.
  • Unicode normalization: 'café' (precomposed) and 'cafe\u0301' (decomposed) are visually identical but compare as different. Apply Normalizer.normalize() if the spec demands it.
  • Counter equality in Python ignores zero values — Counter({'a':1,'b':0}) == Counter({'a':1}). Useful, occasionally surprising.

Ship-it

Java: 26-int counter when the constraint is lowercase ASCII; HashMap<Character,Integer> when it's not. Python: Counter(s) == Counter(t) — the most readable correct answer.

#3 LC 1 Easy Two Sum

Find the two indices that sum to target — the foundation problem of the hash-map era.

Problem Statement

Given an array of integers nums and a target sum target, return the indices of the two numbers that add up to target. Exactly one solution exists, and the same element cannot be used twice.

Signature: int[] twoSum(int[] nums, int target) / def two_sum(nums: List[int], target: int) -> List[int]

Examples

Input: nums = [2,7,11,15], target = 9 Output: [0,1] (nums[0]+nums[1] = 2+7 = 9)
Input: nums = [3,2,4], target = 6 Output: [1,2]
Input: nums = [3,3], target = 6 Output: [0,1]

Constraints

  • 2 <= nums.length <= 10^4
  • -10^9 <= nums[i] <= 10^9
  • -10^9 <= target <= 10^9
  • Exactly one valid answer exists.

Approach Overview

Brute Force

Java: Nested-loop pair check

Python: Nested loops

O(n²) O(1)

Optimal #1

Java: Single-pass HashMap (value → index)

Python: Single-pass dict (value → index)

O(n) O(n)

Optimal #2

Java: Sort + two pointers (returns values, not original indices)

Python: Sort + two pointers (with index recovery)

O(n log n) O(n) for the index-paired copy

Java Solutions

Nested-loop pair check O(n²) O(1)

Try every (i, j). Quadratic but illustrates the search space and is sometimes the right answer when n ≤ 50.

Pseudo-code

for i in 0..n-1:
    for j in i+1..n-1:
        if nums[i] + nums[j] == target:
            return [i, j]

Complexity

Time: O(n²) — n(n-1)/2 pair sums.
Space: O(1) — Two indices.

Edge cases & gotchas

  • Watch for int overflow only if values approach Integer.MAX_VALUE / 2 — guard with long if so.
  • Inner loop starts at i+1 to avoid using the same element twice.

Java code

class Solution {
    public int[] twoSum(final int[] nums, final int target) {
        for (int i = 0; i < nums.length - 1; i++) {
            for (int j = i + 1; j < nums.length; j++) {
                if (nums[i] + nums[j] == target) return new int[]{i, j};
            }
        }
        throw new IllegalArgumentException("No two-sum solution");
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(java.util.Arrays.toString(s.twoSum(new int[]{2,7,11,15}, 9)));   // [0, 1]
        System.out.println(java.util.Arrays.toString(s.twoSum(new int[]{3,2,4}, 6)));       // [1, 2]
    }
}
Single-pass HashMap (value → index) O(n) O(n)

While scanning, ask 'have I seen target - x already?' If yes, done. Single pass, O(n).

Pseudo-code

seen = HashMap<value, index>
for i, x in enumerate(nums):
    need = target - x
    if need in seen: return [seen[need], i]
    seen[x] = i

Complexity

Time: O(n) — Each element does one .get and at most one .put.
Space: O(n) — Up to n-1 entries (the last element finds its complement and never inserts).

Edge cases & gotchas

  • Duplicate values: by inserting AFTER the lookup we never match an element with itself.
  • Negative numbers and zero work fine — hash map keys are unrestricted.
  • Pre-size the map to avoid rehash thrash on large n.

Java code

import java.util.HashMap;
import java.util.Map;

class Solution {
    /**
     * Single-pass: for each x, look up target - x in the map.
     * Insert AFTER the lookup so we never pair an element with itself.
     */
    public int[] twoSum(final int[] nums, final int target) {
        final Map<Integer, Integer> seen = new HashMap<>((int) (nums.length / 0.75f) + 1);
        for (int i = 0; i < nums.length; i++) {
            final int need = target - nums[i];
            final Integer j = seen.get(need);
            if (j != null) return new int[]{j, i};
            seen.put(nums[i], i);
        }
        throw new IllegalArgumentException("No two-sum solution");
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(java.util.Arrays.toString(s.twoSum(new int[]{2,7,11,15}, 9)));   // [0, 1]
        System.out.println(java.util.Arrays.toString(s.twoSum(new int[]{3,3}, 6)));         // [0, 1]
    }
}
Sort + two pointers (returns values, not original indices) O(n log n) O(n) for the index-paired copy

Sort copy; converge l/r toward the target. O(n log n) time, O(1) extra heap (sort is in-place on int[]). Index-recovery requires a second pass.

Pseudo-code

pairs = [(nums[i], i) for i in 0..n-1]
sort pairs by value
l, r = 0, n-1
while l < r:
    s = pairs[l].v + pairs[r].v
    if s == target: return [pairs[l].i, pairs[r].i]
    elif s < target: l += 1
    else: r -= 1

Complexity

Time: O(n log n) — Sort dominates; pointer sweep is O(n).
Space: O(n) for the index-paired copy — We must keep original indices, so we sort an int[][] of (value, index).

Edge cases & gotchas

  • Pure two-pointer on a sorted array assumes the original order is irrelevant — it isn't here, hence the index pairing.
  • If the API allowed returning values rather than indices, the implementation simplifies.
  • Watch for overflow on the sum — guard with long if values approach Integer.MAX_VALUE.

Java code

import java.util.Arrays;

class Solution {
    /**
     * Sort + two pointers. We keep original indices alongside values to
     * satisfy the index-return contract. O(n log n) time.
     */
    public int[] twoSum(final int[] nums, final int target) {
        final int n = nums.length;
        final int[][] pairs = new int[n][2];
        for (int i = 0; i < n; i++) { pairs[i][0] = nums[i]; pairs[i][1] = i; }
        Arrays.sort(pairs, (a, b) -> Integer.compare(a[0], b[0]));
        int l = 0, r = n - 1;
        while (l < r) {
            final long sum = (long) pairs[l][0] + pairs[r][0];   // long to avoid overflow
            if (sum == target) return new int[]{pairs[l][1], pairs[r][1]};
            if (sum < target) l++; else r--;
        }
        throw new IllegalArgumentException("No two-sum solution");
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(java.util.Arrays.toString(s.twoSum(new int[]{2,7,11,15}, 9)));   // some valid pair
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n log n)
SpaceO(1)O(n) for index-pair array
Difficulty1/53/5 — must keep indices
Cache behaviorBest — sequential arrayOK — sort is contiguous, but pair[][] hurts locality
Order-preserving?Yes — returns first found pairNo — order depends on sort
Adversarial inputAlways quadraticQuicksort worst-case
When it winsn ≤ 50, no extra allocation allowedWhen extra space is forbidden AND the array can be mutated AND values, not indices, are returned
PE VerdictShip the single-pass HashMap. It's the only O(n) approach for this problem given the index-return contract, and the implementation reads itself. Two-pointer-after-sort is the right tool for follow-up problems (3Sum, k-sum) but is strictly worse here.

Python Solutions

Nested loops O(n²) O(1)

Same idea — quadratic, fine for tiny n.

Pseudo-code

for i, x in enumerate(nums):
    for j in range(i+1, len(nums)):
        if x + nums[j] == target:
            return [i, j]

Complexity

Time: O(n²) — All pairs.
Space: O(1) — None.

Edge cases & gotchas

  • TLEs on n = 10^4 in pure Python.

Python code

from typing import List


class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        n = len(nums)
        for i in range(n - 1):
            for j in range(i + 1, n):
                if nums[i] + nums[j] == target:
                    return [i, j]
        return []


if __name__ == "__main__":
    s = Solution()
    print(s.twoSum([2, 7, 11, 15], 9))   # [0, 1]
Single-pass dict (value → index) O(n) O(n)

Idiomatic Python: scan once, dict-lookup the complement, insert after.

Pseudo-code

seen = {}
for i, x in enumerate(nums):
    need = target - x
    if need in seen: return [seen[need], i]
    seen[x] = i

Complexity

Time: O(n) — Single pass, O(1) per dict op.
Space: O(n) — Up to n entries.

Edge cases & gotchas

  • Duplicates: insert AFTER lookup so an element doesn't pair with itself.
  • Use enumerate, not range(len(nums)) — Pythonic, slightly faster.

Python code

from typing import List, Dict


class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        """Single-pass complement lookup."""
        seen: Dict[int, int] = {}
        for i, x in enumerate(nums):
            need = target - x
            if need in seen:
                return [seen[need], i]
            seen[x] = i
        return []   # contract guarantees a solution; this is defensive


if __name__ == "__main__":
    s = Solution()
    print(s.twoSum([2, 7, 11, 15], 9))   # [0, 1]
    print(s.twoSum([3, 2, 4], 6))        # [1, 2]
    print(s.twoSum([3, 3], 6))           # [0, 1]
Sort + two pointers (with index recovery) O(n log n) O(n)

Sort an (value, index) tuple list, converge from both ends. Slower than dict but illustrates the technique used by k-sum follow-ups.

Pseudo-code

pairs = sorted((x, i) for i, x in enumerate(nums))
l, r = 0, n-1
while l < r:
    s = pairs[l][0] + pairs[r][0]
    if s == target: return sorted([pairs[l][1], pairs[r][1]])
    elif s < target: l += 1
    else: r -= 1

Complexity

Time: O(n log n) — Sort dominates.
Space: O(n) — Auxiliary tuple list.

Edge cases & gotchas

  • Final return sorts the indices for stable comparison if the spec wants ascending order.
  • Python ints are unbounded — no overflow worries.

Python code

from typing import List


class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        """
        Sort + two pointers. Slower than the dict approach but the technique
        generalizes cleanly to 3Sum / kSum — worth knowing.
        """
        pairs = sorted((x, i) for i, x in enumerate(nums))
        l, r = 0, len(pairs) - 1
        while l < r:
            s = pairs[l][0] + pairs[r][0]
            if s == target:
                return sorted([pairs[l][1], pairs[r][1]])
            if s < target:
                l += 1
            else:
                r -= 1
        return []


if __name__ == "__main__":
    s = Solution()
    print(s.twoSum([2, 7, 11, 15], 9))   # [0, 1]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n log n)
SpaceO(1)O(n)
Difficulty1/53/5
Constant factorWorst — pure Python loopsSort is C-level fast but tuple overhead hurts
GeneralizationNonePivots to canonical 3Sum / kSum two-pointer technique
When it winsDemo / very small nWhen you're warming up for 3Sum
PE VerdictShip the single-pass dict. The two-pointer variant is mandatory study material because of how cleanly it scales to 3Sum, but for this exact problem the dict is faster and shorter.

What the interviewer is really testing

Two Sum is the canonical 'hash-map vs nested loop' question. PE-level signal: do you insert after lookup (handles duplicates correctly), do you call out the index-vs-value contract that breaks naive sort+two-pointer, and can you transition smoothly into the follow-up 'how does this generalize to 3Sum / kSum'?

Top gotchas

  • Insert into the map AFTER the complement lookup — otherwise nums = [3,3], target = 6 returns [0,0] (same element used twice).
  • Sort + two pointers loses original indices unless you carry them through the sort.
  • Watch overflow when summing two ints near Integer.MAX_VALUE — cast to long.
  • Pre-sizing the HashMap (Java) by n / 0.75 + 1 avoids rehash storms on hot paths.

Ship-it

Single-pass HashMap / dict in both languages. Insert after lookup. Pre-size in Java. Use enumerate in Python.

#4 LC 49 Medium Group Anagrams

Bucket strings by their canonical form — a hash-key design problem.

Problem Statement

Given an array of strings strs, group the anagrams together. Return a list of groups in any order. All strings in the same group are anagrams of one another.

Signature: List<List<String>> groupAnagrams(String[] strs) / def group_anagrams(strs: List[str]) -> List[List[str]]

Examples

Input: strs = ["eat","tea","tan","ate","nat","bat"] Output: [["bat"],["nat","tan"],["ate","eat","tea"]]
Input: strs = [""] Output: [[""]]
Input: strs = ["a"] Output: [["a"]]

Constraints

  • 1 <= strs.length <= 10^4
  • 0 <= strs[i].length <= 100
  • strs[i] consists of lowercase English letters.

Approach Overview

Brute Force

Java: Pairwise anagram check

Python: Pairwise anagram check

O(n² · k log k) where k = max string length O(n·k) for output

Optimal #1

Java: HashMap keyed by sorted string

Python: defaultdict keyed by sorted tuple

O(n · k log k) O(n · k)

Optimal #2

Java: HashMap keyed by 26-frequency signature

Python: defaultdict keyed by 26-frequency tuple

O(n · k) O(n · k)

Java Solutions

Pairwise anagram check O(n² · k log k) where k = max string length O(n·k) for output

For each unbucketed string, scan all buckets to see if it matches. Quadratic and bad — establish the baseline.

Pseudo-code

groups = []
for s in strs:
    placed = False
    for g in groups:
        if isAnagram(s, g[0]): g.add(s); placed = True; break
    if not placed: groups.append([s])
return groups

Complexity

Time: O(n² · k log k) where k = max string length — n² pairs × k log k per anagram check.
Space: O(n·k) for output — Output dominates auxiliary.

Edge cases & gotchas

  • All distinct → worst case, no early-exit.
  • Empty string is its own group.

Java code

import java.util.*;

class Solution {
    public List<List<String>> groupAnagrams(final String[] strs) {
        final List<List<String>> groups = new ArrayList<>();
        for (final String s : strs) {
            boolean placed = false;
            for (final List<String> g : groups) {
                if (isAnagram(s, g.get(0))) { g.add(s); placed = true; break; }
            }
            if (!placed) { final List<String> ng = new ArrayList<>(); ng.add(s); groups.add(ng); }
        }
        return groups;
    }

    private boolean isAnagram(final String a, final String b) {
        if (a.length() != b.length()) return false;
        final char[] ca = a.toCharArray(); final char[] cb = b.toCharArray();
        Arrays.sort(ca); Arrays.sort(cb);
        return Arrays.equals(ca, cb);
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.groupAnagrams(new String[]{"eat","tea","tan","ate","nat","bat"}));
    }
}
HashMap keyed by sorted string O(n · k log k) O(n · k)

Sorting normalizes each string to a canonical key. All anagrams share that key. One pass.

Pseudo-code

groups = HashMap<String, List<String>>
for s in strs:
    key = sorted(s)
    groups[key].append(s)
return groups.values()

Complexity

Time: O(n · k log k) — Each of n strings costs k log k to sort.
Space: O(n · k) — Map values store all input strings.

Edge cases & gotchas

  • Sorted-string key uses O(k) bytes per entry.
  • Empty string sorts to empty — distinct group, correct behavior.
  • computeIfAbsent avoids the get-then-put dance.

Java code

import java.util.*;

class Solution {
    /**
     * Canonical form by sort. Cleanest reading; O(n·k log k) total work.
     */
    public List<List<String>> groupAnagrams(final String[] strs) {
        final Map<String, List<String>> groups = new HashMap<>();
        for (final String s : strs) {
            final char[] chars = s.toCharArray();
            Arrays.sort(chars);
            final String key = new String(chars);
            groups.computeIfAbsent(key, k -> new ArrayList<>()).add(s);
        }
        return new ArrayList<>(groups.values());
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.groupAnagrams(new String[]{"eat","tea","tan","ate","nat","bat"}));
    }
}
HashMap keyed by 26-frequency signature O(n · k) O(n · k)

Skip the sort — use a 26-int frequency vector as the key. Stringified into 'a3b1c0...' for the map.

Pseudo-code

for s in strs:
    counts = int[26]
    for c in s: counts[c - 'a'] += 1
    key = encode(counts)        # 'a3b1c0...' or '#3#1#0#...'
    groups[key].append(s)

Complexity

Time: O(n · k) — Linear per string; signature build is k + 26.
Space: O(n · k) — Same output footprint.

Edge cases & gotchas

  • Avoid using Arrays.toString(counts) — it allocates a wasteful string. Build with StringBuilder + delimiters.
  • For the lowercase-only contract, this strictly beats sort. For Unicode, fall back to sort or a TreeMap-of-codepoints.

Java code

import java.util.*;

class Solution {
    /**
     * Frequency-vector canonical form: O(n·k) total. Beats the sort-key
     * approach when k log k > k (always for k ≥ 4 with cache-friendly arrays).
     */
    public List<List<String>> groupAnagrams(final String[] strs) {
        final Map<String, List<String>> groups = new HashMap<>();
        for (final String s : strs) {
            final int[] counts = new int[26];
            for (int i = 0; i < s.length(); i++) counts[s.charAt(i) - 'a']++;
            // Build a delimited signature; '#' avoids 'a11' colliding with 'a1+a1'.
            final StringBuilder sb = new StringBuilder(60);
            for (final int c : counts) { sb.append('#').append(c); }
            groups.computeIfAbsent(sb.toString(), k -> new ArrayList<>()).add(s);
        }
        return new ArrayList<>(groups.values());
    }

    public static void main(String[] args) {
        Solution s = new Solution();
        System.out.println(s.groupAnagrams(new String[]{"eat","tea","tan","ate","nat","bat"}));
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²·k log k)O(n·k)
Space (auxiliary)O(n·k)O(n·k)
Difficulty2/53/5 — must avoid signature collisions
Constant factorBrutalBest for k ≥ 4 and small alphabet
AlphabetAnyLowercase ASCII only (trivially extends to bounded alphabets)
ReadabilityOKSlightly noisier
When it winsn ≤ 100, demoHot path with stated lowercase contract
PE VerdictShip the sort-key (Optimal #1) by default — it's the most readable, alphabet-agnostic, and fast enough at LeetCode scale (n=10^4, k=100 → 10^6·log₂100 ≈ 7·10^6 ops). Switch to the frequency-vector key (Optimal #2) when the alphabet is small and you're profiling a hot path.

Python Solutions

Pairwise anagram check O(n² · k log k) O(n · k)

For each string, scan existing groups. Same quadratic shape as Java baseline.

Pseudo-code

groups = []
for s in strs:
    for g in groups:
        if sorted(s) == sorted(g[0]): g.append(s); break
    else:
        groups.append([s])

Complexity

Time: O(n² · k log k) — Quadratic.
Space: O(n · k) — Output.

Edge cases & gotchas

  • TLEs on n = 10^4.

Python code

from typing import List


class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        groups: List[List[str]] = []
        for s in strs:
            key = sorted(s)
            for g in groups:
                if sorted(g[0]) == key:
                    g.append(s)
                    break
            else:
                groups.append([s])
        return groups


if __name__ == "__main__":
    print(Solution().groupAnagrams(["eat","tea","tan","ate","nat","bat"]))
defaultdict keyed by sorted tuple O(n · k log k) O(n · k)

tuple(sorted(s)) is hashable and cheap to compute. defaultdict(list) writes itself.

Pseudo-code

groups = defaultdict(list)
for s in strs:
    groups[''.join(sorted(s))].append(s)
return list(groups.values())

Complexity

Time: O(n · k log k) — Sort per string.
Space: O(n · k) — All input strings end up in the dict values.

Edge cases & gotchas

  • Use ''.join(sorted(s)) as the key — strings hash faster than tuples in CPython.
  • Empty string maps to empty key — its own group, correct.

Python code

from typing import List, Dict
from collections import defaultdict


class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        """Sort each string into its canonical form; bucket by that key."""
        groups: Dict[str, List[str]] = defaultdict(list)
        for s in strs:
            groups[''.join(sorted(s))].append(s)
        return list(groups.values())


if __name__ == "__main__":
    print(Solution().groupAnagrams(["eat","tea","tan","ate","nat","bat"]))
    # [['eat','tea','ate'], ['tan','nat'], ['bat']]
defaultdict keyed by 26-frequency tuple O(n · k) O(n · k)

Skip the sort: build a 26-int tuple, use as key. Avoids k log k entirely.

Pseudo-code

groups = defaultdict(list)
for s in strs:
    counts = [0]*26
    for c in s: counts[ord(c)-ord('a')] += 1
    groups[tuple(counts)].append(s)
return list(groups.values())

Complexity

Time: O(n · k) — Linear per string.
Space: O(n · k) — Same.

Edge cases & gotchas

  • Tuple keys hash in O(26) — comparable to a 26-char string.
  • Only valid for the lowercase-ASCII contract.

Python code

from typing import List, Dict, Tuple
from collections import defaultdict


class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        """
        Frequency-vector canonical form. Skips the per-string sort, so it's
        asymptotically better when k is large.
        """
        groups: Dict[Tuple[int, ...], List[str]] = defaultdict(list)
        for s in strs:
            counts = [0] * 26
            for c in s:
                counts[ord(c) - 97] += 1
            groups[tuple(counts)].append(s)
        return list(groups.values())


if __name__ == "__main__":
    print(Solution().groupAnagrams(["eat","tea","tan","ate","nat","bat"]))

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²·k log k)O(n·k)
SpaceO(n·k)O(n·k)
Difficulty2/52/5
Constant factorWorstBest for k ≥ 10 and known alphabet
AlphabetAny hashable charLowercase ASCII only
ReadabilityVerboseSlightly more code
When it winsTiny demoProfiled hot path with stated contract
PE VerdictShip the sorted-key defaultdict in Python. It's three lines, alphabet-agnostic, and CPython's Timsort makes the constant factor tolerable. Reach for the frequency tuple only when the alphabet is bounded and you've measured.

What the interviewer is really testing

This problem is hash-key design dressed as string manipulation. PE-level signal: do you propose multiple canonicalization strategies (sort-key vs frequency-vector) and discuss when each wins? Do you avoid the subtle collision in 'a11' vs 'a1' + 'a1' if you ever stringify counts without delimiters?

Top gotchas

  • Naive concat of '+ count' creates collisions: counts [1,11,...] and [11,1,...] would both stringify to '111...'. Use a delimiter.
  • Choosing tuple(...) vs ''.join(...) as the dict key: in CPython, strings hash faster than tuples for long keys but tuples are cheaper to construct from a list.
  • Don't return groups.values() directly in Java without wrapping in ArrayList — the view's iteration order isn't guaranteed across versions.
  • Frequency-vector approach assumes a bounded alphabet; for Unicode anagrams (rare interview ask) revert to sort-key.

Ship-it

Sort-key HashMap / defaultdict in both languages. It's optimal-enough at LeetCode scale and the cleanest code review. Switch to frequency-vector only when the alphabet is small AND you're chasing constant-factor wins.

#5 LC 347 Medium Top K Frequent Elements

Return the k most frequent values. The PE answer is bucket sort, not a heap.

Problem Statement

Given an integer array nums and an integer k, return the k most frequent elements. The answer is guaranteed unique. You may return them in any order.

Signature: int[] topKFrequent(int[] nums, int k) / def top_k_frequent(nums: List[int], k: int) -> List[int]

Examples

Input: nums = [1,1,1,2,2,3], k = 2 Output: [1,2]
Input: nums = [1], k = 1 Output: [1]

Constraints

  • 1 <= nums.length <= 10^5
  • k is in [1, # unique elements]
  • It is guaranteed that the answer is unique.

Approach Overview

Brute Force

Java: Count + sort by frequency

Python: Counter.most_common(k)

O(n + u log u) where u = # unique O(u)

Optimal #1

Java: Min-heap of size k

Python: heapq.nlargest manual

O(n + u log k) O(u + k)

Optimal #2

Java: Bucket sort by frequency

Python: Bucket sort by frequency

O(n) O(n)

Java Solutions

Count + sort by frequency O(n + u log u) where u = # unique O(u)

Build frequency map, sort entries by count desc, take first k.

Pseudo-code

freq = HashMap<num, count>
entries = list(freq.entrySet())
sort entries by count desc
return first k keys

Complexity

Time: O(n + u log u) where u = # unique — Counting is O(n); sort is u log u.
Space: O(u) — One map plus a list of entries.

Edge cases & gotchas

  • u can equal n in the worst case (all distinct).
  • Sort gives total order — overkill when we just need top-k.

Java code

import java.util.*;

class Solution {
    public int[] topKFrequent(final int[] nums, final int k) {
        final Map<Integer,Integer> freq = new HashMap<>();
        for (final int x : nums) freq.merge(x, 1, Integer::sum);
        final List<Map.Entry<Integer,Integer>> entries = new ArrayList<>(freq.entrySet());
        entries.sort((a,b) -> b.getValue() - a.getValue());
        final int[] out = new int[k];
        for (int i = 0; i < k; i++) out[i] = entries.get(i).getKey();
        return out;
    }

    public static void main(String[] args) {
        System.out.println(Arrays.toString(new Solution().topKFrequent(new int[]{1,1,1,2,2,3}, 2)));
    }
}
Min-heap of size k O(n + u log k) O(u + k)

Maintain a min-heap of size k keyed by count. Each insertion is O(log k); total O(n + u log k).

Pseudo-code

freq = count map
heap = min-heap by count, capacity k
for each entry: heap.offer; if size > k: heap.poll
drain heap → result

Complexity

Time: O(n + u log k) — u log k beats u log u when k << u — typical case.
Space: O(u + k) — Map + heap.

Edge cases & gotchas

  • Min-heap kicks out the smallest count when size > k.
  • PriorityQueue in Java is a min-heap by default — perfect.
  • When k == u, heap reduces to identity.

Java code

import java.util.*;

class Solution {
    public int[] topKFrequent(final int[] nums, final int k) {
        final Map<Integer,Integer> freq = new HashMap<>();
        for (final int x : nums) freq.merge(x, 1, Integer::sum);
        // Min-heap so that the smallest count is at the top — that's who gets evicted.
        final PriorityQueue<int[]> heap = new PriorityQueue<>((a,b) -> a[1] - b[1]);
        for (final Map.Entry<Integer,Integer> e : freq.entrySet()) {
            heap.offer(new int[]{e.getKey(), e.getValue()});
            if (heap.size() > k) heap.poll();
        }
        final int[] out = new int[k];
        for (int i = k - 1; i >= 0; i--) out[i] = heap.poll()[0];
        return out;
    }

    public static void main(String[] args) {
        System.out.println(Arrays.toString(new Solution().topKFrequent(new int[]{1,1,1,2,2,3}, 2)));
    }
}
Bucket sort by frequency O(n) O(n)

Frequency is bounded by n. Buckets[count] = list of values. Walk buckets from high to low until k collected. True O(n).

Pseudo-code

freq = count map
buckets = array[n+1] of empty lists
for (val, cnt) in freq: buckets[cnt].append(val)
result = []
for i from n down to 1:
    result.extend(buckets[i])
    if len(result) >= k: return result[:k]

Complexity

Time: O(n) — Counting + bucket fill + descending sweep all linear.
Space: O(n) — n+1 buckets.

Edge cases & gotchas

  • Counts are bounded by n — bucket array is exactly n+1.
  • Bucket index 0 is unused (no element has frequency 0 unless absent).
  • When ties span the k-boundary, the problem guarantees uniqueness so no tiebreak logic needed.

Java code

import java.util.*;

class Solution {
    /**
     * Bucket sort by frequency: O(n) optimal. Frequencies are bounded by n,
     * so we sidestep the heap's log factor entirely.
     */
    public int[] topKFrequent(final int[] nums, final int k) {
        final int n = nums.length;
        final Map<Integer,Integer> freq = new HashMap<>();
        for (final int x : nums) freq.merge(x, 1, Integer::sum);

        final List<List<Integer>> buckets = new ArrayList<>(n + 1);
        for (int i = 0; i <= n; i++) buckets.add(new ArrayList<>());
        for (final Map.Entry<Integer,Integer> e : freq.entrySet()) {
            buckets.get(e.getValue()).add(e.getKey());
        }

        final int[] out = new int[k];
        int idx = 0;
        for (int i = n; i >= 1 && idx < k; i--) {
            for (final int v : buckets.get(i)) {
                out[idx++] = v;
                if (idx == k) break;
            }
        }
        return out;
    }

    public static void main(String[] args) {
        System.out.println(Arrays.toString(new Solution().topKFrequent(new int[]{1,1,1,2,2,3}, 2)));
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n + u log u)O(n + u log k)
SpaceO(u)O(u + k)
Difficulty2/53/5
k ≪ u caseWasteful — sorts the whole mapBest constant factor with PriorityQueue
k ≈ u caseSame as bucketHeap collapses to log u
Streaming friendly?No — needs full mapYes — heap maintains top-k online
When it winsQuick & dirty whiteboardStreaming top-k or memory-tight
PE VerdictShip bucket sort. The frequency upper bound (≤ n) makes the heap's log factor unnecessary. Use the heap when the counts are unbounded (e.g., streaming with a sliding decay) or when the input doesn't fit in memory — in those cases the streaming property of a fixed-size heap is the entire point.

Python Solutions

Counter.most_common(k) O(n + u log k) O(u)

One-liner. Counter.most_common is implemented with heapq.nlargest under the hood — O(n + u log k).

Pseudo-code

return [x for x, _ in Counter(nums).most_common(k)]

Complexity

Time: O(n + u log k) — Counter is O(n); most_common(k) uses heapq.nlargest = u log k.
Space: O(u) — Counter object.

Edge cases & gotchas

  • Most pythonic — and the constant factor is tiny because the work is in C.
  • If k is None, most_common returns ALL entries sorted — different cost.

Python code

from typing import List
from collections import Counter


class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        """Most-Pythonic shipping answer — Counter.most_common is heapq.nlargest in C."""
        return [val for val, _ in Counter(nums).most_common(k)]


if __name__ == "__main__":
    print(Solution().topKFrequent([1,1,1,2,2,3], 2))   # [1, 2]
heapq.nlargest manual O(n + u log k) O(u)

Same algorithm as most_common but explicit — useful when you want to swap the keying function.

Pseudo-code

counts = Counter(nums)
return heapq.nlargest(k, counts.keys(), key=counts.get)

Complexity

Time: O(n + u log k) — nlargest pushes onto a size-k heap.
Space: O(u) — Counter + heap.

Edge cases & gotchas

  • key=counts.get avoids materializing tuples.
  • Slightly slower than most_common because Python-level key callback.

Python code

from typing import List
from collections import Counter
import heapq


class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        """Manual heap selection — useful template when you need a custom key."""
        counts = Counter(nums)
        return heapq.nlargest(k, counts.keys(), key=counts.get)


if __name__ == "__main__":
    print(Solution().topKFrequent([1,1,1,2,2,3], 2))
Bucket sort by frequency O(n) O(n)

True O(n) with a bucket per possible count. Beats heap for k close to u.

Pseudo-code

counts = Counter(nums)
buckets = [[] for _ in range(len(nums)+1)]
for val, c in counts.items(): buckets[c].append(val)
result = []
for i in range(len(buckets)-1, 0, -1):
    result.extend(buckets[i])
    if len(result) >= k: return result[:k]

Complexity

Time: O(n) — Linear counting + linear walk.
Space: O(n) — n+1 buckets.

Edge cases & gotchas

  • Buckets index 0 unused.
  • extend + slice avoids per-append branching.

Python code

from typing import List
from collections import Counter


class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        """
        Bucket sort by frequency — O(n) optimal. Frequencies are bounded
        by len(nums), so we don't need a heap's log factor.
        """
        counts = Counter(nums)
        buckets: List[List[int]] = [[] for _ in range(len(nums) + 1)]
        for val, c in counts.items():
            buckets[c].append(val)
        result: List[int] = []
        for i in range(len(buckets) - 1, 0, -1):
            result.extend(buckets[i])
            if len(result) >= k:
                return result[:k]
        return result


if __name__ == "__main__":
    print(Solution().topKFrequent([1,1,1,2,2,3], 2))   # [1, 2]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n + u log k)O(n)
SpaceO(u)O(n)
Difficulty2/53/5
Constant factorSlightly worse (Python key callback)Best when u ≈ n
ReadabilityOKOK — explicit but more code
When it winsCustom keying / scoringProfiled hot path; counts bounded by n
PE VerdictIn Python, ship Counter.most_common(k) — it IS the heap solution, in C, with one line of code. Bucket sort is the asymptotically optimal answer to discuss with the interviewer; reach for it in production only when profiling demands it.

What the interviewer is really testing

Top-k is a classic 'when does bucket sort beat a heap' moment. PE signal: do you recognize that frequencies are bounded by n, making the log factor avoidable? Do you call out the streaming case where heap-of-k is the right answer because you can't afford to count everything first?

Top gotchas

  • Min-heap, NOT max-heap, when keeping size k — you evict the smallest each time.
  • Bucket-sort writes to bucket[freq], NOT bucket[freq-1]; off-by-one will silently lose the highest-count items.
  • Java PriorityQueue toArray ordering is heap-internal, not sorted — drain by polling.
  • Counter.most_common with k=None returns ALL entries sorted in O(u log u), not the same as most_common(k).

Ship-it

Java: bucket sort — O(n), no heap overhead. Python: Counter.most_common(k) — readable and the work is already in C.

#6 LC 271 Medium Encode and Decode Strings

Round-trip a list of arbitrary strings through a single string. Length-prefix vs delimiter-escape design.

Problem Statement

Design encode and decode so that any list of strings (containing any 256 ASCII or even Unicode characters) can be serialized into a single string and recovered exactly. The encode and decode must be inverses.

Signature: String encode(List<String> strs); List<String> decode(String s)

Examples

encode(["lint","code","love","you"]) → some single string decode(...) → ["lint","code","love","you"]
encode([""]) → must round-trip the empty string
encode(["#3#","","abc"]) → delimiter chars must survive

Constraints

  • 0 <= strs.length <= 200
  • 0 <= strs[i].length <= 200
  • strs[i] contains any valid ASCII (or Unicode) character — your code must not assume otherwise.

Approach Overview

Brute Force

Java: Reserved-delimiter join (BROKEN by adversarial input)

Python: Naive '#'.join (BROKEN)

O(N) where N = total chars O(N)

Optimal #1

Java: Length-prefix framing (chosen format: 'len#payload')

Python: Length-prefix framing

O(N) O(N)

Optimal #2

Java: Escape-character framing (delimiter + escape sequence)

Python: Escape-character framing

O(N) O(N)

Java Solutions

Reserved-delimiter join (BROKEN by adversarial input) O(N) where N = total chars O(N)

Naively join with a 'special' separator like '#'. Fails the moment the input contains that separator. Useful only as a teaching foil.

Pseudo-code

encode: return '#'.join(strs)
decode: return s.split('#')   # WRONG: '#3#' would split mid-payload

Complexity

Time: O(N) where N = total chars — Linear concat / split.
Space: O(N) — Output buffer.

Edge cases & gotchas

  • ANY printable ASCII char can appear in payload — there is no truly 'safe' single-char delimiter.
  • Demonstrates why this approach is unshippable — set up the optimal as a fix.

Java code

import java.util.*;

class Codec {
    /** BROKEN encode — used only as a discussion baseline. Do not ship. */
    public String encode(final List<String> strs) {
        return String.join("#", strs);   // collapses if any payload contains '#'
    }
    public List<String> decode(final String s) {
        return Arrays.asList(s.split("#", -1));   // round-trips ONLY for delimiter-free inputs
    }

    public static void main(String[] args) {
        Codec c = new Codec();
        List<String> in = Arrays.asList("a","#","b");
        System.out.println(c.decode(c.encode(in)));   // [a, , , b]  ← BROKEN
    }
}
Length-prefix framing (chosen format: 'len#payload') O(N) O(N)

Each string is encoded as `<length>#<payload>`. Length tells the decoder exactly how many chars to read — payload can contain '#' freely.

Pseudo-code

encode:
    sb = StringBuilder
    for s in strs: sb.append(s.length()).append('#').append(s)
    return sb.toString()
decode:
    i = 0; out = []
    while i < s.length():
        j = s.indexOf('#', i)
        len = parseInt(s[i:j])
        out.add(s.substring(j+1, j+1+len))
        i = j + 1 + len

Complexity

Time: O(N) — Single pass each direction.
Space: O(N) — Output of size N + Θ(k log L) for length headers.

Edge cases & gotchas

  • Empty list → empty string. encode([""]) → "0#" (single empty payload).
  • indexOf for '#' is safe because we only look up to the FIRST '#' after position i — the length prefix is digits only.
  • Negative or oversized length parses must be rejected if integrity matters.

Java code

import java.util.*;

class Codec {
    /**
     * Length-prefix framing: '<len>#<payload>' per string, concatenated.
     * Robust against any payload character because we never split — we
     * read exactly len chars after the '#'.
     */
    public String encode(final List<String> strs) {
        final StringBuilder sb = new StringBuilder();
        for (final String s : strs) sb.append(s.length()).append('#').append(s);
        return sb.toString();
    }

    public List<String> decode(final String s) {
        final List<String> out = new ArrayList<>();
        int i = 0;
        final int n = s.length();
        while (i < n) {
            final int hash = s.indexOf('#', i);
            final int len = Integer.parseInt(s, i, hash, 10);   // JDK 9+
            out.add(s.substring(hash + 1, hash + 1 + len));
            i = hash + 1 + len;
        }
        return out;
    }

    public static void main(String[] args) {
        Codec c = new Codec();
        List<String> in = Arrays.asList("lint","code","love","you","","#3#");
        System.out.println(c.decode(c.encode(in)));   // round-trips perfectly
    }
}
Escape-character framing (delimiter + escape sequence) O(N) O(N)

Use a delimiter and ESCAPE that delimiter inside payloads. Mirrors how CSV/JSON handle this. Slower than length-prefix but useful when the framing must be human-readable.

Pseudo-code

encode:  for each s: replace '\\' with '\\\\', then '|' with '\\|', then append + '|'
decode:  walk one char at a time, handling '\\|' and '\\\\' as escapes; '|' (unescaped) ends a payload

Complexity

Time: O(N) — Each char inspected once on encode and decode.
Space: O(N) — Up to 2N output if every char needs escaping (pathological).

Edge cases & gotchas

  • Two-character escape vocabulary: \\ and \| — anything else after a \\ is a malformed encoding.
  • Empty list and empty strings round-trip cleanly: encode([""]) = "|" (a single delimiter).
  • Pathological adversarial input ('|||' style) doubles in size due to escaping.

Java code

import java.util.*;

class Codec {
    private static final char DELIM = '|';
    private static final char ESC = '\\';

    public String encode(final List<String> strs) {
        final StringBuilder sb = new StringBuilder();
        for (final String s : strs) {
            for (int i = 0; i < s.length(); i++) {
                final char c = s.charAt(i);
                if (c == DELIM || c == ESC) sb.append(ESC);
                sb.append(c);
            }
            sb.append(DELIM);
        }
        return sb.toString();
    }

    public List<String> decode(final String s) {
        final List<String> out = new ArrayList<>();
        final StringBuilder cur = new StringBuilder();
        for (int i = 0; i < s.length(); i++) {
            final char c = s.charAt(i);
            if (c == ESC && i + 1 < s.length()) {
                cur.append(s.charAt(++i));
            } else if (c == DELIM) {
                out.add(cur.toString());
                cur.setLength(0);
            } else {
                cur.append(c);
            }
        }
        return out;
    }

    public static void main(String[] args) {
        Codec c = new Codec();
        System.out.println(c.decode(c.encode(Arrays.asList("a","|","\\","#3#",""))));
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
Time (encode)O(N)O(N) but ~2× constant
Time (decode)O(N)O(N)
Output size overhead0 (broken)Up to 2N pathological, ~N typical
Robust to adversarial inputNo — payload may contain delimiterYes — explicit escapes
Streaming friendlyN/A — brokenYes — char-at-a-time decode
Human-readableYes (broken though)Yes
When to useNeverStreaming decode, audit logs, human inspection
PE VerdictShip length-prefix framing. It's the fastest robust option, decodes by jumping (no per-char branching), and matches how protobuf and HTTP/2 frame messages. Reach for escape-framing when the wire format must be human-readable or when streaming decode without backtracking matters.

Python Solutions

Naive '#'.join (BROKEN) O(N) O(N)

Same broken approach as Java baseline — for discussion only.

Pseudo-code

encode: '#'.join(strs)
decode: s.split('#')   # WRONG

Complexity

Time: O(N) — Linear.
Space: O(N) — Linear.

Edge cases & gotchas

  • Cannot survive a payload containing '#'.

Python code

from typing import List


class Codec:
    def encode(self, strs: List[str]) -> str:
        return "#".join(strs)
    def decode(self, s: str) -> List[str]:
        return s.split("#")


if __name__ == "__main__":
    c = Codec()
    print(c.decode(c.encode(["a","#","b"])))   # ['a', '', '', 'b'] — BROKEN
Length-prefix framing O(N) O(N)

Same algorithm as Java optimal #1 — '<len>#<payload>' concatenation.

Pseudo-code

encode: ''.join(f'{len(s)}#{s}' for s in strs)
decode: walk index, find '#', slice [hash+1 : hash+1+len], advance

Complexity

Time: O(N) — One pass each way.
Space: O(N) — Output buffer.

Edge cases & gotchas

  • Use s.find('#', i) — O(1) call into C.
  • Slicing in Python is O(len) and copies — fine here, but watch on truly huge strings.

Python code

from typing import List


class Codec:
    """Length-prefix framing: '<len>#<payload>' for each string."""

    def encode(self, strs: List[str]) -> str:
        # f-strings + str.join is C-optimized in CPython.
        return "".join(f"{len(s)}#{s}" for s in strs)

    def decode(self, s: str) -> List[str]:
        out: List[str] = []
        i, n = 0, len(s)
        while i < n:
            j = s.find("#", i)
            length = int(s[i:j])
            out.append(s[j + 1 : j + 1 + length])
            i = j + 1 + length
        return out


if __name__ == "__main__":
    c = Codec()
    in_ = ["lint","code","love","you","","#3#"]
    print(c.decode(c.encode(in_)) == in_)   # True
Escape-character framing O(N) O(N)

Same algorithm as Java optimal #2 — delimiter + escape.

Pseudo-code

encode: for s in strs: append (s.replace('\\','\\\\').replace('|','\\|') + '|')
decode: walk char-by-char, treat '\\X' as literal X, '|' as terminator

Complexity

Time: O(N) — Char-at-a-time.
Space: O(N) — Up to 2N pathological.

Edge cases & gotchas

  • str.replace must escape backslash FIRST, then delimiter — order matters.
  • Pure-Python char-walk is slow vs C-level operations; for large inputs the regex approach can outperform.

Python code

from typing import List


class Codec:
    DELIM = "|"
    ESC = "\\"

    def encode(self, strs: List[str]) -> str:
        parts = []
        for s in strs:
            # Order matters: escape backslashes BEFORE delimiter, otherwise the
            # newly-introduced backslashes would themselves get escaped.
            t = s.replace(self.ESC, self.ESC + self.ESC).replace(self.DELIM, self.ESC + self.DELIM)
            parts.append(t + self.DELIM)
        return "".join(parts)

    def decode(self, s: str) -> List[str]:
        out: List[str] = []
        cur = []
        i, n = 0, len(s)
        while i < n:
            c = s[i]
            if c == self.ESC and i + 1 < n:
                cur.append(s[i + 1])
                i += 2
            elif c == self.DELIM:
                out.append("".join(cur))
                cur.clear()
                i += 1
            else:
                cur.append(c)
                i += 1
        return out


if __name__ == "__main__":
    c = Codec()
    in_ = ["a", "|", "\\", "#3#", ""]
    print(c.decode(c.encode(in_)) == in_)   # True

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(N)O(N) — pure-Python char loop hurts
SpaceO(N)Up to 2N
RobustNoYes
ReadabilityBest (but broken)Decent
Constant factorBest — single C callWorst — Python char loop
When to useNeverWhen the wire format must escape rather than length-prefix (e.g., debugging readability)
PE VerdictLength-prefix is the right ship-it answer in Python too. The slicing operations stay in C and decode is essentially a series of int-parse + slice. Escape-framing's char-at-a-time decode in pure Python is materially slower for any non-trivial input.

What the interviewer is really testing

This is a wire-protocol design question disguised as a string problem. PE signal: you immediately recognize that 'is there a safe delimiter' is the wrong question, propose length-prefix as the canonical answer, and compare against escape-framing with concrete trade-offs. Bonus signal: you mention this is exactly how HTTP/2, gRPC, protobuf, and ZooKeeper jute frame their messages.

Top gotchas

  • Do NOT use a single-character delimiter without escapes — it's an interview honeypot, not a solution.
  • When escaping, escape the escape character FIRST, then the delimiter. Reverse order corrupts.
  • encode([""]) is the canonical edge case — a single empty string. Length-prefix yields '0#' (correct); naive join yields '' (collides with encode([])).
  • Java: use Integer.parseInt(s, start, end, radix) (JDK 9+) to avoid creating a substring just to parse.

Ship-it

Length-prefix framing in both languages — '<len>#<payload>' concatenated. Decoder reads length, jumps exactly that many chars. Adversarial-input safe by construction.

#7 LC 238 Medium Product of Array Except Self

Compute prefix * suffix products without division — the canonical 'two passes' problem.

Problem Statement

Given an integer array nums, return an array answer such that answer[i] equals the product of all elements of nums except nums[i]. The algorithm must run in O(n) and (per the follow-up) without using division.

Signature: int[] productExceptSelf(int[] nums) / def product_except_self(nums: List[int]) -> List[int]

Examples

Input: nums = [1,2,3,4] Output: [24,12,8,6]
Input: nums = [-1,1,0,-3,3] Output: [0,0,9,0,0]

Constraints

  • 2 <= nums.length <= 10^5
  • -30 <= nums[i] <= 30
  • The product of any prefix or suffix fits a 32-bit integer.
  • Must run in O(n); cannot use the division operator.

Approach Overview

Brute Force

Java: Total product / nums[i] (BANNED by problem)

Python: Division (BANNED)

O(n) O(1)

Optimal #1

Java: Two passes with prefix and suffix arrays

Python: Prefix and suffix arrays

O(n) O(n)

Optimal #2

Java: O(1) extra space with output array reused

Python: O(1) extra space

O(n) O(1) extra (output not counted)

Java Solutions

Total product / nums[i] (BANNED by problem) O(n) O(1)

Compute product of all, divide each out. Disallowed by spec AND breaks on zeros.

Pseudo-code

total = product of all
for i: out[i] = total / nums[i]   # ILLEGAL and zero-divides

Complexity

Time: O(n) — Two linear passes.
Space: O(1) — Output only.

Edge cases & gotchas

  • Single zero → all answers wrong unless special-cased.
  • Multiple zeros → entire output is 0, except both zero positions are also 0 (special case).
  • Division forbidden by problem statement.

Java code

class Solution {
    /** SHOWS WHY DIVISION IS WRONG: zeros break it; problem also forbids /. */
    public int[] productExceptSelfDivisionVariant(final int[] nums) {
        int total = 1, zeros = 0, zeroIdx = -1;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == 0) { zeros++; zeroIdx = i; }
            else total *= nums[i];
        }
        final int[] out = new int[nums.length];
        if (zeros >= 2) return out;             // all zero
        if (zeros == 1) { out[zeroIdx] = total; return out; }
        for (int i = 0; i < nums.length; i++) out[i] = total / nums[i];
        return out;
    }

    public static void main(String[] args) {
        // For instructional comparison only — do not ship.
    }
}
Two passes with prefix and suffix arrays O(n) O(n)

Build left[i] = product of nums[0..i-1], right[i] = product of nums[i+1..n-1]. Answer = left * right. Two clean passes.

Pseudo-code

left[0] = 1
for i in 1..n-1: left[i] = left[i-1] * nums[i-1]
right[n-1] = 1
for i in n-2..0: right[i] = right[i+1] * nums[i+1]
for i: out[i] = left[i] * right[i]

Complexity

Time: O(n) — Three linear passes.
Space: O(n) — Two auxiliary arrays of size n.

Edge cases & gotchas

  • Boundary indices: left[0] = 1, right[n-1] = 1 — represent the 'empty product' identity.
  • Long is unnecessary per the problem's guarantee that prefix/suffix products fit int. Don't over-engineer.

Java code

class Solution {
    /** Prefix and suffix product arrays — clearest possible reading. */
    public int[] productExceptSelf(final int[] nums) {
        final int n = nums.length;
        final int[] left = new int[n];
        final int[] right = new int[n];
        left[0] = 1;
        for (int i = 1; i < n; i++) left[i] = left[i-1] * nums[i-1];
        right[n-1] = 1;
        for (int i = n-2; i >= 0; i--) right[i] = right[i+1] * nums[i+1];
        final int[] out = new int[n];
        for (int i = 0; i < n; i++) out[i] = left[i] * right[i];
        return out;
    }

    public static void main(String[] args) {
        System.out.println(java.util.Arrays.toString(new Solution().productExceptSelf(new int[]{1,2,3,4})));
    }
}
O(1) extra space with output array reused O(n) O(1) extra (output not counted)

Stream the prefix into the output array, then a backward sweep multiplies in the running suffix. The shipping answer.

Pseudo-code

out[0] = 1
for i in 1..n-1: out[i] = out[i-1] * nums[i-1]   # out holds left products
right = 1
for i in n-1..0:
    out[i] *= right
    right *= nums[i]

Complexity

Time: O(n) — Two linear passes.
Space: O(1) extra (output not counted) — One scalar (right) outside output.

Edge cases & gotchas

  • out[0] starts at 1 — empty-product identity.
  • right is updated AFTER multiplying into out[i] so it represents 'product strictly to the right of i'.

Java code

class Solution {
    /**
     * O(1) extra space (output excluded). Two passes:
     *   pass 1: out[i] = product of nums[0..i-1]   (prefix)
     *   pass 2: walk right-to-left; multiply in running 'right' (suffix)
     */
    public int[] productExceptSelf(final int[] nums) {
        final int n = nums.length;
        final int[] out = new int[n];
        out[0] = 1;
        for (int i = 1; i < n; i++) out[i] = out[i-1] * nums[i-1];
        int right = 1;
        for (int i = n - 1; i >= 0; i--) {
            out[i] *= right;
            right *= nums[i];
        }
        return out;
    }

    public static void main(String[] args) {
        System.out.println(java.util.Arrays.toString(new Solution().productExceptSelf(new int[]{1,2,3,4})));
        System.out.println(java.util.Arrays.toString(new Solution().productExceptSelf(new int[]{-1,1,0,-3,3})));
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
Space (auxiliary)O(1) but ILLEGALO(n)
Difficulty1/5 — but disqualified2/5
Handles zerosRequires explicit guardsYes — works uniformly
Cache behaviorBest (but moot)OK — three sequential passes
ReadabilityWorst — special cases everywhereBest — separate roles for left/right
When to useNever (banned)Whiteboard / explanation
PE VerdictShip the O(1)-extra-space version. It's the same number of operations as the prefix/suffix-array variant minus two array allocations, and the trick (output holds prefix; backward sweep multiplies in suffix) is the entire learning of this problem. The two-array form is fine for first explanation; never for final code.

Python Solutions

Division (BANNED) O(n) O(1)

Same shape — for instructional comparison only.

Pseudo-code

total = math.prod(nums); out[i] = total // nums[i]   # forbidden

Complexity

Time: O(n) — Two passes.
Space: O(1) — Output.

Edge cases & gotchas

  • Zero handling needs special cases.
  • Division banned by problem statement.

Python code

from typing import List
import math


class Solution:
    """Division-based reference; do not submit."""
    def productExceptSelfDivVariant(self, nums: List[int]) -> List[int]:
        zeros = nums.count(0)
        if zeros >= 2:
            return [0] * len(nums)
        non_zero_prod = math.prod(x for x in nums if x != 0)
        if zeros == 1:
            return [non_zero_prod if x == 0 else 0 for x in nums]
        return [non_zero_prod // x for x in nums]
Prefix and suffix arrays O(n) O(n)

Same algorithm as Java optimal #1.

Pseudo-code

left[0] = 1; right[n-1] = 1
for i in 1..n-1: left[i] = left[i-1] * nums[i-1]
for i in n-2..0: right[i] = right[i+1] * nums[i+1]
out[i] = left[i] * right[i]

Complexity

Time: O(n) — Three passes.
Space: O(n) — Two aux arrays.

Edge cases & gotchas

  • Empty product = 1 at boundaries.

Python code

from typing import List


class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        n = len(nums)
        left = [1] * n
        right = [1] * n
        for i in range(1, n):
            left[i] = left[i-1] * nums[i-1]
        for i in range(n-2, -1, -1):
            right[i] = right[i+1] * nums[i+1]
        return [left[i] * right[i] for i in range(n)]


if __name__ == "__main__":
    print(Solution().productExceptSelf([1,2,3,4]))     # [24,12,8,6]
    print(Solution().productExceptSelf([-1,1,0,-3,3])) # [0,0,9,0,0]
O(1) extra space O(n) O(1) extra

Reuse the output array for prefix; backward sweep multiplies in running suffix.

Pseudo-code

out = [1]*n
for i in 1..n-1: out[i] = out[i-1] * nums[i-1]
right = 1
for i in n-1..0:
    out[i] *= right
    right *= nums[i]

Complexity

Time: O(n) — Two passes.
Space: O(1) extra — One scalar.

Edge cases & gotchas

  • Multiplications in Python use arbitrary precision — overflow is non-issue.

Python code

from typing import List


class Solution:
    def productExceptSelf(self, nums: List[int]) -> List[int]:
        """
        Two passes, O(1) extra space (output not counted, per the problem
        statement's allowance).
        """
        n = len(nums)
        out = [1] * n
        # Pass 1: prefix products into out
        for i in range(1, n):
            out[i] = out[i-1] * nums[i-1]
        # Pass 2: multiply in suffix products from the right
        right = 1
        for i in range(n - 1, -1, -1):
            out[i] *= right
            right *= nums[i]
        return out


if __name__ == "__main__":
    print(Solution().productExceptSelf([1,2,3,4]))     # [24,12,8,6]
    print(Solution().productExceptSelf([-1,1,0,-3,3])) # [0,0,9,0,0]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(1) but ILLEGALO(n)
Difficulty1/5 (disqualified)2/5
Constant factorThree list traversals
ReadabilityTricky due to zero casesBest — explicit roles
When to useNeverTeaching
PE VerdictSame as Java: ship the O(1)-extra-space variant. The pattern (output holds running prefix; backward pass multiplies in running suffix) is one of the most-asked array tricks in interviews.

What the interviewer is really testing

This problem teaches that 'product of array' decomposes into prefix * suffix, and that you can fold both passes into the output array to hit O(1) extra space. PE signal: you derive the trick from first principles, you handle zeros without explicit branching (the algorithm just works), and you call out the 'output array doesn't count toward extra space' nuance the problem statement implies.

Top gotchas

  • Division feels obvious but is forbidden AND fails on zero — interview honeypot.
  • Boundary identity: left[0] = right[n-1] = 1 (empty product). Forgetting this makes both endpoints zero.
  • In the O(1) variant, update `right` AFTER multiplying it into out[i], not before — otherwise you include nums[i] in its own answer.
  • Java: outputs fit in int per problem constraints; don't reach for long — over-engineering signals you didn't read constraints.

Ship-it

O(1)-extra-space two-pass: prefix into output, backward sweep with a running scalar suffix.

#8 LC 36 Medium Valid Sudoku

Validate three independent constraints (row, col, 3×3 box) in a single pass — encoding-key design.

Problem Statement

Given a 9×9 Sudoku board partially filled with digits '1'–'9' and dots '.' for empty cells, determine whether the current configuration is valid. Validity means: each row, each column, and each of the nine 3×3 sub-boxes contains the digits 1–9 without repetition. The board need not be solvable; only the partial state is checked.

Signature: boolean isValidSudoku(char[][] board) / def is_valid_sudoku(board: List[List[str]]) -> bool

Examples

Input: a partially-filled 9x9 grid Output: true if no row/col/box has duplicate digits
If '5' appears twice in the top-left 3x3 box → false

Constraints

  • Board is exactly 9 × 9.
  • Each cell is a digit '1'–'9' or '.' for empty.

Approach Overview

Brute Force

Java: Three independent passes (rows, cols, boxes)

Python: Three independent passes

O(81) = O(1) O(1)

Optimal #1

Java: Single pass with three sets-of-sets (HashSet of String keys)

Python: Single pass with set of (kind, index, digit) tuples

O(81) O(81)

Optimal #2

Java: Single pass with bitmask per row / col / box

Python: Bitmask arrays

O(81) O(1) — 27 ints

Java Solutions

Three independent passes (rows, cols, boxes) O(81) = O(1) O(1)

Validate each constraint group separately. Three full board scans. Correct but verbose.

Pseudo-code

for each row r: ensure no duplicates among row r digits
for each col c: ensure no duplicates among col c digits
for each 3x3 box: ensure no duplicates among the 9 digits

Complexity

Time: O(81) = O(1) — Constant — board is fixed-size.
Space: O(1) — Three sets of size ≤ 9.

Edge cases & gotchas

  • Duplicates across different constraint types (e.g., row OK but column dup) — must check all three.
  • Empty cells '.' must be skipped, not counted as a digit.

Java code

class Solution {
    public boolean isValidSudoku(final char[][] board) {
        for (int r = 0; r < 9; r++) {
            final boolean[] seen = new boolean[10];
            for (int c = 0; c < 9; c++) {
                final char ch = board[r][c];
                if (ch == '.') continue;
                final int d = ch - '0';
                if (seen[d]) return false;
                seen[d] = true;
            }
        }
        for (int c = 0; c < 9; c++) {
            final boolean[] seen = new boolean[10];
            for (int r = 0; r < 9; r++) {
                final char ch = board[r][c];
                if (ch == '.') continue;
                final int d = ch - '0';
                if (seen[d]) return false;
                seen[d] = true;
            }
        }
        for (int br = 0; br < 9; br += 3) for (int bc = 0; bc < 9; bc += 3) {
            final boolean[] seen = new boolean[10];
            for (int r = br; r < br + 3; r++) for (int c = bc; c < bc + 3; c++) {
                final char ch = board[r][c];
                if (ch == '.') continue;
                final int d = ch - '0';
                if (seen[d]) return false;
                seen[d] = true;
            }
        }
        return true;
    }
}
Single pass with three sets-of-sets (HashSet of String keys) O(81) O(81)

Walk every cell once. For each digit, attempt to add three string keys: row#d, col#d, box#d. Any collision = invalid.

Pseudo-code

seen = HashSet<String>
for r,c in 0..8:
    d = board[r][c]
    if d == '.': continue
    if not seen.add('R'+r+d): return false
    if not seen.add('C'+c+d): return false
    if not seen.add('B'+(r/3)+(c/3)+d): return false
return true

Complexity

Time: O(81) — One pass.
Space: O(81) — Up to 3·81 strings.

Edge cases & gotchas

  • String keys are clear but allocate 3 strings per non-empty cell — wasteful for hot path.
  • set.add returns false on duplicate — single-call dup check.

Java code

import java.util.*;

class Solution {
    public boolean isValidSudoku(final char[][] board) {
        final Set<String> seen = new HashSet<>(243);   // 3 keys × 81 cells, sized
        for (int r = 0; r < 9; r++) {
            for (int c = 0; c < 9; c++) {
                final char ch = board[r][c];
                if (ch == '.') continue;
                final int b = (r / 3) * 3 + (c / 3);
                if (!seen.add("R" + r + ch) ||
                    !seen.add("C" + c + ch) ||
                    !seen.add("B" + b + ch)) return false;
            }
        }
        return true;
    }
}
Single pass with bitmask per row / col / box O(81) O(1) — 27 ints

Three int[9] arrays of bitmasks. Bit d in row[r] means digit d already seen in row r. Flip-and-test in one operation.

Pseudo-code

rows[9], cols[9], boxes[9] = int[]
for r,c in 0..8:
    d = board[r][c] - '0'  # bit index
    if d == '.': continue
    mask = 1 << d
    b = (r/3)*3 + (c/3)
    if rows[r] & mask or cols[c] & mask or boxes[b] & mask: return false
    rows[r] |= mask; cols[c] |= mask; boxes[b] |= mask
return true

Complexity

Time: O(81) — One pass, all O(1) bit ops.
Space: O(1) — 27 ints — Constant 27 ints.

Edge cases & gotchas

  • Bit 0 unused (digits are 1–9), so bit positions 1–9 represent each digit.
  • Box index formula (r/3)*3 + c/3 is the canonical 0..8 mapping.
  • All operations branchless and cache-resident — fastest possible at 9×9 scale.

Java code

class Solution {
    /**
     * Bitmask Sudoku validator. 27 ints, no allocations during the pass.
     * Each bit position 1..9 represents whether that digit has appeared.
     */
    public boolean isValidSudoku(final char[][] board) {
        final int[] rows = new int[9];
        final int[] cols = new int[9];
        final int[] boxes = new int[9];
        for (int r = 0; r < 9; r++) {
            for (int c = 0; c < 9; c++) {
                final char ch = board[r][c];
                if (ch == '.') continue;
                final int mask = 1 << (ch - '0');
                final int b = (r / 3) * 3 + (c / 3);
                if ((rows[r] & mask) != 0 || (cols[c] & mask) != 0 || (boxes[b] & mask) != 0) return false;
                rows[r] |= mask; cols[c] |= mask; boxes[b] |= mask;
            }
        }
        return true;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(1) (3·81)O(1) (1·81)
SpaceO(1)O(243) strings
Difficulty2/52/5
Constant factor~3× the workHeavy: 3 strings/cell
AllocationsMinorMany (string concat)
GeneralizesHard — three loopsYes — string keys are flexible
When to useFirst-pass sanity checkQuick whiteboard ship
PE VerdictShip the bitmask version. Same code length as the HashSet variant, zero allocations, ~10× faster in micro-benchmarks. The bitmask-per-constraint pattern recurs in NQueens and other constraint-satisfaction problems — it's worth committing to muscle memory.

Python Solutions

Three independent passes O(1) O(1)

Same as Java baseline — clear but verbose.

Pseudo-code

Validate rows, then columns, then boxes — return False on any duplicate.

Complexity

Time: O(1) — 81 cells × 3 passes.
Space: O(1) — Sets of ≤ 9.

Edge cases & gotchas

  • Skip '.' cells.
  • Box index from (r//3, c//3).

Python code

from typing import List


class Solution:
    def isValidSudoku(self, board: List[List[str]]) -> bool:
        for r in range(9):
            seen = set()
            for c in range(9):
                ch = board[r][c]
                if ch == '.': continue
                if ch in seen: return False
                seen.add(ch)
        for c in range(9):
            seen = set()
            for r in range(9):
                ch = board[r][c]
                if ch == '.': continue
                if ch in seen: return False
                seen.add(ch)
        for br in range(0, 9, 3):
            for bc in range(0, 9, 3):
                seen = set()
                for r in range(br, br+3):
                    for c in range(bc, bc+3):
                        ch = board[r][c]
                        if ch == '.': continue
                        if ch in seen: return False
                        seen.add(ch)
        return True
Single pass with set of (kind, index, digit) tuples O(1) O(1)

Idiomatic Python: one set, three tuple keys per cell. Reads beautifully.

Pseudo-code

seen = set()
for r,c,d in non-empty cells:
    keys = ((r,d,'R'), (d,c,'C'), (r//3,c//3,d,'B'))
    for k in keys:
        if k in seen: return False
        seen.add(k)
return True

Complexity

Time: O(1) — 81 cells, 3 inserts each.
Space: O(1) — ≤ 243 tuples.

Edge cases & gotchas

  • Tuple hashing is fast in CPython — beats string concat.
  • Use tag chars ('R', 'C', 'B') in tuple to avoid namespace collisions across constraints.

Python code

from typing import List


class Solution:
    def isValidSudoku(self, board: List[List[str]]) -> bool:
        """One pass, set of tuples — Pythonic and clear."""
        seen = set()
        for r in range(9):
            for c in range(9):
                d = board[r][c]
                if d == '.':
                    continue
                row_key = ('R', r, d)
                col_key = ('C', c, d)
                box_key = ('B', r // 3, c // 3, d)
                if row_key in seen or col_key in seen or box_key in seen:
                    return False
                seen.add(row_key); seen.add(col_key); seen.add(box_key)
        return True
Bitmask arrays O(1) O(1)

Same bitmask trick as Java — three int lists of length 9. Tightest Python possible.

Pseudo-code

rows, cols, boxes = [0]*9, [0]*9, [0]*9
for r,c,d in non-empty cells:
    bit = 1 << int(d)
    b = (r//3)*3 + c//3
    if rows[r] & bit or cols[c] & bit or boxes[b] & bit: return False
    rows[r] |= bit; cols[c] |= bit; boxes[b] |= bit
return True

Complexity

Time: O(1) — 81 cells, all bit ops.
Space: O(1) — 27 ints.

Edge cases & gotchas

  • int(d) parses the digit string '1'..'9' to int. ord(d) - ord('0') is faster but less Pythonic.
  • Pure-Python bit ops are slower than tuple-set in CPython — measure before believing the asymptote.

Python code

from typing import List


class Solution:
    def isValidSudoku(self, board: List[List[str]]) -> bool:
        """Bitmask validator — same algorithm as the Java optimal."""
        rows = [0] * 9
        cols = [0] * 9
        boxes = [0] * 9
        for r in range(9):
            for c in range(9):
                ch = board[r][c]
                if ch == '.':
                    continue
                bit = 1 << (ord(ch) - 48)   # 48 == ord('0')
                b = (r // 3) * 3 + c // 3
                if rows[r] & bit or cols[c] & bit or boxes[b] & bit:
                    return False
                rows[r] |= bit; cols[c] |= bit; boxes[b] |= bit
        return True

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(1)O(1)
SpaceO(1)O(1)
Difficulty2/53/5
Constant factor (CPython)~3× more iterationsSlightly worse — Python int bit-ops
ReadabilityVerboseBest for Java/C readers; less so for Python
Generalizes to NxNEasyEasy — bigint masks for N > 64
When to useBeginnerPerformance-critical or aligning with the C++ / Java idiom
PE VerdictIn Python, ship the tuple-set version — it's the same asymptote as the bitmask, the constant factor is competitive, and it reads like English. The bitmask form is the right answer when teaching the cross-language pattern or when N grows to a non-9 generalization.

What the interviewer is really testing

This problem teaches encoding multiple constraints into shared keys. PE signal: you fuse the three passes into one with a single seen-set, AND you propose the bitmask form as a constant-factor optimization once you know the alphabet is bounded. Bonus signal: you recognize this same encoding-key pattern from email deduplication, log enrichment, and constraint solvers.

Top gotchas

  • Box index formula: (r // 3) * 3 + (c // 3). Off-by-one or transposed and you misclassify boxes.
  • Skipping '.' is mandatory — counting it as a 'digit' produces phantom collisions.
  • If you stringify keys naively ('R'+r+d), '11' might collide with '1'+'1' in another encoding. Use delimiters or tuples.
  • Bitmask uses bits 1–9; leaving bit 0 unused is intentional and matches the digit values directly.

Ship-it

Java: bitmask validator. Python: tuple-set or bitmask — both are correct PE answers.

#9 LC 128 Medium Longest Consecutive Sequence

Find the longest run of consecutive integers — without sorting, in O(n).

Problem Statement

Given an unsorted array of integers nums, return the length of the longest sequence of consecutive integers (any order in the array). The algorithm must run in O(n).

Signature: int longestConsecutive(int[] nums) / def longest_consecutive(nums: List[int]) -> int

Examples

Input: nums = [100,4,200,1,3,2] Output: 4 ([1,2,3,4])
Input: nums = [0,3,7,2,5,8,4,6,0,1] Output: 9
Input: nums = [] Output: 0

Constraints

  • 0 <= nums.length <= 10^5
  • -10^9 <= nums[i] <= 10^9

Approach Overview

Brute Force

Java: Sort + linear scan

Python: Sort + scan

O(n log n) O(1) — sort is in-place on int[]

Optimal #1

Java: HashSet + only-extend-from-sequence-starts

Python: set + start-of-run walk

O(n) O(n)

Optimal #2

Java: Union-Find with hash-mapped roots

Python: Union-Find on a dict

O(n · α(n)) ≈ O(n) O(n)

Java Solutions

Sort + linear scan O(n log n) O(1) — sort is in-place on int[]

Sort, then walk and count consecutive runs. Violates the O(n) requirement but is the natural first answer.

Pseudo-code

sort(nums)
best = 1; run = 1
for i in 1..n-1:
    if nums[i] == nums[i-1]: continue   # skip dups
    if nums[i] == nums[i-1] + 1: run += 1
    else: run = 1
    best = max(best, run)
return best

Complexity

Time: O(n log n) — Sort dominates.
Space: O(1) — sort is in-place on int[] — In-place sort on primitive array.

Edge cases & gotchas

  • Empty array → return 0 (skip the scan).
  • Duplicates: skip them, don't reset the run.
  • Mutates the input — confirm the caller is OK with it.

Java code

import java.util.*;

class Solution {
    public int longestConsecutive(final int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        Arrays.sort(nums);
        int best = 1, run = 1;
        for (int i = 1; i < nums.length; i++) {
            if (nums[i] == nums[i-1]) continue;
            run = (nums[i] == nums[i-1] + 1) ? run + 1 : 1;
            best = Math.max(best, run);
        }
        return best;
    }
}
HashSet + only-extend-from-sequence-starts O(n) O(n)

Insert all into a set. For each x where x-1 is NOT present (start of a run), walk forward counting. Each integer visited at most twice.

Pseudo-code

S = HashSet(nums)
best = 0
for x in S:
    if x-1 not in S:        # x is run start
        cur = x
        while cur+1 in S: cur += 1
        best = max(best, cur - x + 1)
return best

Complexity

Time: O(n) — Each element is the head of at most one run; total inner-loop work is bounded by n.
Space: O(n) — HashSet of n distinct ints.

Edge cases & gotchas

  • Iterate over the SET, not the array — duplicates would make the array iteration redundant.
  • The 'is x-1 in set' check is the trick that makes this O(n): only run-starters initiate the inner walk.
  • Empty input → return 0.

Java code

import java.util.*;

class Solution {
    /**
     * Hash-set with start-of-run detection. The key insight: only initiate
     * the forward walk when x-1 is NOT in the set, ensuring each integer
     * is touched at most twice across the whole algorithm.
     */
    public int longestConsecutive(final int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        final Set<Integer> S = new HashSet<>((int) (nums.length / 0.75f) + 1);
        for (final int x : nums) S.add(x);
        int best = 0;
        for (final int x : S) {
            if (S.contains(x - 1)) continue;        // not a run start
            int cur = x;
            while (S.contains(cur + 1)) cur++;
            best = Math.max(best, cur - x + 1);
        }
        return best;
    }
}
Union-Find with hash-mapped roots O(n · α(n)) ≈ O(n) O(n)

Union x with x-1 and x+1 if present. Track size of components. The largest component size is the answer.

Pseudo-code

parent = HashMap<int,int>; size = HashMap<int,int>
for x in nums: parent[x] = x; size[x] = 1
for x in nums:
    for n in (x-1, x+1):
        if n in parent: union(x, n)
return max(size.values())

Complexity

Time: O(n · α(n)) ≈ O(n) — Each union/find is near-constant amortized.
Space: O(n) — Parent and size maps.

Edge cases & gotchas

  • Path compression + union-by-size keep amortized cost ~ O(1).
  • Union must update size on the new root, not the old — easy bug.
  • Slower constant factor than the set approach; useful when offline updates arrive incrementally.

Java code

import java.util.*;

class Solution {
    private final Map<Integer,Integer> parent = new HashMap<>();
    private final Map<Integer,Integer> size = new HashMap<>();

    private int find(int x) {
        while (parent.get(x) != x) { parent.put(x, parent.get(parent.get(x))); x = parent.get(x); }
        return x;
    }
    private void union(int a, int b) {
        final int ra = find(a), rb = find(b);
        if (ra == rb) return;
        final int sa = size.get(ra), sb = size.get(rb);
        if (sa < sb) { parent.put(ra, rb); size.put(rb, sa + sb); }
        else         { parent.put(rb, ra); size.put(ra, sa + sb); }
    }

    public int longestConsecutive(final int[] nums) {
        if (nums == null || nums.length == 0) return 0;
        for (final int x : nums) { parent.putIfAbsent(x, x); size.putIfAbsent(x, 1); }
        for (final int x : nums) {
            if (parent.containsKey(x - 1)) union(x, x - 1);
            if (parent.containsKey(x + 1)) union(x, x + 1);
        }
        int best = 0;
        for (final int x : parent.keySet()) if (find(x) == x) best = Math.max(best, size.get(x));
        return best;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n log n)O(n α(n)) ≈ O(n)
SpaceO(1)O(n)
Difficulty2/54/5 — union-find
Spec complianceViolates O(n) requirementMeets it
Constant factorlog n hides a 3-5× factorWorse — HashMap ops + path compression overhead
Streaming / onlineBad — need full sortGood — supports incremental adds
When to useQuick whiteboard fallbackOnline / incremental scenarios
PE VerdictShip the HashSet variant. Linear time, simple implementation, the only one that meets the spec. Union-find is the right answer when the input arrives as a stream and you want incremental queries; for the static-array variant it's overkill.

Python Solutions

Sort + scan O(n log n) O(1) sort in-place on a list.copy

Same as Java baseline.

Pseudo-code

sort(nums); walk, skip dups, count runs.

Complexity

Time: O(n log n) — Timsort.
Space: O(1) sort in-place on a list.copy — Sort list (often O(n) for sorted()) — call .sort() to mutate in-place.

Edge cases & gotchas

  • Empty array.
  • Duplicates.

Python code

from typing import List


class Solution:
    def longestConsecutive(self, nums: List[int]) -> int:
        if not nums:
            return 0
        nums = sorted(nums)
        best = run = 1
        for i in range(1, len(nums)):
            if nums[i] == nums[i-1]:
                continue
            run = run + 1 if nums[i] == nums[i-1] + 1 else 1
            best = max(best, run)
        return best


if __name__ == "__main__":
    print(Solution().longestConsecutive([100,4,200,1,3,2]))   # 4
set + start-of-run walk O(n) O(n)

Idiomatic Python — same algorithm as Java optimal #1.

Pseudo-code

S = set(nums)
best = 0
for x in S:
    if x-1 not in S:
        cur = x
        while cur+1 in S: cur += 1
        best = max(best, cur - x + 1)
return best

Complexity

Time: O(n) — Linear amortized.
Space: O(n) — Set of distinct ints.

Edge cases & gotchas

  • Iterate over the set, not the input list (handles duplicates and shrinks iteration).
  • Empty input → set is empty → loop never runs → returns initial best=0.

Python code

from typing import List


class Solution:
    def longestConsecutive(self, nums: List[int]) -> int:
        """
        O(n) via a set with start-of-run detection. Each integer is touched
        at most twice (once for membership probe, once during a forward walk).
        """
        S = set(nums)
        best = 0
        for x in S:
            if x - 1 in S:
                continue                        # not a run-starter
            cur = x
            while cur + 1 in S:
                cur += 1
            best = max(best, cur - x + 1)
        return best


if __name__ == "__main__":
    print(Solution().longestConsecutive([100,4,200,1,3,2]))            # 4
    print(Solution().longestConsecutive([0,3,7,2,5,8,4,6,0,1]))        # 9
    print(Solution().longestConsecutive([]))                            # 0
Union-Find on a dict O(n α(n)) O(n)

Same as Java optimal #2 — adds incremental update support at the cost of constant factor.

Pseudo-code

Build dict-based DSU; union x with x±1 if present; return max component size.

Complexity

Time: O(n α(n)) — Near-linear with path compression.
Space: O(n) — Parent + size dicts.

Edge cases & gotchas

  • Watch dict.setdefault vs dict.get(..., default) — the former mutates.

Python code

from typing import List, Dict


class Solution:
    def longestConsecutive(self, nums: List[int]) -> int:
        if not nums:
            return 0
        parent: Dict[int, int] = {}
        size: Dict[int, int] = {}

        def find(x: int) -> int:
            while parent[x] != x:
                parent[x] = parent[parent[x]]
                x = parent[x]
            return x

        def union(a: int, b: int) -> None:
            ra, rb = find(a), find(b)
            if ra == rb:
                return
            if size[ra] < size[rb]:
                ra, rb = rb, ra
            parent[rb] = ra
            size[ra] += size[rb]

        for x in nums:
            if x not in parent:
                parent[x] = x
                size[x] = 1
        for x in list(parent):
            for n in (x - 1, x + 1):
                if n in parent:
                    union(x, n)
        return max(size[r] for r in parent if find(r) == r)


if __name__ == "__main__":
    print(Solution().longestConsecutive([100,4,200,1,3,2]))   # 4

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n log n)O(n α(n))
SpaceO(n) (sorted())O(n)
Difficulty1/54/5
Spec complianceViolates O(n)Meets it
Constant factorSurprisingly competitiveWorst — DSU bookkeeping in pure Python
GeneralizesLimitedYes — full DSU template
When to useDemo / tiny nStreaming / online queries
PE VerdictSame as Java: ship the set + start-of-run trick. The union-find solution is a fine answer when the interviewer asks about online updates, but for the static array it's unnecessary machinery.

What the interviewer is really testing

The big learning here is that 'O(n) with a hash set' requires a non-obvious twist: only walk forward from run-starters. Without the 'x-1 not in set' guard the algorithm is O(n²) in the worst case (every element triggers a full forward walk). PE signal: you state the amortization argument explicitly, and you call out that union-find unlocks streaming updates if the interviewer pivots.

Top gotchas

  • Without the 'x-1 not in S' check, the inner walk repeats work and the algorithm degenerates to O(n²).
  • Iterate over the SET, not the array — duplicates in the array would cause repeated work.
  • Sort + scan is a perfectly correct algorithm but VIOLATES the O(n) requirement — interviewer will dock you.
  • Java HashSet stores boxed Integer; for n = 10^5 the autoboxing pressure is real but fine within problem limits.

Ship-it

HashSet/set with start-of-run walk in both languages. Mentally rehearse the amortization argument so you can say it out loud during the interview.

Two Pointers

#10 LC 125 Easy Valid Palindrome

Two-pointer string scrubbing — the foundational palindrome problem.

Problem Statement

Given a string s, return true if it is a palindrome after converting all uppercase to lowercase and removing all non-alphanumeric characters. Treat the empty string as a palindrome.

Signature: boolean isPalindrome(String s) / def is_palindrome(s: str) -> bool

Examples

Input: s = "A man, a plan, a canal: Panama" Output: true
Input: s = "race a car" Output: false
Input: s = " " Output: true (empty after scrubbing)

Constraints

  • 1 <= s.length <= 2 * 10^5
  • s contains printable ASCII.

Approach Overview

Brute Force

Java: Filter to new string, then reverse-compare

Python: Filter + reverse-compare

O(n) O(n)

Optimal #1

Java: Two pointers, in-place skip

Python: Two pointers in-place

O(n) O(1)

Optimal #2

Java: Streamed-filter two-pointer (regex-style filter applied)

Python: Filter once, then bare two-pointer

O(n) O(n) for the filtered buffer

Java Solutions

Filter to new string, then reverse-compare O(n) O(n)

Build a scrubbed lowercase string, compare to its reverse. Two extra allocations.

Pseudo-code

filtered = lowercased alphanumeric chars of s
return filtered == reverse(filtered)

Complexity

Time: O(n) — Two linear passes.
Space: O(n) — Filtered string + reverse.

Edge cases & gotchas

  • Use Character.isLetterOrDigit for ASCII; for Unicode you may want stricter ASCII-only check.
  • Two allocations (filter + reverse) hurt the constant factor.

Java code

class Solution {
    public boolean isPalindrome(final String s) {
        final StringBuilder sb = new StringBuilder(s.length());
        for (int i = 0; i < s.length(); i++) {
            final char c = s.charAt(i);
            if (Character.isLetterOrDigit(c)) sb.append(Character.toLowerCase(c));
        }
        return sb.toString().equals(sb.reverse().toString());
    }
}
Two pointers, in-place skip O(n) O(1)

Pointers from both ends, skip non-alphanumerics, compare lowercased. Single pass, O(1) auxiliary.

Pseudo-code

l, r = 0, n-1
while l < r:
    while l < r and !isAlnum(s[l]): l += 1
    while l < r and !isAlnum(s[r]): r -= 1
    if lower(s[l]) != lower(s[r]): return false
    l += 1; r -= 1
return true

Complexity

Time: O(n) — Each character visited at most twice (skip + compare).
Space: O(1) — Two indices.

Edge cases & gotchas

  • Both pointers may converge inside the same skip block — the l < r guard inside the inner while protects against running past each other.
  • Use Character.toLowerCase only for letters (digits are no-ops) — minor optimization.
  • Empty string after scrubbing → loop never runs → returns true.

Java code

class Solution {
    public boolean isPalindrome(final String s) {
        int l = 0, r = s.length() - 1;
        while (l < r) {
            while (l < r && !isAlnum(s.charAt(l))) l++;
            while (l < r && !isAlnum(s.charAt(r))) r--;
            if (toLower(s.charAt(l)) != toLower(s.charAt(r))) return false;
            l++; r--;
        }
        return true;
    }
    private static boolean isAlnum(final char c) {
        return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9');
    }
    private static char toLower(final char c) {
        return (c >= 'A' && c <= 'Z') ? (char)(c + 32) : c;
    }
}
Streamed-filter two-pointer (regex-style filter applied) O(n) O(n) for the filtered buffer

Pre-filter once into a char[] (or scrub during a stream), then pure two-pointer comparison without skip-logic. Cleaner read; one extra allocation.

Pseudo-code

buf = lowercased alphanumeric chars
l, r = 0, len(buf)-1
while l < r:
    if buf[l] != buf[r]: return false
    l += 1; r -= 1
return true

Complexity

Time: O(n) — Single pass to filter, single sweep to compare.
Space: O(n) for the filtered buffer — Filtered buffer.

Edge cases & gotchas

  • Trades O(1) auxiliary for code clarity — useful when explaining to a less-experienced reviewer.
  • On extremely long strings the extra allocation is a noticeable cache-miss cost.

Java code

class Solution {
    public boolean isPalindrome(final String s) {
        final char[] buf = new char[s.length()];
        int n = 0;
        for (int i = 0; i < s.length(); i++) {
            final char c = s.charAt(i);
            if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) buf[n++] = c;
            else if (c >= 'A' && c <= 'Z') buf[n++] = (char)(c + 32);
        }
        int l = 0, r = n - 1;
        while (l < r) {
            if (buf[l] != buf[r]) return false;
            l++; r--;
        }
        return true;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
Space (auxiliary)O(n)O(n)
Difficulty1/52/5
Constant factorWorst — two allocationsMid — one allocation, pure compare
Cache behaviorOKExcellent after filter
Code clarityBest for a junior reviewerBest for explaining the technique
When to useHate-debugging skip-logicFilter step needed anyway (e.g., before normalization)
PE VerdictShip the in-place two-pointer (Optimal #1). It's the textbook two-pointer template, zero allocations, and the right answer for any future palindrome variant. The pre-filter version is what to write if the interviewer asks for code that won't make a junior dev cry.

Python Solutions

Filter + reverse-compare O(n) O(n)

Pythonic one-liner using comprehension or filter + slice reverse.

Pseudo-code

f = ''.join(c.lower() for c in s if c.isalnum())
return f == f[::-1]

Complexity

Time: O(n) — Two passes.
Space: O(n) — Filtered list + reversed list.

Edge cases & gotchas

  • f[::-1] is C-fast in CPython.
  • isalnum() is Unicode-aware — treats accented letters and digits as alphanumeric.

Python code

class Solution:
    def isPalindrome(self, s: str) -> bool:
        f = ''.join(c.lower() for c in s if c.isalnum())
        return f == f[::-1]


if __name__ == "__main__":
    print(Solution().isPalindrome("A man, a plan, a canal: Panama"))   # True
    print(Solution().isPalindrome("race a car"))                       # False
Two pointers in-place O(n) O(1)

Same as Java optimal #1 — skip non-alphanumerics in two while loops, compare lowercased.

Pseudo-code

l, r = 0, n-1
while l < r:
    while l < r and not s[l].isalnum(): l += 1
    while l < r and not s[r].isalnum(): r -= 1
    if s[l].lower() != s[r].lower(): return False
    l += 1; r -= 1
return True

Complexity

Time: O(n) — Linear.
Space: O(1) — Two indices.

Edge cases & gotchas

  • Pure Python loops are slow vs the C-level brute force; in raw benchmarks the one-liner often beats this for short strings.
  • isalnum() called twice per char in the worst case — for huge strings consider caching is_alpha checks.

Python code

class Solution:
    def isPalindrome(self, s: str) -> bool:
        """In-place two-pointer scan — O(1) extra space."""
        l, r = 0, len(s) - 1
        while l < r:
            while l < r and not s[l].isalnum():
                l += 1
            while l < r and not s[r].isalnum():
                r -= 1
            if s[l].lower() != s[r].lower():
                return False
            l += 1
            r -= 1
        return True


if __name__ == "__main__":
    print(Solution().isPalindrome("A man, a plan, a canal: Panama"))   # True
    print(Solution().isPalindrome("race a car"))                       # False
    print(Solution().isPalindrome(" "))                                # True (empty after scrub)
Filter once, then bare two-pointer O(n) O(n)

Hybrid — filter to a list once, then linear two-pointer over a cleaner buffer.

Pseudo-code

buf = [c.lower() for c in s if c.isalnum()]
two-pointer compare buf with buf reversed

Complexity

Time: O(n) — Two C-level passes.
Space: O(n) — Filtered list.

Edge cases & gotchas

  • Allocates once but the comparison inner loop has no skip-logic — readable for review.

Python code

class Solution:
    def isPalindrome(self, s: str) -> bool:
        buf = [c.lower() for c in s if c.isalnum()]
        l, r = 0, len(buf) - 1
        while l < r:
            if buf[l] != buf[r]:
                return False
            l += 1
            r -= 1
        return True


if __name__ == "__main__":
    print(Solution().isPalindrome("A man, a plan, a canal: Panama"))   # True

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(1)O(n)
Difficulty3/52/5
Constant factor (CPython)Worst — Python while loopsMid — filter in C, compare in Python
ReadabilityMost engineeredGood
When to useTight memory / interview ship-itWhen filter step is needed for other reasons
PE VerdictIn Python, ship the brute-force one-liner — it's the most idiomatic and the work happens in C. The two-pointer in-place version is the pure-algorithm answer to walk through; reach for it when memory is genuinely constrained or when the interviewer wants to see you do the index math.

What the interviewer is really testing

Two-pointer 101 — the question tests whether you can manage the skip-logic boundary conditions cleanly. PE signal: you write the inner-while-loops with the l < r guard correctly the first time, you handle the Unicode question if asked ('is.isalnum() includes accented letters; do you want strict ASCII?'), and you discuss the constant-factor trade-off between in-place pointers and pre-filter approaches.

Top gotchas

  • Inner while-loops MUST guard with `l < r` or you'll run pointers past each other on all-punctuation strings.
  • isLetterOrDigit / isalnum behavior: ASCII vs Unicode. Confirm the contract before writing.
  • Comparing lowercased forms — don't forget to lowercase BOTH sides; `'A' == 'a'` is false.
  • Off-by-one: increment AFTER the comparison, never before — otherwise you skip an unmatched pair.

Ship-it

Java: in-place two-pointer with explicit ASCII checks. Python: the `''.join(c.lower() for c in s if c.isalnum()) == ...[::-1]` one-liner — readable and C-fast.

#11 LC 167 Medium Two Sum II - Input Array Is Sorted

Sorted-array two-sum: O(n) without extra space, the canonical two-pointer template.

Problem Statement

Given a 1-indexed sorted array numbers and a target target, return the two indices (1-based) of the pair that sums to target. Exactly one solution exists, and you must use only constant extra space.

Signature: int[] twoSum(int[] numbers, int target) / def two_sum(numbers: List[int], target: int) -> List[int]

Examples

Input: numbers = [2,7,11,15], target = 9 Output: [1,2]
Input: numbers = [2,3,4], target = 6 Output: [1,3]
Input: numbers = [-1,0], target = -1 Output: [1,2]

Constraints

  • 2 <= numbers.length <= 3 * 10^4
  • -1000 <= numbers[i] <= 1000
  • numbers is sorted in non-decreasing order.
  • -1000 <= target <= 1000; exactly one valid pair exists.

Approach Overview

Brute Force

Java: Nested loop

Python: Nested loop

O(n²) O(1)

Optimal #1

Java: Two pointers (l, r) converging

Python: Two pointers

O(n) O(1)

Optimal #2

Java: Binary search the complement

Python: bisect-based complement search

O(n log n) O(1)

Java Solutions

Nested loop O(n²) O(1)

Same as unsorted Two Sum brute — ignores the sorted property, O(n²).

Pseudo-code

for i,j: if numbers[i]+numbers[j]==target return [i+1, j+1]

Complexity

Time: O(n²) — All pairs.
Space: O(1) — None.

Edge cases & gotchas

  • Wastes the sorted invariant — interviewer will dock for missing the pattern.

Java code

class Solution {
    public int[] twoSum(final int[] numbers, final int target) {
        for (int i = 0; i < numbers.length - 1; i++) {
            for (int j = i + 1; j < numbers.length; j++) {
                if (numbers[i] + numbers[j] == target) return new int[]{i+1, j+1};
            }
        }
        throw new IllegalArgumentException();
    }
}
Two pointers (l, r) converging O(n) O(1)

l = 0, r = n-1. Sum < target → l++; sum > target → r--; equal → done. O(n) and O(1).

Pseudo-code

l, r = 0, n-1
while l < r:
    s = numbers[l] + numbers[r]
    if s == target: return [l+1, r+1]
    elif s < target: l += 1
    else: r -= 1

Complexity

Time: O(n) — Each pointer moves at most n times total.
Space: O(1) — Two indices.

Edge cases & gotchas

  • 1-based indexing per the spec — return l+1, r+1.
  • Sum can overflow on int+int near INT_MAX/2 — use long for safety (problem caps at 1000 so it's fine here).
  • Strict l < r prevents pairing an element with itself.

Java code

class Solution {
    /** Two pointers — the canonical sorted-array template. */
    public int[] twoSum(final int[] numbers, final int target) {
        int l = 0, r = numbers.length - 1;
        while (l < r) {
            final int s = numbers[l] + numbers[r];
            if (s == target) return new int[]{l + 1, r + 1};
            if (s < target) l++; else r--;
        }
        throw new IllegalArgumentException("No two-sum solution");
    }

    public static void main(String[] args) {
        System.out.println(java.util.Arrays.toString(new Solution().twoSum(new int[]{2,7,11,15}, 9)));
    }
}
Binary search the complement O(n log n) O(1)

For each i, binary-search target - numbers[i] in numbers[i+1..n-1]. O(n log n) — strictly worse than two-pointer here, but illustrates the technique used for kSum follow-ups.

Pseudo-code

for i in 0..n-2:
    j = binarySearch(target - numbers[i], i+1, n-1)
    if j != -1: return [i+1, j+1]

Complexity

Time: O(n log n) — n iterations × log n binary search.
Space: O(1) — None.

Edge cases & gotchas

  • Search space starts at i+1 to avoid using same element twice.
  • Slower than two-pointer for this problem — present it only as a stepping-stone to 3Sum / kSum where it can dominate the recursive variant.

Java code

class Solution {
    public int[] twoSum(final int[] numbers, final int target) {
        for (int i = 0; i < numbers.length - 1; i++) {
            int lo = i + 1, hi = numbers.length - 1;
            final int need = target - numbers[i];
            while (lo <= hi) {
                final int mid = lo + (hi - lo) / 2;
                if (numbers[mid] == need) return new int[]{i + 1, mid + 1};
                if (numbers[mid] < need) lo = mid + 1; else hi = mid - 1;
            }
        }
        throw new IllegalArgumentException();
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n log n)
SpaceO(1)O(1)
Difficulty1/53/5
Uses sorted invariantNoYes — binary search
GeneralizesNoTo problems where each step costs an array search
When to useDon'tStepping stone to kSum recursion
PE VerdictTwo pointers, no exceptions. The binary-search variant is a useful template to remember for kSum recursive solutions, but for this exact problem it's strictly slower.

Python Solutions

Nested loop O(n²) O(1)

Quadratic; ignores sortedness.

Pseudo-code

nested for-loops

Complexity

Time: O(n²) — All pairs.
Space: O(1) — None.

Python code

from typing import List


class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        n = len(numbers)
        for i in range(n - 1):
            for j in range(i + 1, n):
                if numbers[i] + numbers[j] == target:
                    return [i + 1, j + 1]
        return []
Two pointers O(n) O(1)

Same as Java — converging l/r pointers.

Pseudo-code

l, r = 0, n-1; while l < r: adjust based on sum vs target

Complexity

Time: O(n) — Linear.
Space: O(1) — Two indices.

Edge cases & gotchas

  • Python ints are unbounded — no overflow worries.

Python code

from typing import List


class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        l, r = 0, len(numbers) - 1
        while l < r:
            s = numbers[l] + numbers[r]
            if s == target:
                return [l + 1, r + 1]
            if s < target:
                l += 1
            else:
                r -= 1
        return []


if __name__ == "__main__":
    print(Solution().twoSum([2, 7, 11, 15], 9))   # [1, 2]
bisect-based complement search O(n log n) O(1)

Use bisect.bisect_left to binary-search the complement. Mirrors the Java O(n log n) variant.

Pseudo-code

for i: j = bisect_left(numbers, need, i+1); if numbers[j] == need: return

Complexity

Time: O(n log n) — n × log n.
Space: O(1) — None.

Edge cases & gotchas

  • bisect_left can return n — bounds check before indexing.

Python code

from typing import List
from bisect import bisect_left


class Solution:
    def twoSum(self, numbers: List[int], target: int) -> List[int]:
        n = len(numbers)
        for i in range(n - 1):
            need = target - numbers[i]
            j = bisect_left(numbers, need, i + 1)
            if j < n and numbers[j] == need:
                return [i + 1, j + 1]
        return []

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n log n)
SpaceO(1)O(1)
Difficulty1/52/5
Constant factorWorstbisect is C-fast but n outer loop in Python
When to useDemoPedagogy
PE VerdictSame as Java: ship two-pointer. bisect_left is the right tool when the search becomes the inner step of a kSum recursion.

What the interviewer is really testing

Whether you reach for the sorted-array two-pointer pattern instantly is the entire signal. PE-level: you explain the monotonicity argument (sum only increases by moving l, only decreases by moving r) without prompting and you note this is the inner step that 3Sum and kSum both reduce to.

Top gotchas

  • 1-based indexing per spec — easy off-by-one if you forget.
  • Strict l < r matters — equal pointers mean we'd be pairing an element with itself.
  • Update pointer based on sum-vs-target sign; equality returns immediately, never increment then check.

Ship-it

Two pointers, both languages. l from left, r from right, converge based on comparison with target.

#12 LC 15 Medium 3Sum

Find all unique triplets that sum to zero — sort + two-pointer composition.

Problem Statement

Given an integer array nums, return all triplets [a, b, c] such that a + b + c == 0, where the three indices are distinct and the result list contains no duplicate triplets (order within a triplet does not matter).

Signature: List<List<Integer>> threeSum(int[] nums) / def three_sum(nums: List[int]) -> List[List[int]]

Examples

Input: nums = [-1,0,1,2,-1,-4] Output: [[-1,-1,2],[-1,0,1]]
Input: nums = [0,1,1] Output: []
Input: nums = [0,0,0] Output: [[0,0,0]]

Constraints

  • 3 <= nums.length <= 3000
  • -10^5 <= nums[i] <= 10^5

Approach Overview

Brute Force

Java: Triple nested loop with set deduplication

Python: Triple loop + set deduplication

O(n³) O(k) for unique triplets

Optimal #1

Java: Sort + two-pointer (the canonical answer)

Python: Sort + two-pointer

O(n²) O(1) auxiliary (output excluded; sort is in-place on int[])

Optimal #2

Java: Sort + hash-set complement (no two-pointer)

Python: Sort + per-anchor hash-set

O(n²) O(n)

Java Solutions

Triple nested loop with set deduplication O(n³) O(k) for unique triplets

Try all triples; canonicalize by sorting each triplet and using a HashSet to dedupe.

Pseudo-code

for i,j,k in n³: if sum==0: add sorted(triplet) to set

Complexity

Time: O(n³) — n³ triples × constant work to canonicalize.
Space: O(k) for unique triplets — Output + dedup set.

Edge cases & gotchas

  • n³ for n=3000 → 2.7·10^10 ops, will TLE.
  • Sorting triplets to dedupe is the standard but tiresome trick.

Java code

import java.util.*;

class Solution {
    public List<List<Integer>> threeSum(final int[] nums) {
        final Set<List<Integer>> seen = new HashSet<>();
        final int n = nums.length;
        for (int i = 0; i < n - 2; i++) for (int j = i+1; j < n-1; j++) for (int k = j+1; k < n; k++) {
            if (nums[i] + nums[j] + nums[k] == 0) {
                final List<Integer> t = Arrays.asList(nums[i], nums[j], nums[k]);
                Collections.sort(t);
                seen.add(t);
            }
        }
        return new ArrayList<>(seen);
    }
}
Sort + two-pointer (the canonical answer) O(n²) O(1) auxiliary (output excluded; sort is in-place on int[])

Sort. For each i, run two-pointer on the remainder for target = -nums[i]. Skip duplicates at every level.

Pseudo-code

sort(nums)
for i in 0..n-3:
    if nums[i] > 0: break       # all positive, no zero-sum triplet possible
    if i>0 and nums[i]==nums[i-1]: continue   # skip duplicate anchors
    l, r = i+1, n-1
    while l < r:
        s = nums[i]+nums[l]+nums[r]
        if s == 0:
            result.append([nums[i],nums[l],nums[r]])
            l += 1; r -= 1
            while l < r and nums[l]==nums[l-1]: l += 1
            while l < r and nums[r]==nums[r+1]: r -= 1
        elif s < 0: l += 1
        else: r -= 1

Complexity

Time: O(n²) — n outer × n inner two-pointer.
Space: O(1) auxiliary (output excluded; sort is in-place on int[]) — Output dominates.

Edge cases & gotchas

  • Skipping duplicates at i (anchor) and at l/r after a hit is THE bug source — write all three skip blocks.
  • Early break when nums[i] > 0: sorted array, anchor positive → impossible.
  • All-zero arrays: must yield [0,0,0] exactly once; the dedup logic handles this naturally.

Java code

import java.util.*;

class Solution {
    /**
     * Sort + two-pointer composition. O(n^2). Skip duplicates at every level
     * to satisfy the unique-triplets contract.
     */
    public List<List<Integer>> threeSum(final int[] nums) {
        Arrays.sort(nums);
        final List<List<Integer>> out = new ArrayList<>();
        final int n = nums.length;
        for (int i = 0; i < n - 2; i++) {
            if (nums[i] > 0) break;                         // sorted, no triplet possible
            if (i > 0 && nums[i] == nums[i-1]) continue;    // skip duplicate anchor
            int l = i + 1, r = n - 1;
            while (l < r) {
                final int sum = nums[i] + nums[l] + nums[r];
                if (sum == 0) {
                    out.add(Arrays.asList(nums[i], nums[l], nums[r]));
                    l++; r--;
                    while (l < r && nums[l] == nums[l-1]) l++;
                    while (l < r && nums[r] == nums[r+1]) r--;
                } else if (sum < 0) l++;
                else r--;
            }
        }
        return out;
    }

    public static void main(String[] args) {
        System.out.println(new Solution().threeSum(new int[]{-1,0,1,2,-1,-4}));
    }
}
Sort + hash-set complement (no two-pointer) O(n²) O(n)

For each pair (i, j) check if -(nums[i]+nums[j]) appeared in nums[i+1..j-1] via a per-i hash-set. Same n² complexity, different deduplication.

Pseudo-code

sort(nums)
for i in 0..n-3:
    seen = HashSet<int>
    for j in i+1..n-1:
        need = -(nums[i] + nums[j])
        if need in seen:
            emit [nums[i], need, nums[j]]
            while j+1<n and nums[j+1]==nums[j]: j += 1   # skip dup
        seen.add(nums[j])

Complexity

Time: O(n²) — n outer × n inner with O(1) lookups.
Space: O(n) — Per-anchor hash set up to n.

Edge cases & gotchas

  • Dedup is trickier than two-pointer — must track 'last emitted second value' explicitly.
  • Same asymptote as two-pointer but worse constant factor (hash ops vs index ops).
  • Useful as a stepping stone if the problem changes (e.g., 3Sum closest with negative-sum target).

Java code

import java.util.*;

class Solution {
    public List<List<Integer>> threeSum(final int[] nums) {
        Arrays.sort(nums);
        final List<List<Integer>> out = new ArrayList<>();
        final int n = nums.length;
        for (int i = 0; i < n - 2; i++) {
            if (nums[i] > 0) break;
            if (i > 0 && nums[i] == nums[i-1]) continue;
            final Set<Integer> seen = new HashSet<>();
            for (int j = i + 1; j < n; j++) {
                final int need = -nums[i] - nums[j];
                if (seen.contains(need)) {
                    out.add(Arrays.asList(nums[i], need, nums[j]));
                    while (j + 1 < n && nums[j+1] == nums[j]) j++;
                }
                seen.add(nums[j]);
            }
        }
        return out;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n³)O(n²)
SpaceO(k) for setO(n)
Difficulty2/54/5
Constant factorWorstSlightly worse — hash ops
Cache behaviorOKWorse — pointer chasing
Generalizes to 4SumTriviallyYes
When to useTiny n, demoWhen the values are sparse and a hash-set complements better than index arithmetic
PE VerdictSort + two-pointer is the canonical answer and what every interviewer expects. Memorize the three skip blocks (anchor, l after hit, r after hit) and the early-break-on-positive optimization. The hash-set variant is a fine pivot answer to '4Sum' or 'how would you handle streaming additions'.

Python Solutions

Triple loop + set deduplication O(n³) O(k)

Same as Java baseline.

Pseudo-code

for i,j,k: if sum==0: add tuple(sorted(...)) to set

Complexity

Time: O(n³) — Cubic.
Space: O(k) — Output + dedup set.

Edge cases & gotchas

  • TLEs.

Python code

from typing import List


class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        out = set()
        n = len(nums)
        for i in range(n-2):
            for j in range(i+1, n-1):
                for k in range(j+1, n):
                    if nums[i]+nums[j]+nums[k] == 0:
                        out.add(tuple(sorted((nums[i], nums[j], nums[k]))))
        return [list(t) for t in out]
Sort + two-pointer O(n²) O(1)

Same algorithm as Java optimal #1.

Pseudo-code

see Java pseudo-code

Complexity

Time: O(n²) — Quadratic.
Space: O(1) — Output only.

Edge cases & gotchas

  • Same skip-block care as Java.

Python code

from typing import List


class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        nums.sort()
        out: List[List[int]] = []
        n = len(nums)
        for i in range(n - 2):
            if nums[i] > 0:
                break
            if i > 0 and nums[i] == nums[i-1]:
                continue
            l, r = i + 1, n - 1
            while l < r:
                s = nums[i] + nums[l] + nums[r]
                if s == 0:
                    out.append([nums[i], nums[l], nums[r]])
                    l += 1
                    r -= 1
                    while l < r and nums[l] == nums[l-1]:
                        l += 1
                    while l < r and nums[r] == nums[r+1]:
                        r -= 1
                elif s < 0:
                    l += 1
                else:
                    r -= 1
        return out


if __name__ == "__main__":
    print(Solution().threeSum([-1,0,1,2,-1,-4]))   # [[-1,-1,2],[-1,0,1]]
    print(Solution().threeSum([0,0,0]))            # [[0,0,0]]
Sort + per-anchor hash-set O(n²) O(n)

Same as Java optimal #2.

Pseudo-code

for i: build a per-i set; check complement for each j

Complexity

Time: O(n²) — Quadratic.
Space: O(n) — Hash set per anchor.

Edge cases & gotchas

  • Dedup at the inner pointer is fiddly.

Python code

from typing import List


class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        nums.sort()
        out: List[List[int]] = []
        n = len(nums)
        for i in range(n - 2):
            if nums[i] > 0:
                break
            if i > 0 and nums[i] == nums[i-1]:
                continue
            seen = set()
            j = i + 1
            while j < n:
                need = -nums[i] - nums[j]
                if need in seen:
                    out.append([nums[i], need, nums[j]])
                    # skip duplicate j values to avoid emitting same triplet
                    while j + 1 < n and nums[j+1] == nums[j]:
                        j += 1
                seen.add(nums[j])
                j += 1
        return out

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n³)O(n²)
SpaceO(k)O(n)
Difficulty2/54/5
Constant factorWorstWorse — set ops in CPython
Generalizes to kSumTriviallyYes
When to useDemoSparse / hash-friendly variants
PE VerdictSame as Java — ship sort + two-pointer. The pattern generalizes cleanly: 4Sum is a loop wrapping 3Sum, kSum is recursion bottoming out at twoSumSorted.

What the interviewer is really testing

3Sum is THE composition problem — sort transforms it, then two-pointer solves the inner reduction. PE signal: you propose the sort+two-pointer instantly, you write the three skip blocks correctly the first time, you mention the early-break-on-positive optimization, and you sketch how to extend to kSum.

Top gotchas

  • THREE places to skip duplicates: (i) the outer anchor, (ii) l after a hit, (iii) r after a hit. Missing any one yields duplicate triplets.
  • After finding a hit you MUST advance both l and r — moving only one creates infinite loops or duplicates.
  • Early break when nums[i] > 0 saves significant time on dense-positive inputs.
  • Don't return tuples to a List<List<Integer>> contract — wrap with Arrays.asList in Java.

Ship-it

Sort + two-pointer with three skip blocks + early break on positive. Memorize this template — 4Sum, kSum, and 3Sum-closest all build on it.

#13 LC 11 Medium Container With Most Water

Two-pointer area maximization with a monotonic argument: shrink the shorter side.

Problem Statement

Given n non-negative integers height[i] representing vertical lines on the x-axis, find two lines that together with the x-axis form a container holding the most water. Return the maximum area.

Signature: int maxArea(int[] height) / def max_area(height: List[int]) -> int

Examples

Input: height = [1,8,6,2,5,4,8,3,7] Output: 49
Input: height = [1,1] Output: 1

Constraints

  • 2 <= height.length <= 10^5
  • 0 <= height[i] <= 10^4

Approach Overview

Brute Force

Java: All pairs

Python: All pairs

O(n²) O(1)

Optimal #1

Java: Two pointers, shrink the shorter side

Python: Two pointers

O(n) O(1)

Optimal #2

Java: Two pointers with skip-the-shorter-runs heuristic

Python: Two pointers with skip-runs

O(n) O(1)

Java Solutions

All pairs O(n²) O(1)

Try every (i, j); compute (j-i) * min(height[i], height[j]); track max.

Pseudo-code

for i, j: best = max(best, (j-i) * min(h[i], h[j]))

Complexity

Time: O(n²) — n(n-1)/2 pairs.
Space: O(1) — Scalar accumulators.

Edge cases & gotchas

  • TLEs at n = 10^5.

Java code

class Solution {
    public int maxArea(final int[] height) {
        int best = 0;
        for (int i = 0; i < height.length - 1; i++)
            for (int j = i + 1; j < height.length; j++)
                best = Math.max(best, (j - i) * Math.min(height[i], height[j]));
        return best;
    }
}
Two pointers, shrink the shorter side O(n) O(1)

Start at extremes. Width is maximal; height is bounded by min(left, right). Moving the taller side can never improve area — so move the shorter.

Pseudo-code

l, r = 0, n-1; best = 0
while l < r:
    h = min(height[l], height[r])
    best = max(best, (r - l) * h)
    if height[l] < height[r]: l += 1
    else: r -= 1
return best

Complexity

Time: O(n) — Pointers cover n total moves.
Space: O(1) — Two indices.

Edge cases & gotchas

  • Why shrink shorter? The pair (l, r) area is bounded by min(height). Moving the TALLER side strictly decreases width AND cannot raise the min — guaranteed not better.
  • Equal heights — moving either pointer is fine; conventionally move l.

Java code

class Solution {
    /**
     * Two pointers; always shrink toward the shorter side. The monotonic
     * argument: keeping the shorter side guarantees the new area is <=
     * the old, so abandoning it is the only progress move.
     */
    public int maxArea(final int[] height) {
        int l = 0, r = height.length - 1, best = 0;
        while (l < r) {
            final int h = Math.min(height[l], height[r]);
            best = Math.max(best, (r - l) * h);
            if (height[l] < height[r]) l++; else r--;
        }
        return best;
    }
}
Two pointers with skip-the-shorter-runs heuristic O(n) O(1)

After choosing to shrink the shorter side, skip past lines NOT taller than the one we just abandoned — they can't improve the answer.

Pseudo-code

l, r = 0, n-1; best = 0
while l < r:
    h = min(height[l], height[r])
    best = max(best, (r-l) * h)
    if height[l] < height[r]:
        prev = height[l]; l += 1
        while l < r and height[l] <= prev: l += 1
    else:
        prev = height[r]; r -= 1
        while l < r and height[r] <= prev: r -= 1

Complexity

Time: O(n) — Same asymptote, smaller constant on inputs with monotone runs.
Space: O(1) — Two indices.

Edge cases & gotchas

  • Strictly the same answer; just fewer iterations on already-sorted-ish inputs.
  • Marginal win — branch prediction may absorb the difference. Don't add complexity unless profiling shows it.

Java code

class Solution {
    public int maxArea(final int[] height) {
        int l = 0, r = height.length - 1, best = 0;
        while (l < r) {
            final int h = Math.min(height[l], height[r]);
            best = Math.max(best, (r - l) * h);
            if (height[l] < height[r]) {
                final int prev = height[l++];
                while (l < r && height[l] <= prev) l++;
            } else {
                final int prev = height[r--];
                while (l < r && height[r] <= prev) r--;
            }
        }
        return best;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(1)O(1)
Difficulty1/54/5 — extra inner loop
Cache behaviorBest (sequential)Excellent
Constant factorWorstMarginally faster on adversarial inputs
ProvabilityTrivialSame as Optimal #1 plus skip optimality
When to useTiny n onlyProfiled hot path; rarely worth the extra complexity
PE VerdictShip the simple two-pointer (Optimal #1). The skip-runs optimization (Optimal #2) is a real but tiny win and adds an inner loop that's easy to bug. The interview gold is articulating the monotonic proof: 'moving the taller side can't help because the new pair's height is still capped by the shorter side, and the width is now smaller.'

Python Solutions

All pairs O(n²) O(1)

Quadratic — TLEs at n=10^5.

Pseudo-code

nested for loops

Complexity

Time: O(n²) — All pairs.
Space: O(1) — Scalars.

Python code

from typing import List


class Solution:
    def maxArea(self, height: List[int]) -> int:
        best = 0
        n = len(height)
        for i in range(n - 1):
            for j in range(i + 1, n):
                best = max(best, (j - i) * min(height[i], height[j]))
        return best
Two pointers O(n) O(1)

Same as Java optimal #1.

Pseudo-code

l, r converge; shrink shorter side; track max area

Complexity

Time: O(n) — Linear.
Space: O(1) — Two indices.

Edge cases & gotchas

  • Pure-Python loop is slower than Java but still fits the time budget.

Python code

from typing import List


class Solution:
    def maxArea(self, height: List[int]) -> int:
        l, r = 0, len(height) - 1
        best = 0
        while l < r:
            h = height[l] if height[l] < height[r] else height[r]
            area = (r - l) * h
            if area > best:
                best = area
            if height[l] < height[r]:
                l += 1
            else:
                r -= 1
        return best


if __name__ == "__main__":
    print(Solution().maxArea([1,8,6,2,5,4,8,3,7]))   # 49
Two pointers with skip-runs O(n) O(1)

Same as Java optimal #2.

Pseudo-code

after each shrink, skip past heights <= the abandoned one

Complexity

Time: O(n) — Linear.
Space: O(1) — Two indices.

Edge cases & gotchas

  • Tiny win in pure Python — skip if readability matters.

Python code

from typing import List


class Solution:
    def maxArea(self, height: List[int]) -> int:
        l, r = 0, len(height) - 1
        best = 0
        while l < r:
            best = max(best, (r - l) * min(height[l], height[r]))
            if height[l] < height[r]:
                prev = height[l]
                l += 1
                while l < r and height[l] <= prev:
                    l += 1
            else:
                prev = height[r]
                r -= 1
                while l < r and height[r] <= prev:
                    r -= 1
        return best

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(1)O(1)
Difficulty1/54/5
Constant factorWorstMarginal extra branch overhead
When to useDemoPerformance-tuned
PE VerdictTwo-pointer with the simple version. In Python the skip-runs optimization is hidden under interpreter overhead anyway.

What the interviewer is really testing

The whole signal here is the monotonic argument: 'moving the taller side cannot help, only moving the shorter can'. Articulate it before writing code. PE signal: you state the invariant (best area found so far is optimal over all pairs containing the abandoned-shorter side) and you connect this to other 'shrink the constraining side' problems (Trapping Rain Water, k-windowed problems).

Top gotchas

  • Equal-height case: either pointer move is valid; pick one consistently to avoid loops.
  • Track running max BEFORE moving pointers — moving first then computing risks missing the boundary case.
  • Don't try to be clever with both pointers moving simultaneously; the proof requires moving exactly one.

Ship-it

Two-pointer, shrink shorter side. Both languages. State the proof out loud during the interview.

#14 LC 42 Hard Trapping Rain Water

Compute trapped rainfall over a histogram — three legitimate approaches with a clear winner.

Problem Statement

Given an array height representing an elevation map (each bar 1 unit wide), compute how much water can be trapped after rain. Water above bar i equals min(maxLeft, maxRight) − height[i].

Signature: int trap(int[] height) / def trap(height: List[int]) -> int

Examples

Input: height = [0,1,0,2,1,0,1,3,2,1,2,1] Output: 6
Input: height = [4,2,0,3,2,5] Output: 9

Constraints

  • n == height.length
  • 1 <= n <= 2 * 10^4
  • 0 <= height[i] <= 10^5

Approach Overview

Brute Force

Java: For each bar, scan left and right for the max

Python: For each i, scan both sides

O(n²) O(1)

Optimal #1

Java: Prefix-max + suffix-max arrays

Python: Prefix-max + suffix-max arrays

O(n) O(n)

Optimal #2

Java: Two pointers, advance from the lower side

Python: Two pointers

O(n) O(1)

Java Solutions

For each bar, scan left and right for the max O(n²) O(1)

Quadratic baseline. Captures the formula directly: water[i] = min(maxLeft, maxRight) - height[i].

Pseudo-code

for i in 0..n-1:
    L = max(height[0..i]); R = max(height[i..n-1])
    water += max(0, min(L,R) - height[i])

Complexity

Time: O(n²) — n outer × n inner.
Space: O(1) — Scalars.

Edge cases & gotchas

  • Endpoints contribute zero water (their min(L,R) = height[i] trivially).
  • All-zero or strictly-increasing heights → zero water. Already-handled by the formula.

Java code

class Solution {
    public int trap(final int[] height) {
        int total = 0;
        for (int i = 0; i < height.length; i++) {
            int L = 0, R = 0;
            for (int j = 0; j <= i; j++) L = Math.max(L, height[j]);
            for (int j = i; j < height.length; j++) R = Math.max(R, height[j]);
            total += Math.max(0, Math.min(L, R) - height[i]);
        }
        return total;
    }
}
Prefix-max + suffix-max arrays O(n) O(n)

Precompute leftMax and rightMax in two linear passes; sum min(left,right) - height[i].

Pseudo-code

leftMax[i] = max of height[0..i]
rightMax[i] = max of height[i..n-1]
total = sum( min(leftMax[i], rightMax[i]) - height[i] )

Complexity

Time: O(n) — Three passes.
Space: O(n) — Two aux arrays of n.

Edge cases & gotchas

  • Inclusive of position i in both running maxes — water above i can't exceed height[i].
  • Allocations dominate constant factor at n=10^5 (still fast in absolute terms).

Java code

class Solution {
    public int trap(final int[] height) {
        final int n = height.length;
        if (n == 0) return 0;
        final int[] L = new int[n];
        final int[] R = new int[n];
        L[0] = height[0];
        for (int i = 1; i < n; i++) L[i] = Math.max(L[i-1], height[i]);
        R[n-1] = height[n-1];
        for (int i = n - 2; i >= 0; i--) R[i] = Math.max(R[i+1], height[i]);
        int total = 0;
        for (int i = 0; i < n; i++) total += Math.min(L[i], R[i]) - height[i];
        return total;
    }
}
Two pointers, advance from the lower side O(n) O(1)

Maintain leftMax and rightMax scalars. Advance whichever side is shorter — the other side's max is guaranteed at least as large.

Pseudo-code

l, r = 0, n-1; lmax = rmax = 0; total = 0
while l < r:
    if height[l] < height[r]:
        if height[l] >= lmax: lmax = height[l]
        else: total += lmax - height[l]
        l += 1
    else:
        if height[r] >= rmax: rmax = height[r]
        else: total += rmax - height[r]
        r -= 1

Complexity

Time: O(n) — Single pass with two pointers.
Space: O(1) — Two indices, two scalars.

Edge cases & gotchas

  • Why this works: when height[l] < height[r], we KNOW some bar to the right of l is ≥ height[r] > height[l]. So lmax is the binding constraint, water above l is exactly lmax - height[l].
  • Symmetric on the other side.
  • No allocations during the pass — best constant factor.

Java code

class Solution {
    /**
     * Two-pointer rainwater. Always advance from the lower wall — its
     * opposing wall is guaranteed at least as tall, so the running max
     * on the lower side fully determines water above each step.
     */
    public int trap(final int[] height) {
        int l = 0, r = height.length - 1;
        int lmax = 0, rmax = 0, total = 0;
        while (l < r) {
            if (height[l] < height[r]) {
                if (height[l] >= lmax) lmax = height[l];
                else total += lmax - height[l];
                l++;
            } else {
                if (height[r] >= rmax) rmax = height[r];
                else total += rmax - height[r];
                r--;
            }
        }
        return total;
    }

    public static void main(String[] args) {
        System.out.println(new Solution().trap(new int[]{0,1,0,2,1,0,1,3,2,1,2,1}));   // 6
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(1)O(n)
Difficulty2/53/5
Constant factorWorstTwo arrays + three passes
Cache behaviorOKOK
ProvabilityDirectDirect
When to useQuick whiteboardWhiteboard if you can't prove two-pointer
PE VerdictShip the two-pointer. The prefix/suffix array variant is a fine teaching answer; in production it doubles the memory traffic. Memorize the two-pointer proof — interviewers almost always ask 'why does that work?'

Python Solutions

For each i, scan both sides O(n²) O(1)

Same shape — quadratic.

Pseudo-code

for i: L = max(height[:i+1]); R = max(height[i:]); water += min(L,R) - height[i]

Complexity

Time: O(n²) — Quadratic.
Space: O(1) — None.

Python code

from typing import List


class Solution:
    def trap(self, height: List[int]) -> int:
        total = 0
        n = len(height)
        for i in range(n):
            L = max(height[: i + 1])
            R = max(height[i:])
            total += min(L, R) - height[i]
        return total
Prefix-max + suffix-max arrays O(n) O(n)

Same algorithm as Java optimal #1.

Pseudo-code

build L and R running max; sum min(L,R) - h

Complexity

Time: O(n) — Three passes.
Space: O(n) — Two arrays.

Edge cases & gotchas

  • Easy and clear — write this if you forget the two-pointer proof live.

Python code

from typing import List


class Solution:
    def trap(self, height: List[int]) -> int:
        n = len(height)
        if n == 0:
            return 0
        L = [0] * n
        R = [0] * n
        L[0] = height[0]
        for i in range(1, n):
            L[i] = max(L[i-1], height[i])
        R[-1] = height[-1]
        for i in range(n - 2, -1, -1):
            R[i] = max(R[i+1], height[i])
        return sum(min(L[i], R[i]) - height[i] for i in range(n))


if __name__ == "__main__":
    print(Solution().trap([0,1,0,2,1,0,1,3,2,1,2,1]))   # 6
Two pointers O(n) O(1)

Same algorithm as Java optimal #2.

Pseudo-code

see Java pseudo-code

Complexity

Time: O(n) — Single pass.
Space: O(1) — Scalars.

Edge cases & gotchas

  • Branch ordering matters for correctness — write it carefully.

Python code

from typing import List


class Solution:
    def trap(self, height: List[int]) -> int:
        l, r = 0, len(height) - 1
        lmax = rmax = total = 0
        while l < r:
            if height[l] < height[r]:
                if height[l] >= lmax:
                    lmax = height[l]
                else:
                    total += lmax - height[l]
                l += 1
            else:
                if height[r] >= rmax:
                    rmax = height[r]
                else:
                    total += rmax - height[r]
                r -= 1
        return total


if __name__ == "__main__":
    print(Solution().trap([0,1,0,2,1,0,1,3,2,1,2,1]))   # 6
    print(Solution().trap([4,2,0,3,2,5]))                # 9

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(1)O(n)
Difficulty2/53/5
Constant factorWorstTwo passes in C
When to useDemoWhiteboard fallback
PE VerdictShip the two-pointer. The prefix-suffix array form is a perfectly fine answer if the two-pointer proof escapes you mid-interview — write it cleanly, then explain how to compress it.

What the interviewer is really testing

Trapping Rain Water is one of THE classic 'do you understand the invariant' problems. PE signal: you derive the formula water[i] = min(maxLeft, maxRight) - height[i] from first principles, you propose the prefix-max approach as the natural O(n) answer, and then you tighten to two-pointer with the asymmetric-walls argument — stating it before coding.

Top gotchas

  • Two-pointer proof: when height[l] < height[r], some bar in [l+1..r] is ≥ height[r] > height[l]. So the right wall is at least height[r]; lmax is therefore the binding constraint at position l.
  • Prefix/suffix arrays must INCLUDE the current index in the running max, not exclude — otherwise you treat each bar as having infinite water capacity above itself.
  • Don't subtract negative values — they cannot occur if your running maxes include position i.
  • Stack-based monotonic-stack solutions (column-by-column) also work and are O(n); they're a fine alternative if you can't sell the two-pointer proof.

Ship-it

Two-pointer with running lmax/rmax. State the asymmetric-walls invariant before coding so the reviewer knows you understand why this works.

Sliding Window

#15 LC 121 Easy Best Time to Buy and Sell Stock

Maximum profit from one buy + one sell — running-min sliding window.

Problem Statement

Given an array prices where prices[i] is the stock price on day i, return the maximum profit achievable from a single buy and a single sell. You must buy before you sell. If no profit is possible, return 0.

Signature: int maxProfit(int[] prices) / def max_profit(prices: List[int]) -> int

Examples

Input: prices = [7,1,5,3,6,4] Output: 5 (buy at 1, sell at 6)
Input: prices = [7,6,4,3,1] Output: 0 (monotone decreasing)

Constraints

  • 1 <= prices.length <= 10^5
  • 0 <= prices[i] <= 10^4

Approach Overview

Brute Force

Java: All buy/sell pairs

Python: All pairs

O(n²) O(1)

Optimal #1

Java: Single pass, track running min and best diff

Python: Running min + diff

O(n) O(1)

Optimal #2

Java: Two-pointer sliding window (l = buy candidate, r = sell)

Python: Two-pointer window framing

O(n) O(1)

Java Solutions

All buy/sell pairs O(n²) O(1)

For each i < j, compute prices[j] - prices[i], track max. Quadratic.

Pseudo-code

for i, j: best = max(best, prices[j] - prices[i])

Complexity

Time: O(n²) — All pairs.
Space: O(1) — Scalar.

Edge cases & gotchas

  • TLEs at n=10^5.

Java code

class Solution {
    public int maxProfit(final int[] prices) {
        int best = 0;
        for (int i = 0; i < prices.length - 1; i++)
            for (int j = i + 1; j < prices.length; j++)
                best = Math.max(best, prices[j] - prices[i]);
        return best;
    }
}
Single pass, track running min and best diff O(n) O(1)

Sweep once. min so far = best buy day; best = max(best, price - min). O(1) state.

Pseudo-code

minPrice = +inf; best = 0
for p in prices:
    minPrice = min(minPrice, p)
    best = max(best, p - minPrice)
return best

Complexity

Time: O(n) — Single pass.
Space: O(1) — Two scalars.

Edge cases & gotchas

  • Order matters: update min FIRST, then compute diff. Otherwise on day p == minPrice you'd compute (p - prev min) then overwrite.
  • All-decreasing input → best stays 0, never goes negative because we initialized to 0.
  • Single-element input → loop runs once, best stays 0.

Java code

class Solution {
    public int maxProfit(final int[] prices) {
        int minPrice = Integer.MAX_VALUE;
        int best = 0;
        for (final int p : prices) {
            if (p < minPrice) minPrice = p;
            else if (p - minPrice > best) best = p - minPrice;
        }
        return best;
    }

    public static void main(String[] args) {
        System.out.println(new Solution().maxProfit(new int[]{7,1,5,3,6,4}));   // 5
    }
}
Two-pointer sliding window (l = buy candidate, r = sell) O(n) O(1)

Same algorithm in two-pointer dress. l locks to the lowest-so-far; r scans. Useful as a stepping stone to harder window problems.

Pseudo-code

l, r = 0, 1; best = 0
while r < n:
    if prices[r] < prices[l]: l = r          # found a better buy day
    else: best = max(best, prices[r] - prices[l])
    r += 1

Complexity

Time: O(n) — Each pointer moves at most n.
Space: O(1) — Two indices.

Edge cases & gotchas

  • Shifting l to r when prices[r] < prices[l] is the correct generalization — locking in the new minimum buy.
  • Identical to Optimal #1; framing differs, work is the same.

Java code

class Solution {
    public int maxProfit(final int[] prices) {
        int l = 0, best = 0;
        for (int r = 1; r < prices.length; r++) {
            if (prices[r] < prices[l]) l = r;
            else best = Math.max(best, prices[r] - prices[l]);
        }
        return best;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(1)O(1)
Difficulty1/52/5
Constant factorWorstSame
GeneralizesNoTo 'longest profit window' style problems
When to useTiny nWhen framing as a window helps the next problem
PE VerdictShip the running-min single pass. Both Optimal forms are the same algorithm — choose the one that's easier to teach in your interview style. Update min FIRST, then diff — the order matters.

Python Solutions

All pairs O(n²) O(1)

Quadratic.

Pseudo-code

nested loops

Complexity

Time: O(n²) — Quadratic.
Space: O(1) — Scalar.

Python code

from typing import List


class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        best = 0
        for i in range(len(prices) - 1):
            for j in range(i + 1, len(prices)):
                best = max(best, prices[j] - prices[i])
        return best
Running min + diff O(n) O(1)

Same as Java optimal #1.

Pseudo-code

see Java pseudo-code

Complexity

Time: O(n) — Single pass.
Space: O(1) — Scalars.

Edge cases & gotchas

  • math.inf is the cleanest sentinel.

Python code

from typing import List
import math


class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        min_price = math.inf
        best = 0
        for p in prices:
            if p < min_price:
                min_price = p
            elif p - min_price > best:
                best = p - min_price
        return best


if __name__ == "__main__":
    print(Solution().maxProfit([7,1,5,3,6,4]))   # 5
    print(Solution().maxProfit([7,6,4,3,1]))     # 0
Two-pointer window framing O(n) O(1)

Same as Java optimal #2.

Pseudo-code

l = best buy index; r scans forward

Complexity

Time: O(n) — Single pass.
Space: O(1) — Two indices.

Python code

from typing import List


class Solution:
    def maxProfit(self, prices: List[int]) -> int:
        l, best = 0, 0
        for r in range(1, len(prices)):
            if prices[r] < prices[l]:
                l = r
            else:
                profit = prices[r] - prices[l]
                if profit > best:
                    best = profit
        return best

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(1)O(1)
Difficulty1/52/5
Constant factorWorstSame
When to useDemoEquivalent — pick by readability preference
PE VerdictSame as Java — running min, single pass.

What the interviewer is really testing

Foundational sliding-window / DP-with-running-state question. PE signal: you immediately propose O(1) state (min seen so far + best so far), articulate the order-of-updates invariant, and gracefully handle the follow-up generalizations (cooldown, k transactions, transaction fee) by sketching the DP states.

Top gotchas

  • Update min FIRST, THEN compute diff — reversed order overstates profit on a new low day.
  • Initialize best to 0, not negative infinity — the contract returns 0 for unprofitable.
  • Don't try to track buy index — only the min price matters for this single-transaction variant.
  • Watch for off-by-one in the two-pointer variant: r starts at 1, not 0.

Ship-it

Single pass with running min and best — both languages.

#16 LC 3 Medium Longest Substring Without Repeating Characters

Sliding-window with last-seen-index map — the canonical 'unique window' problem.

Problem Statement

Given a string s, return the length of the longest substring without repeating characters.

Signature: int lengthOfLongestSubstring(String s) / def length_of_longest_substring(s: str) -> int

Examples

Input: s = "abcabcbb" Output: 3 ("abc")
Input: s = "bbbbb" Output: 1
Input: s = "pwwkew" Output: 3 ("wke")

Constraints

  • 0 <= s.length <= 5 * 10^4
  • s consists of English letters, digits, symbols and spaces.

Approach Overview

Brute Force

Java: All substrings; check uniqueness

Python: All substrings

O(n³) O(n)

Optimal #1

Java: Sliding window with HashSet

Python: Sliding window with set

O(n) O(min(n, |Σ|))

Optimal #2

Java: Sliding window with last-seen index map (single advance of l)

Python: Sliding window with last-seen dict

O(n) O(min(n, |Σ|))

Java Solutions

All substrings; check uniqueness O(n³) O(n)

Examine every substring with a HashSet for uniqueness. O(n³).

Pseudo-code

for each (i,j): if substring i..j has all unique → track length

Complexity

Time: O(n³) — n² substrings × n uniqueness check.
Space: O(n) — HashSet per check.

Edge cases & gotchas

  • TLEs above n=200.

Java code

import java.util.*;
class Solution {
    public int lengthOfLongestSubstring(final String s) {
        int best = 0;
        for (int i = 0; i < s.length(); i++) {
            final Set<Character> seen = new HashSet<>();
            for (int j = i; j < s.length(); j++) {
                if (!seen.add(s.charAt(j))) break;
                best = Math.max(best, j - i + 1);
            }
        }
        return best;
    }
}
Sliding window with HashSet O(n) O(min(n, |Σ|))

Two pointers l, r. Expand r, evicting from set + advancing l on duplicate.

Pseudo-code

l = 0; seen = set(); best = 0
for r in 0..n-1:
    while s[r] in seen: seen.remove(s[l]); l += 1
    seen.add(s[r])
    best = max(best, r - l + 1)

Complexity

Time: O(n) — Each char enters and leaves the set at most once.
Space: O(min(n, |Σ|)) — Bounded by alphabet size or window length.

Edge cases & gotchas

  • While-loop is critical — the duplicate may be deep inside the window, must advance l until it's removed.
  • Each char processed at most twice → linear total.

Java code

import java.util.*;
class Solution {
    public int lengthOfLongestSubstring(final String s) {
        final Set<Character> seen = new HashSet<>();
        int l = 0, best = 0;
        for (int r = 0; r < s.length(); r++) {
            while (!seen.add(s.charAt(r))) {
                seen.remove(s.charAt(l++));
            }
            best = Math.max(best, r - l + 1);
        }
        return best;
    }
}
Sliding window with last-seen index map (single advance of l) O(n) O(min(n, |Σ|))

Map char → last index. On duplicate, jump l past that last index in one step instead of inching.

Pseudo-code

lastSeen = {}; l = 0; best = 0
for r, c in enumerate(s):
    if c in lastSeen and lastSeen[c] >= l:
        l = lastSeen[c] + 1
    lastSeen[c] = r
    best = max(best, r - l + 1)

Complexity

Time: O(n) — r visits each char once; l only ever increases.
Space: O(min(n, |Σ|)) — Last-seen map.

Edge cases & gotchas

  • Guard `lastSeen[c] >= l`: an old occurrence already outside the current window must be ignored.
  • Jumping l (rather than inching) makes this strictly faster on adversarial inputs like 'abcdefga' — l skips to the duplicate position + 1 in O(1).

Java code

import java.util.*;
class Solution {
    /**
     * Last-seen index map — O(n) with single-step pointer jumps.
     * Faster than the set-based variant on adversarial inputs because
     * 'l' advances in O(1) rather than inching through the window.
     */
    public int lengthOfLongestSubstring(final String s) {
        final Map<Character, Integer> last = new HashMap<>();
        int l = 0, best = 0;
        for (int r = 0; r < s.length(); r++) {
            final char c = s.charAt(r);
            final Integer prev = last.get(c);
            if (prev != null && prev >= l) l = prev + 1;
            last.put(c, r);
            best = Math.max(best, r - l + 1);
        }
        return best;
    }

    public static void main(String[] args) {
        System.out.println(new Solution().lengthOfLongestSubstring("abcabcbb"));   // 3
        System.out.println(new Solution().lengthOfLongestSubstring("pwwkew"));     // 3
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n³)O(n)
SpaceO(n)O(min(n, |Σ|))
Difficulty2/53/5
Constant factorWorstEach char up to 2 ops
When to useTiny nWhiteboard ship
PE VerdictShip the last-seen index map. Same asymptote, smaller constant, and the jumping behavior makes the algorithm easier to reason about. The set-and-inch version is fine for whiteboard if you forget the jump trick; both are correct.

Python Solutions

All substrings O(n³) O(n)

Cubic baseline.

Pseudo-code

nested loops + set

Complexity

Time: O(n³) — Cubic.
Space: O(n) — Set per check.

Python code

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        best = 0
        for i in range(len(s)):
            seen = set()
            for j in range(i, len(s)):
                if s[j] in seen:
                    break
                seen.add(s[j])
                best = max(best, j - i + 1)
        return best
Sliding window with set O(n) O(|Σ|)

Same as Java optimal #1.

Pseudo-code

l=0; seen=set(); for r,c in enumerate(s): while c in seen: seen.remove(s[l]); l+=1; seen.add(c)

Complexity

Time: O(n) — Linear.
Space: O(|Σ|) — Set bounded by alphabet.

Python code

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        seen = set()
        l = best = 0
        for r, c in enumerate(s):
            while c in seen:
                seen.remove(s[l])
                l += 1
            seen.add(c)
            best = max(best, r - l + 1)
        return best
Sliding window with last-seen dict O(n) O(|Σ|)

Same as Java optimal #2 — the canonical Python answer.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(|Σ|) — dict.

Edge cases & gotchas

  • Guard last[c] >= l so stale indices don't shrink the window.

Python code

from typing import Dict


class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        last: Dict[str, int] = {}
        l = best = 0
        for r, c in enumerate(s):
            if c in last and last[c] >= l:
                l = last[c] + 1
            last[c] = r
            best = max(best, r - l + 1)
        return best


if __name__ == "__main__":
    print(Solution().lengthOfLongestSubstring("abcabcbb"))   # 3
    print(Solution().lengthOfLongestSubstring("pwwkew"))     # 3

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n³)O(n)
SpaceO(n)O(|Σ|)
Difficulty2/53/5
Constant factorWorstInner-loop overhead
When to useDemoWhiteboard fallback
PE VerdictLast-seen dict is the canonical Python answer. The crucial guard is `last[c] >= l`; forgetting it lets stale entries shrink the window incorrectly.

What the interviewer is really testing

Sliding-window template question. PE signal: you write the last-seen-index version, you guard against stale entries with `last[c] >= l`, and you can articulate why the jumping form has a smaller constant factor than the inching form.

Top gotchas

  • The stale-index guard `last[c] >= l` is essential — without it, a duplicate occurrence outside the current window incorrectly shrinks l.
  • Inching with a set requires a WHILE loop, not IF — the duplicate may be deep inside the window.
  • For ASCII-only inputs, replace HashMap with int[128] for ~3× speedup.
  • Empty-string corner case: loop doesn't execute, returns 0 — already correct.

Ship-it

Last-seen dict / HashMap with the stale-index guard. Both languages.

#17 LC 424 Medium Longest Repeating Character Replacement

Variable-window with the (windowLen - maxFreq) ≤ k invariant — the trickiest sliding window.

Problem Statement

Given a string s and an integer k, return the length of the longest substring containing the same letter you can obtain by replacing at most k characters.

Signature: int characterReplacement(String s, int k) / def character_replacement(s: str, k: int) -> int

Examples

Input: s = "ABAB", k = 2 Output: 4
Input: s = "AABABBA", k = 1 Output: 4

Constraints

  • 1 <= s.length <= 10^5
  • 0 <= k <= s.length
  • s consists of uppercase English letters.

Approach Overview

Brute Force

Java: Try every window, count majority

Python: All windows

O(n² · |Σ|) O(|Σ|)

Optimal #1

Java: Sliding window with running maxFreq (correct but maxFreq drifts)

Python: Sliding window with running maxFreq

O(n) O(|Σ|)

Optimal #2

Java: 26 outer loops — fix the target letter

Python: 26 windows, one per target letter

O(26 · n) O(1)

Java Solutions

Try every window, count majority O(n² · |Σ|) O(|Σ|)

Examine all (l, r); window valid iff (r-l+1) - max_freq ≤ k. O(n²·26).

Pseudo-code

for each (l,r): build counts; if (r-l+1)-max(counts) ≤ k: track size

Complexity

Time: O(n² · |Σ|) — Quadratic windows × constant alphabet.
Space: O(|Σ|) — Counts.

Edge cases & gotchas

  • TLEs at n=10^5.

Java code

class Solution {
    public int characterReplacement(final String s, final int k) {
        int best = 0;
        for (int l = 0; l < s.length(); l++) {
            final int[] c = new int[26];
            int max = 0;
            for (int r = l; r < s.length(); r++) {
                c[s.charAt(r) - 'A']++;
                max = Math.max(max, c[s.charAt(r) - 'A']);
                if ((r - l + 1) - max <= k) best = Math.max(best, r - l + 1);
            }
        }
        return best;
    }
}
Sliding window with running maxFreq (correct but maxFreq drifts) O(n) O(|Σ|)

Track per-window character counts and a maxFreq. While (windowLen - maxFreq) > k, advance l. The trick: maxFreq doesn't need to be exact when shrinking — best answer is monotone in maxFreq.

Pseudo-code

counts = int[26]; l = 0; maxFreq = 0; best = 0
for r in 0..n-1:
    counts[s[r]] += 1
    maxFreq = max(maxFreq, counts[s[r]])
    while (r - l + 1) - maxFreq > k:
        counts[s[l]] -= 1
        l += 1
    best = max(best, r - l + 1)

Complexity

Time: O(n) — Each char enters and leaves once.
Space: O(|Σ|) — Counts.

Edge cases & gotchas

  • Subtle: maxFreq is NOT recomputed when shrinking. We can leave it stale because best is monotonically tied to the largest valid window ever seen — a stale maxFreq just means we don't shrink past the best window's bound.
  • If we shrank by recomputing max, we'd waste O(26) per shrink; the stale-maxFreq trick keeps it O(1).
  • k = 0 → effectively 'longest run of identical characters' — the algorithm handles this directly.

Java code

class Solution {
    public int characterReplacement(final String s, final int k) {
        final int[] counts = new int[26];
        int l = 0, maxFreq = 0, best = 0;
        for (int r = 0; r < s.length(); r++) {
            counts[s.charAt(r) - 'A']++;
            if (counts[s.charAt(r) - 'A'] > maxFreq) maxFreq = counts[s.charAt(r) - 'A'];
            // Note: maxFreq is intentionally not refreshed when shrinking — see comment block.
            while ((r - l + 1) - maxFreq > k) {
                counts[s.charAt(l) - 'A']--;
                l++;
            }
            best = Math.max(best, r - l + 1);
        }
        return best;
    }

    public static void main(String[] args) {
        System.out.println(new Solution().characterReplacement("AABABBA", 1));   // 4
    }
}
26 outer loops — fix the target letter O(26 · n) O(1)

For each of 26 target letters, sliding-window with 'replacements used' = (window length - count[target]) ≤ k.

Pseudo-code

for each letter T in 'A'..'Z':
    l = 0; cnt = 0; best = max(best, ...)
    for r in 0..n-1:
        if s[r] == T: cnt += 1
        while (r - l + 1) - cnt > k:
            if s[l] == T: cnt -= 1
            l += 1
        best = max(best, r - l + 1)

Complexity

Time: O(26 · n) — 26 outer × n inner.
Space: O(1) — Scalars.

Edge cases & gotchas

  • Conceptually clearer than the maxFreq trick — runs 26 independent windows.
  • Same asymptote (treating |Σ| = 26 as constant), worse constant factor.
  • Use this approach if you can't sell the stale-maxFreq invariant in the interview.

Java code

class Solution {
    public int characterReplacement(final String s, final int k) {
        int best = 0;
        for (char T = 'A'; T <= 'Z'; T++) {
            int l = 0, cnt = 0;
            for (int r = 0; r < s.length(); r++) {
                if (s.charAt(r) == T) cnt++;
                while ((r - l + 1) - cnt > k) {
                    if (s.charAt(l) == T) cnt--;
                    l++;
                }
                best = Math.max(best, r - l + 1);
            }
        }
        return best;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²·26)O(26n)
SpaceO(26)O(1)
Difficulty2/53/5 — straightforward
Constant factorWorst26× the inner loop
ProvabilityTrivialTrivial
GeneralizesNoStandard window over each label
When to useTiny nWhen you can't articulate the maxFreq invariant live
PE VerdictShip the maxFreq sliding window — it's textbook for this problem and the basis of all 'longest window with at most k replacements' problems. Memorize the stale-maxFreq invariant: best is monotone in maxFreq, so we never miss a larger answer by failing to refresh.

Python Solutions

All windows O(n²·26) O(26)

Quadratic baseline.

Pseudo-code

for each (l, r): check if window valid

Complexity

Time: O(n²·26) — Quadratic.
Space: O(26) — Counter.

Python code

from collections import Counter


class Solution:
    def characterReplacement(self, s: str, k: int) -> int:
        best = 0
        for l in range(len(s)):
            c = Counter()
            for r in range(l, len(s)):
                c[s[r]] += 1
                if (r - l + 1) - max(c.values()) <= k:
                    best = max(best, r - l + 1)
        return best
Sliding window with running maxFreq O(n) O(26)

Same as Java optimal #1.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(26) — Counter.

Edge cases & gotchas

  • Stale-maxFreq trick is the entire learning.

Python code

from collections import defaultdict


class Solution:
    def characterReplacement(self, s: str, k: int) -> int:
        """
        Sliding window. Key invariant: window is valid iff
            (windowLen - maxFreq) <= k
        where maxFreq is the count of the most-frequent char in the window.
        We DON'T refresh maxFreq when shrinking — best is monotone in
        maxFreq, so a stale value just keeps us from shrinking past the
        best window we've seen.
        """
        counts = defaultdict(int)
        l = max_freq = best = 0
        for r, c in enumerate(s):
            counts[c] += 1
            if counts[c] > max_freq:
                max_freq = counts[c]
            while (r - l + 1) - max_freq > k:
                counts[s[l]] -= 1
                l += 1
            if r - l + 1 > best:
                best = r - l + 1
        return best


if __name__ == "__main__":
    print(Solution().characterReplacement("AABABBA", 1))   # 4
26 windows, one per target letter O(26·n) O(1)

Same as Java optimal #2.

Pseudo-code

26 outer loops; per-letter sliding window

Complexity

Time: O(26·n) — Linear.
Space: O(1) — Scalars.

Edge cases & gotchas

  • Conceptually simpler; safer fallback.

Python code

from string import ascii_uppercase


class Solution:
    def characterReplacement(self, s: str, k: int) -> int:
        best = 0
        for T in ascii_uppercase:
            l = cnt = 0
            for r, c in enumerate(s):
                if c == T:
                    cnt += 1
                while (r - l + 1) - cnt > k:
                    if s[l] == T:
                        cnt -= 1
                    l += 1
                if r - l + 1 > best:
                    best = r - l + 1
        return best

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²·26)O(26n)
SpaceO(26)O(1)
Difficulty2/53/5
Constant factorWorst26× constant
ProvabilityTrivialTrivial
When to useDemoFallback
PE VerdictShip the maxFreq window. The 26-letter outer loop is a fine, safe alternative when the invariant doesn't click for you — it's the same asymptote and only ~26× slower constant.

What the interviewer is really testing

This is the gnarliest classic sliding window because the validity check involves a running max that changes as the window mutates. PE signal: you state the (windowLen - maxFreq) ≤ k invariant first, then you defend the stale-maxFreq trick with the monotonicity argument: 'best can only grow when maxFreq does, so a stale maxFreq doesn't lose us the optimum'.

Top gotchas

  • DO NOT refresh maxFreq when shrinking — it's both unnecessary (the invariant still holds for best-tracking) and would cost O(26) per shrink.
  • The shrinking is `while`, not `if` — multiple chars may need to leave on a single r-step.
  • Window length is r - l + 1 (inclusive both ends). Forget the +1 and you're off by one consistently.
  • Replace HashMap with int[26] for the lowercase-only contract — ~5× speedup.

Ship-it

maxFreq sliding window in both languages. State the stale-maxFreq invariant out loud.

#18 LC 567 Medium Permutation in String

Sliding-window of fixed length with anagram-equality check.

Problem Statement

Given two strings s1 and s2, return true if s2 contains a permutation of s1 as a substring.

Signature: boolean checkInclusion(String s1, String s2) / def check_inclusion(s1: str, s2: str) -> bool

Examples

Input: s1 = "ab", s2 = "eidbaooo" Output: true ("ba")
Input: s1 = "ab", s2 = "eidboaoo" Output: false

Constraints

  • 1 <= s1.length, s2.length <= 10^4
  • s1, s2 consist of lowercase English letters.

Approach Overview

Brute Force

Java: Each window: rebuild and compare

Python: Window rebuild + Counter equality

O((n - m) · m) O(26)

Optimal #1

Java: Sliding window with two count arrays + Arrays.equals

Python: Sliding window with Counter and add/remove

O(n · 26) O(26)

Optimal #2

Java: Sliding window with running 'matches' counter (O(n))

Python: Matches-counter window

O(n + m + 26) O(26)

Java Solutions

Each window: rebuild and compare O((n - m) · m) O(26)

For each starting index in s2 of length |s1|, count chars and compare to s1's count.

Pseudo-code

for each window of length m: build counts; compare with s1 counts

Complexity

Time: O((n - m) · m) — Window count built fresh each step.
Space: O(26) — Count arrays.

Edge cases & gotchas

  • TLEs at n=10^4 if m is large.

Java code

class Solution {
    public boolean checkInclusion(final String s1, final String s2) {
        if (s1.length() > s2.length()) return false;
        final int[] target = new int[26];
        for (final char c : s1.toCharArray()) target[c - 'a']++;
        for (int i = 0; i + s1.length() <= s2.length(); i++) {
            final int[] win = new int[26];
            for (int j = 0; j < s1.length(); j++) win[s2.charAt(i + j) - 'a']++;
            if (java.util.Arrays.equals(target, win)) return true;
        }
        return false;
    }
}
Sliding window with two count arrays + Arrays.equals O(n · 26) O(26)

Slide a fixed-size window; add char on right, remove on left. Compare count arrays at each step.

Pseudo-code

build target counts from s1; build first-window counts from s2[0..m-1]
for r in m..n-1:
    add s2[r] to win; remove s2[r-m] from win
    if win == target: return true

Complexity

Time: O(n · 26) — n window steps × 26-element compare.
Space: O(26) — Two int[26].

Edge cases & gotchas

  • Pre-check |s1| > |s2| → false (otherwise the loop does nothing odd, but bail explicitly).
  • Compare Arrays.equals at the END of each step, after the slide.

Java code

import java.util.Arrays;

class Solution {
    public boolean checkInclusion(final String s1, final String s2) {
        final int m = s1.length(), n = s2.length();
        if (m > n) return false;
        final int[] target = new int[26];
        final int[] win = new int[26];
        for (int i = 0; i < m; i++) {
            target[s1.charAt(i) - 'a']++;
            win[s2.charAt(i) - 'a']++;
        }
        if (Arrays.equals(target, win)) return true;
        for (int r = m; r < n; r++) {
            win[s2.charAt(r) - 'a']++;
            win[s2.charAt(r - m) - 'a']--;
            if (Arrays.equals(target, win)) return true;
        }
        return false;
    }
}
Sliding window with running 'matches' counter (O(n)) O(n + m + 26) O(26)

Track how many of the 26 buckets currently match between window and target. Only the two changing buckets per step can affect the count — O(1) per slide.

Pseudo-code

matches = number of buckets where win[c] == target[c]
on each slide: update both affected buckets and matches
if matches == 26: return true

Complexity

Time: O(n + m + 26) — Each step does O(1) work after the initial setup.
Space: O(26) — Two count arrays + matches counter.

Edge cases & gotchas

  • Decrement matches BEFORE updating the bucket if it was matching; increment AFTER if it now matches. Easy to bug.
  • Strictly faster than the Arrays.equals variant for large n.

Java code

class Solution {
    public boolean checkInclusion(final String s1, final String s2) {
        final int m = s1.length(), n = s2.length();
        if (m > n) return false;
        final int[] target = new int[26];
        final int[] win = new int[26];
        for (int i = 0; i < m; i++) {
            target[s1.charAt(i) - 'a']++;
            win[s2.charAt(i) - 'a']++;
        }
        int matches = 0;
        for (int i = 0; i < 26; i++) if (win[i] == target[i]) matches++;
        if (matches == 26) return true;
        for (int r = m; r < n; r++) {
            final int add = s2.charAt(r) - 'a';
            final int rem = s2.charAt(r - m) - 'a';
            // add char
            if (win[add] == target[add]) matches--;
            win[add]++;
            if (win[add] == target[add]) matches++;
            // remove char
            if (win[rem] == target[rem]) matches--;
            win[rem]--;
            if (win[rem] == target[rem]) matches++;
            if (matches == 26) return true;
        }
        return false;
    }

    public static void main(String[] args) {
        System.out.println(new Solution().checkInclusion("ab", "eidbaooo"));   // true
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(nm)O(n)
SpaceO(26)O(26)
Difficulty1/53/5
Constant factorWorstBest — O(1) per step
When to useTiny nProfiled hot path; the 26 factor matters
PE VerdictShip the simple sliding window with Arrays.equals — at LeetCode scale (n=10^4, |Σ|=26) it's plenty fast and trivial to write correctly. The matches-counter variant is the right answer if interviewer asks 'can you make this strictly O(n)?'

Python Solutions

Window rebuild + Counter equality O((n-m)·m) O(26)

Counter(s2[i:i+m]) == Counter(s1) for each i. Quadratic-ish.

Pseudo-code

for each window: Counter equality check

Complexity

Time: O((n-m)·m) — Per-window Counter build.
Space: O(26) — Counters.

Python code

from collections import Counter


class Solution:
    def checkInclusion(self, s1: str, s2: str) -> bool:
        m, n = len(s1), len(s2)
        if m > n:
            return False
        t = Counter(s1)
        for i in range(n - m + 1):
            if Counter(s2[i : i + m]) == t:
                return True
        return False
Sliding window with Counter and add/remove O(n) O(26)

Pythonic: maintain a Counter, add/remove on slide, compare with target.

Pseudo-code

init w; for r in m..n: w[s2[r]] += 1; w[s2[r-m]] -= 1; if w == target: return

Complexity

Time: O(n) — Linear.
Space: O(26) — Counters.

Edge cases & gotchas

  • Counter equality treats missing keys as 0 — but we still need to clean up keys that hit zero, otherwise comparison may surprisingly differ. +c in Counter handles this if you cast.
  • For correctness, delete keys with zero count or cast both sides to dict before equality.

Python code

from collections import Counter


class Solution:
    def checkInclusion(self, s1: str, s2: str) -> bool:
        m, n = len(s1), len(s2)
        if m > n:
            return False
        target = Counter(s1)
        window = Counter(s2[:m])
        if window == target:
            return True
        for r in range(m, n):
            window[s2[r]] += 1
            window[s2[r - m]] -= 1
            if window[s2[r - m]] == 0:
                del window[s2[r - m]]    # keep equality semantics clean
            if window == target:
                return True
        return False


if __name__ == "__main__":
    print(Solution().checkInclusion("ab", "eidbaooo"))   # True
Matches-counter window O(n) O(26)

Same idea as Java optimal #2 — maintain matches counter for O(1) per step.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear, O(1) per step.
Space: O(26) — Two arrays.

Python code

class Solution:
    def checkInclusion(self, s1: str, s2: str) -> bool:
        m, n = len(s1), len(s2)
        if m > n:
            return False
        target = [0] * 26
        win = [0] * 26
        for i in range(m):
            target[ord(s1[i]) - 97] += 1
            win[ord(s2[i]) - 97] += 1
        matches = sum(1 for i in range(26) if win[i] == target[i])
        if matches == 26:
            return True
        for r in range(m, n):
            add = ord(s2[r]) - 97
            rem = ord(s2[r - m]) - 97
            if win[add] == target[add]:
                matches -= 1
            win[add] += 1
            if win[add] == target[add]:
                matches += 1
            if win[rem] == target[rem]:
                matches -= 1
            win[rem] -= 1
            if win[rem] == target[rem]:
                matches += 1
            if matches == 26:
                return True
        return False

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(nm)O(n) tightest const
SpaceO(26)O(26)
Difficulty1/53/5
Constant factorWorstPure Python list ops — slightly slower than Counter
When to useDemoWhen you specifically need O(n) without the |Σ| factor
PE VerdictCounter-based sliding window — Pythonic and fast enough. The matches-counter version is the right answer when the interviewer asks for true O(n).

What the interviewer is really testing

Fixed-window-size template. PE signal: you fuse the 'fixed window' and 'anagram check' cleanly, you handle the |s1| > |s2| short-circuit, and you can articulate the matches-counter optimization that brings it to true O(n).

Top gotchas

  • Counter equality in Python treats zero-count keys as missing — but only after you `del` them, otherwise some equality paths surprise. Cleanest: del keys when they hit zero.
  • Window length is fixed at |s1|, not variable — different from the previous sliding-window problems.
  • Update both add and remove sides in the SAME step before checking — otherwise window length is wrong for one branch.
  • Compare window to target at the END of each slide, not before.

Ship-it

Java: int[26] sliding window with Arrays.equals. Python: Counter-based with del-on-zero hygiene.

#19 LC 76 Hard Minimum Window Substring

Variable-window with 'have/need' counters — the canonical 'shortest covering window' problem.

Problem Statement

Given strings s and t, return the minimum window substring of s that contains every character of t (including multiplicity). If none exists, return the empty string.

Signature: String minWindow(String s, String t) / def min_window(s: str, t: str) -> str

Examples

Input: s = "ADOBECODEBANC", t = "ABC" Output: "BANC"
Input: s = "a", t = "aa" Output: ""

Constraints

  • 1 <= s.length, t.length <= 10^5
  • s, t consist of uppercase and lowercase letters.

Approach Overview

Brute Force

Java: All windows, validate each

Python: All windows + Counter check

O(n² · |Σ|) O(|Σ|)

Optimal #1

Java: Sliding window with have/need + formed counter

Python: Sliding window with have/need + formed

O(n + m) O(|Σ|)

Optimal #2

Java: Filtered string + sliding window

Python: Filtered-string variant

O(n + m + n_f²) where n_f is filtered length O(n_f)

Java Solutions

All windows, validate each O(n² · |Σ|) O(|Σ|)

Try every (l, r); check coverage.

Pseudo-code

for each (l,r): if window covers t: track shortest

Complexity

Time: O(n² · |Σ|) — Quadratic windows.
Space: O(|Σ|) — Counts.

Edge cases & gotchas

  • TLEs at n=10^5.

Java code

import java.util.*;
class Solution {
    public String minWindow(final String s, final String t) {
        if (t.length() > s.length()) return "";
        final int[] need = new int[128];
        for (final char c : t.toCharArray()) need[c]++;
        int bestL = -1, bestLen = Integer.MAX_VALUE;
        for (int l = 0; l < s.length(); l++) {
            final int[] have = new int[128];
            for (int r = l; r < s.length(); r++) {
                have[s.charAt(r)]++;
                if (covers(have, need) && r - l + 1 < bestLen) { bestLen = r - l + 1; bestL = l; break; }
            }
        }
        return bestL == -1 ? "" : s.substring(bestL, bestL + bestLen);
    }
    private boolean covers(final int[] have, final int[] need) {
        for (int i = 0; i < 128; i++) if (have[i] < need[i]) return false;
        return true;
    }
}
Sliding window with have/need + formed counter O(n + m) O(|Σ|)

Track how many chars in t are 'satisfied' (have[c] >= need[c]). When formed == required, try to shrink l.

Pseudo-code

need = count of t chars; required = number of distinct chars in t
have = empty count; formed = 0; l = 0; best = (∞, 0, 0)
for r in 0..n-1:
    have[s[r]] += 1
    if have[s[r]] == need[s[r]]: formed += 1
    while formed == required:
        if r-l+1 < best.len: best = (r-l+1, l, r)
        have[s[l]] -= 1
        if have[s[l]] < need[s[l]]: formed -= 1
        l += 1
return s[best.l : best.r+1] or ''

Complexity

Time: O(n + m) — Each char added once, removed at most once.
Space: O(|Σ|) — Two counts.

Edge cases & gotchas

  • `required` = number of distinct chars in t (NOT len(t)) — multiplicities are encoded in need[c].
  • Decrement formed BEFORE incrementing l so the check fires at the right moment.
  • Empty result when no covering window exists — initialize bestLen = +∞ as sentinel.

Java code

import java.util.*;

class Solution {
    /**
     * Sliding window with 'formed' counter tracking how many distinct chars
     * have hit their required count.
     */
    public String minWindow(final String s, final String t) {
        if (t.length() > s.length()) return "";
        final int[] need = new int[128];
        int required = 0;
        for (final char c : t.toCharArray()) { if (need[c]++ == 0) required++; }
        final int[] have = new int[128];
        int formed = 0, l = 0, bestL = 0, bestLen = Integer.MAX_VALUE;
        for (int r = 0; r < s.length(); r++) {
            final char cr = s.charAt(r);
            have[cr]++;
            if (have[cr] == need[cr]) formed++;
            while (formed == required) {
                if (r - l + 1 < bestLen) { bestLen = r - l + 1; bestL = l; }
                final char cl = s.charAt(l);
                have[cl]--;
                if (have[cl] < need[cl]) formed--;
                l++;
            }
        }
        return bestLen == Integer.MAX_VALUE ? "" : s.substring(bestL, bestL + bestLen);
    }

    public static void main(String[] args) {
        System.out.println(new Solution().minWindow("ADOBECODEBANC", "ABC"));   // BANC
    }
}
Filtered string + sliding window O(n + m + n_f²) where n_f is filtered length O(n_f)

Pre-extract only positions in s where the char appears in t. Slide over the filtered list. Wins on inputs where s is mostly noise.

Pseudo-code

filtered = [(i, c) for i, c in enumerate(s) if c in t]
run sliding window over filtered; map back to s indices

Complexity

Time: O(n + m + n_f²) where n_f is filtered length — When n_f << n, dramatically faster than O(n).
Space: O(n_f) — Filtered list.

Edge cases & gotchas

  • Real win only when t is small relative to |Σ| AND s contains many irrelevant chars.
  • More code; only worth it if the inputs justify it.

Java code

import java.util.*;

class Solution {
    public String minWindow(final String s, final String t) {
        if (t.length() > s.length()) return "";
        final int[] need = new int[128];
        int required = 0;
        for (final char c : t.toCharArray()) { if (need[c]++ == 0) required++; }
        // Build filtered: only positions whose char is in t
        final List<int[]> filtered = new ArrayList<>();
        for (int i = 0; i < s.length(); i++) if (need[s.charAt(i)] > 0) filtered.add(new int[]{i, s.charAt(i)});
        final int[] have = new int[128];
        int formed = 0, l = 0, bestL = 0, bestR = -1, bestLen = Integer.MAX_VALUE;
        for (int r = 0; r < filtered.size(); r++) {
            final int cr = filtered.get(r)[1];
            have[cr]++;
            if (have[cr] == need[cr]) formed++;
            while (formed == required) {
                final int sIdx = filtered.get(l)[0], eIdx = filtered.get(r)[0];
                if (eIdx - sIdx + 1 < bestLen) { bestLen = eIdx - sIdx + 1; bestL = sIdx; bestR = eIdx; }
                final int cl = filtered.get(l)[1];
                have[cl]--;
                if (have[cl] < need[cl]) formed--;
                l++;
            }
        }
        return bestR == -1 ? "" : s.substring(bestL, bestR + 1);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n + n_f) where n_f ≤ n
SpaceO(|Σ|)O(n_f)
Difficulty2/54/5
Constant factorWorstWins when n_f ≪ n
When to useTiny nWhen s is mostly noise
PE VerdictShip the formed-counter sliding window. It's O(n+m), zero allocation hot path, and the template generalizes to every 'shortest covering window' variant. Filtered-string optimization only matters when s is mostly irrelevant chars — niche.

Python Solutions

All windows + Counter check O(n²·|Σ|) O(|Σ|)

Cubic-ish baseline.

Pseudo-code

nested loops + Counter coverage check

Complexity

Time: O(n²·|Σ|) — Quadratic.
Space: O(|Σ|) — Counter per window.

Python code

from collections import Counter


class Solution:
    def minWindow(self, s: str, t: str) -> str:
        if len(t) > len(s):
            return ""
        need = Counter(t)
        best = ""
        for l in range(len(s)):
            have = Counter()
            for r in range(l, len(s)):
                have[s[r]] += 1
                if all(have[c] >= need[c] for c in need):
                    if not best or r - l + 1 < len(best):
                        best = s[l : r + 1]
                    break
        return best
Sliding window with have/need + formed O(n+m) O(|Σ|)

Same algorithm as Java optimal #1.

Pseudo-code

see Java

Complexity

Time: O(n+m) — Linear.
Space: O(|Σ|) — Counters.

Edge cases & gotchas

  • Use Counter for need; defaultdict(int) for have.

Python code

from collections import Counter, defaultdict


class Solution:
    def minWindow(self, s: str, t: str) -> str:
        if len(t) > len(s):
            return ""
        need = Counter(t)
        required = len(need)
        have = defaultdict(int)
        formed = 0
        l = 0
        best_l, best_len = 0, float('inf')
        for r, c in enumerate(s):
            have[c] += 1
            if c in need and have[c] == need[c]:
                formed += 1
            while formed == required:
                if r - l + 1 < best_len:
                    best_len = r - l + 1
                    best_l = l
                lc = s[l]
                have[lc] -= 1
                if lc in need and have[lc] < need[lc]:
                    formed -= 1
                l += 1
        return "" if best_len == float('inf') else s[best_l : best_l + best_len]


if __name__ == "__main__":
    print(Solution().minWindow("ADOBECODEBANC", "ABC"))   # BANC
    print(Solution().minWindow("a", "aa"))                # ""
Filtered-string variant O(n + n_f) O(n_f)

Same idea as Java optimal #2.

Pseudo-code

filter then sliding window

Complexity

Time: O(n + n_f) — Wins when s is sparse in target chars.
Space: O(n_f) — Filtered list.

Edge cases & gotchas

  • Niche optimization.

Python code

from collections import Counter, defaultdict
from typing import List, Tuple


class Solution:
    def minWindow(self, s: str, t: str) -> str:
        if len(t) > len(s):
            return ""
        need = Counter(t)
        required = len(need)
        filtered: List[Tuple[int, str]] = [(i, c) for i, c in enumerate(s) if c in need]
        have = defaultdict(int)
        formed = 0
        l = 0
        best = (-1, -1)
        best_len = float('inf')
        for r, (i_r, c_r) in enumerate(filtered):
            have[c_r] += 1
            if have[c_r] == need[c_r]:
                formed += 1
            while formed == required:
                i_l, c_l = filtered[l]
                if i_r - i_l + 1 < best_len:
                    best_len = i_r - i_l + 1
                    best = (i_l, i_r)
                have[c_l] -= 1
                if have[c_l] < need[c_l]:
                    formed -= 1
                l += 1
        return "" if best == (-1, -1) else s[best[0] : best[1] + 1]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n + n_f)
SpaceO(|Σ|)O(n_f)
Difficulty2/54/5
Constant factorWorstNiche win
When to useDemoSparse target
PE VerdictSame as Java — formed-counter sliding window. The filtered variant is a real optimization for niche inputs.

What the interviewer is really testing

This problem is the apex of variable-size sliding-window questions. PE signal: you propose the 'formed' counter as the right abstraction (counts how many distinct target chars are saturated), you correctly differentiate `required` (distinct count) from |t| (total count), and you write the shrink-loop carefully with the decrement-formed-on-edge logic.

Top gotchas

  • `required` = number of DISTINCT chars in t, not len(t). Multiplicities are encoded in need[c].
  • Decrement `formed` only when have[c] DROPS BELOW need[c] (not just on every removal).
  • Increment `formed` only when have[c] HITS need[c] exactly (not when it exceeds).
  • Update best length BEFORE shrinking, otherwise you miss the current valid window.

Ship-it

Sliding window with have/need + formed counter. Both languages.

#20 LC 239 Hard Sliding Window Maximum

Monotonic deque — the canonical 'window max in O(n)' technique.

Problem Statement

Given an array nums and an integer k, return an array of the maximum value within each contiguous window of size k as the window slides from left to right.

Signature: int[] maxSlidingWindow(int[] nums, int k) / def max_sliding_window(nums: List[int], k: int) -> List[int]

Examples

Input: nums = [1,3,-1,-3,5,3,6,7], k = 3 Output: [3,3,5,5,6,7]
Input: nums = [1], k = 1 Output: [1]

Constraints

  • 1 <= nums.length <= 10^5
  • -10^4 <= nums[i] <= 10^4
  • 1 <= k <= nums.length

Approach Overview

Brute Force

Java: Per-window linear max

Python: Per-window max

O(n·k) O(1)

Optimal #1

Java: Monotonic deque (decreasing, by index)

Python: Monotonic deque

O(n) O(k)

Optimal #2

Java: Block decomposition with prefix/suffix maxes

Python: Block decomposition

O(n) O(n)

Java Solutions

Per-window linear max O(n·k) O(1)

For each window, scan k elements for the max.

Pseudo-code

for each window of length k: take max

Complexity

Time: O(n·k) — n windows × k each.
Space: O(1) — Output only.

Edge cases & gotchas

  • TLEs at n=10^5, k=10^4.

Java code

class Solution {
    public int[] maxSlidingWindow(final int[] nums, final int k) {
        final int n = nums.length;
        final int[] out = new int[n - k + 1];
        for (int i = 0; i + k <= n; i++) {
            int m = nums[i];
            for (int j = i + 1; j < i + k; j++) m = Math.max(m, nums[j]);
            out[i] = m;
        }
        return out;
    }
}
Monotonic deque (decreasing, by index) O(n) O(k)

Deque holds candidate indices in decreasing nums-value order. Front is the current window's max. Each index pushed/popped at most once.

Pseudo-code

dq = empty deque of indices
for i in 0..n-1:
    while dq not empty and dq.front <= i - k: dq.pollFirst()   # evict out-of-window
    while dq not empty and nums[dq.back] <= nums[i]: dq.pollLast()   # maintain decreasing
    dq.offerLast(i)
    if i >= k - 1: out[i - k + 1] = nums[dq.front]

Complexity

Time: O(n) — Each index amortizes O(1).
Space: O(k) — Deque ≤ k.

Edge cases & gotchas

  • Use ArrayDeque (Java) — never legacy Stack/Queue. Deque<Integer> with offerLast/pollLast/pollFirst.
  • Front is the current max INDEX. Compare nums[front] for the value.
  • Off-by-one: emit only once i >= k - 1.

Java code

import java.util.ArrayDeque;
import java.util.Deque;

class Solution {
    /**
     * Monotonic decreasing deque of indices. Each index amortizes O(1):
     * pushed once, popped at most once.
     */
    public int[] maxSlidingWindow(final int[] nums, final int k) {
        final int n = nums.length;
        final int[] out = new int[n - k + 1];
        final Deque<Integer> dq = new ArrayDeque<>();
        for (int i = 0; i < n; i++) {
            while (!dq.isEmpty() && dq.peekFirst() <= i - k) dq.pollFirst();
            while (!dq.isEmpty() && nums[dq.peekLast()] <= nums[i]) dq.pollLast();
            dq.offerLast(i);
            if (i >= k - 1) out[i - k + 1] = nums[dq.peekFirst()];
        }
        return out;
    }

    public static void main(String[] args) {
        System.out.println(java.util.Arrays.toString(
            new Solution().maxSlidingWindow(new int[]{1,3,-1,-3,5,3,6,7}, 3)));   // [3,3,5,5,6,7]
    }
}
Block decomposition with prefix/suffix maxes O(n) O(n)

Partition into blocks of size k. Compute leftMax[i] (block prefix) and rightMax[i] (block suffix). Window max = max(rightMax[i], leftMax[i+k-1]).

Pseudo-code

split nums into blocks of size k
leftMax[i] = block-prefix max ending at i
rightMax[i] = block-suffix max starting at i
for window [i, i+k-1]: out[i] = max(rightMax[i], leftMax[i+k-1])

Complexity

Time: O(n) — Three linear passes.
Space: O(n) — Two aux arrays.

Edge cases & gotchas

  • Same asymptote, different memory pattern; useful when the deque approach is hard to articulate.
  • Block boundary is i % k == 0 — easy to bug.
  • Slightly more work but no deque structure to maintain.

Java code

class Solution {
    public int[] maxSlidingWindow(final int[] nums, final int k) {
        final int n = nums.length;
        final int[] left = new int[n];
        final int[] right = new int[n];
        for (int i = 0; i < n; i++) left[i] = (i % k == 0) ? nums[i] : Math.max(left[i-1], nums[i]);
        for (int i = n - 1; i >= 0; i--) right[i] = (i == n - 1 || (i + 1) % k == 0) ? nums[i] : Math.max(right[i+1], nums[i]);
        final int[] out = new int[n - k + 1];
        for (int i = 0; i + k <= n; i++) out[i] = Math.max(right[i], left[i + k - 1]);
        return out;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(nk)O(n)
SpaceO(1)O(n)
Difficulty1/53/5 — block boundaries
Constant factorWorst3 passes; cache-friendly
Streaming?TrivialNo — needs entire array up front
When to usek tinyWhiteboard if deque proof fails to land
PE VerdictShip the monotonic deque. It's the textbook answer; once you've internalized the invariant ('deque holds indices in strictly decreasing nums-value order') it writes itself. Block decomposition is a fine alternative answer if the deque proof escapes you under pressure.

Python Solutions

Per-window max O(n·k) O(1)

Quadratic-ish.

Pseudo-code

[max(nums[i:i+k]) for i in range(n-k+1)]

Complexity

Time: O(n·k) — n × k.
Space: O(1) — Output.

Python code

from typing import List


class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        return [max(nums[i : i + k]) for i in range(len(nums) - k + 1)]
Monotonic deque O(n) O(k)

Same algorithm as Java optimal #1.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear amortized.
Space: O(k) — Deque.

Edge cases & gotchas

  • collections.deque has O(1) append/popleft — use it, not list.

Python code

from typing import List
from collections import deque


class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        """Monotonic decreasing deque of indices."""
        dq = deque()
        out = []
        for i, x in enumerate(nums):
            while dq and dq[0] <= i - k:
                dq.popleft()
            while dq and nums[dq[-1]] <= x:
                dq.pop()
            dq.append(i)
            if i >= k - 1:
                out.append(nums[dq[0]])
        return out


if __name__ == "__main__":
    print(Solution().maxSlidingWindow([1,3,-1,-3,5,3,6,7], 3))   # [3,3,5,5,6,7]
Block decomposition O(n) O(n)

Same as Java optimal #2.

Pseudo-code

see Java

Complexity

Time: O(n) — Three passes.
Space: O(n) — Two arrays.

Python code

from typing import List


class Solution:
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        n = len(nums)
        left = [0] * n
        right = [0] * n
        for i in range(n):
            left[i] = nums[i] if i % k == 0 else max(left[i-1], nums[i])
        for i in range(n - 1, -1, -1):
            right[i] = nums[i] if i == n - 1 or (i + 1) % k == 0 else max(right[i+1], nums[i])
        return [max(right[i], left[i + k - 1]) for i in range(n - k + 1)]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(nk)O(n)
SpaceO(1)O(n)
Difficulty1/53/5
Constant factorWorstThree passes
When to useTiny kFallback
PE VerdictMonotonic deque, same as Java. collections.deque's append and popleft are both O(1) — use it.

What the interviewer is really testing

Monotonic-deque is the entire learning. PE signal: you propose the deque immediately, you state the invariant ('deque holds indices, nums-values strictly decreasing'), and you write the two while-loops in the right order (evict-out-of-window, then evict-smaller). Bonus: you mention this is the same shape as 'next greater element' problems — generalizable.

Top gotchas

  • Order matters: evict-out-of-window FIRST, then evict-smaller. Reversing risks evicting an element you're about to need.
  • Comparing nums[dq[-1]] <= x (not <) keeps the deque strictly decreasing — duplicates evict, which is fine since indices are still distinct.
  • Emit output only after i >= k - 1 — first complete window.
  • Use ArrayDeque in Java, not LinkedList — both implement Deque but ArrayDeque is faster, no nulls.

Ship-it

Monotonic decreasing deque, both languages. State the invariant out loud.

Stack

#21 LC 20 Easy Valid Parentheses

Stack-based bracket matcher — the foundational stack problem.

Problem Statement

Given a string s containing only the characters (, ), {, }, [, ], determine whether the brackets are properly opened and closed in order. Every open bracket must be closed by the same type of bracket and in the correct order.

Signature: boolean isValid(String s) / def is_valid(s: str) -> bool

Examples

Input: s = "()[]{}" Output: true
Input: s = "(]" Output: false
Input: s = "([)]" Output: false
Input: s = "{[]}" Output: true

Constraints

  • 1 <= s.length <= 10^4
  • s consists of parentheses only (above six characters).

Approach Overview

Brute Force

Java: Repeated 'remove inner pair' until stable

Python: Repeated string replace

O(n²) O(n)

Optimal #1

Java: Stack of opens

Python: Stack with dict<close, open>

O(n) O(n)

Optimal #2

Java: Stack with map<close, open>

Python: Stack pushing the EXPECTED close on each open

O(n) O(n)

Java Solutions

Repeated 'remove inner pair' until stable O(n²) O(n)

Loop replacing '()', '[]', '{}' with '' until no changes. Quadratic.

Pseudo-code

while s contains a pair: remove it
return s.empty()

Complexity

Time: O(n²) — Each pass is O(n); up to n/2 passes.
Space: O(n) — String rebuild.

Edge cases & gotchas

  • Useful only as a teaching foil — quadratic and string-replacement-heavy.

Java code

class Solution {
    public boolean isValid(String s) {
        int len = -1;
        while (s.length() != len) {
            len = s.length();
            s = s.replace("()", "").replace("[]", "").replace("{}", "");
        }
        return s.isEmpty();
    }
}
Stack of opens O(n) O(n)

Push opens onto a stack; on close, pop and verify it matches.

Pseudo-code

stack = empty
for c in s:
    if c is open: stack.push(c)
    else: if stack empty or top != matching open: return false; stack.pop()
return stack.empty()

Complexity

Time: O(n) — Each char one push or one pop+compare.
Space: O(n) — Stack up to n.

Edge cases & gotchas

  • Closing bracket with empty stack → false (e.g., starts with ')').
  • Stack non-empty at the end → false (unclosed opens).
  • Use ArrayDeque, never legacy Stack.

Java code

import java.util.ArrayDeque;
import java.util.Deque;

class Solution {
    public boolean isValid(final String s) {
        final Deque<Character> stack = new ArrayDeque<>(s.length() / 2 + 1);
        for (int i = 0; i < s.length(); i++) {
            final char c = s.charAt(i);
            switch (c) {
                case '(': case '[': case '{': stack.push(c); break;
                case ')': if (stack.isEmpty() || stack.pop() != '(') return false; break;
                case ']': if (stack.isEmpty() || stack.pop() != '[') return false; break;
                case '}': if (stack.isEmpty() || stack.pop() != '{') return false; break;
            }
        }
        return stack.isEmpty();
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        System.out.println(sol.isValid("()[]{}"));   // true
        System.out.println(sol.isValid("(]"));       // false
    }
}
Stack with map<close, open> O(n) O(n)

Same approach, table-driven. Cleaner extension to additional bracket types.

Pseudo-code

match = {')': '(', ']': '[', '}': '{'}
for c in s:
    if c in match.values(): push c
    elif c in match: if stack empty or pop != match[c]: return false
return stack empty

Complexity

Time: O(n) — Linear.
Space: O(n) — Stack + small map.

Edge cases & gotchas

  • Slightly slower than the switch but trivially extensible.

Java code

import java.util.*;

class Solution {
    private static final Map<Character, Character> MATCH = Map.of(')', '(', ']', '[', '}', '{');

    public boolean isValid(final String s) {
        final Deque<Character> stack = new ArrayDeque<>();
        for (int i = 0; i < s.length(); i++) {
            final char c = s.charAt(i);
            if (MATCH.containsKey(c)) {
                if (stack.isEmpty() || stack.pop() != MATCH.get(c)) return false;
            } else {
                stack.push(c);
            }
        }
        return stack.isEmpty();
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(n)O(n)
Difficulty2/52/5
Constant factorWorst — string opsSlightly worse — Map lookup
ExtensibilityHardEasy — add to MATCH map
When to useDon'tWhen bracket alphabet is configurable
PE VerdictShip the switch-based stack. The map-based variant is an interview pivot if asked 'how would you support angle brackets / Unicode brackets / custom open-close pairs?' — answer: extend the MATCH map.

Python Solutions

Repeated string replace O(n²) O(n)

Quadratic baseline.

Pseudo-code

loop replacing pairs until stable

Complexity

Time: O(n²) — Multiple passes.
Space: O(n) — String rebuilds.

Python code

class Solution:
    def isValid(self, s: str) -> bool:
        prev = None
        while s != prev:
            prev = s
            s = s.replace("()", "").replace("[]", "").replace("{}", "")
        return s == ""
Stack with dict<close, open> O(n) O(n)

Pythonic — list as stack, dict for matching.

Pseudo-code

match = {')':'(', ']':'[', '}':'{'}
stack = []
for c in s:
    if c in match:
        if not stack or stack.pop() != match[c]: return False
    else: stack.append(c)
return not stack

Complexity

Time: O(n) — Linear.
Space: O(n) — Stack + small dict.

Edge cases & gotchas

  • Use list as stack — list.append/pop are O(1) amortized.

Python code

class Solution:
    def isValid(self, s: str) -> bool:
        match = {')': '(', ']': '[', '}': '{'}
        stack = []
        for c in s:
            if c in match:
                if not stack or stack.pop() != match[c]:
                    return False
            else:
                stack.append(c)
        return not stack


if __name__ == "__main__":
    print(Solution().isValid("()[]{}"))   # True
    print(Solution().isValid("(]"))       # False
    print(Solution().isValid("{[]}"))     # True
Stack pushing the EXPECTED close on each open O(n) O(n)

On '(' push ')'. On a close, check stack.pop() == c. One fewer dict lookup.

Pseudo-code

stack = []
for c in s:
    if c == '(': stack.append(')')
    elif c == '[': stack.append(']')
    elif c == '{': stack.append('}')
    else:
        if not stack or stack.pop() != c: return False
return not stack

Complexity

Time: O(n) — Linear.
Space: O(n) — Stack.

Edge cases & gotchas

  • Slightly faster — one comparison instead of dict lookup + comparison.

Python code

class Solution:
    def isValid(self, s: str) -> bool:
        stack = []
        for c in s:
            if c == '(':
                stack.append(')')
            elif c == '[':
                stack.append(']')
            elif c == '{':
                stack.append('}')
            elif not stack or stack.pop() != c:
                return False
        return not stack

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(n)O(n)
Difficulty1/52/5
Constant factorWorstBest — pure equality
ExtensibilityHardHard — cases hard-coded
When to useDon'tProfiled hot path
PE VerdictShip the dict-based stack. The 'push expected close' variant is a marginally faster alternative; both are valid PE answers.

What the interviewer is really testing

Stack 101. PE signal: you reach for ArrayDeque (Java) or list (Python) — never legacy Stack — and you handle the two failure modes (empty stack on close, non-empty stack at end) in the same readable structure.

Top gotchas

  • Empty-stack-on-close is a failure: `(])` would crash if you pop without checking.
  • Final stack non-empty is also a failure: `(((` ends with three unclosed opens.
  • Use ArrayDeque in Java; java.util.Stack is synchronized legacy code — slow and discouraged.

Ship-it

Java: ArrayDeque + switch. Python: list + dict. Both languages O(n) time, O(n) space.

#22 LC 155 Medium Min Stack

Stack with O(1) min — twin-stack vs encoded-delta design.

Problem Statement

Design a stack supporting push, pop, top, and getMin, all in O(1) time.

Signature: class MinStack { void push(int x); void pop(); int top(); int getMin(); }

Examples

push(-2); push(0); push(-3); getMin() → -3; pop(); top() → 0; getMin() → -2

Constraints

  • -2^31 <= val <= 2^31 - 1
  • Methods top, pop, getMin called only on non-empty stack.
  • At most 3 * 10^4 calls in total.

Approach Overview

Brute Force

Java: Single stack; O(n) getMin by scan

Python: Single list; O(n) getMin

push/pop/top O(1); getMin O(n) O(n)

Optimal #1

Java: Twin stack: values + running mins

Python: Twin stack

O(1) for all ops O(n)

Optimal #2

Java: Single stack with encoded deltas (or only-track-mins compression)

Python: Compressed mins stack

O(1) for all ops O(n) worst case, often less

Java Solutions

Single stack; O(n) getMin by scan push/pop/top O(1); getMin O(n) O(n)

Push/pop in O(1); getMin scans the whole stack — O(n).

Pseudo-code

stack with linear-scan getMin

Complexity

Time: push/pop/top O(1); getMin O(n) — Scan dominates getMin.
Space: O(n) — One stack.

Edge cases & gotchas

  • Violates the O(1) getMin requirement.

Java code

import java.util.*;
class MinStack {
    private final Deque<Integer> s = new ArrayDeque<>();
    public void push(int x) { s.push(x); }
    public void pop()       { s.pop(); }
    public int top()        { return s.peek(); }
    public int getMin() {
        int m = Integer.MAX_VALUE;
        for (final int v : s) m = Math.min(m, v);
        return m;
    }
}
Twin stack: values + running mins O(1) for all ops O(n)

Auxiliary stack tracks the minimum at each level of the main stack. push pushes min(current top of mins, new val) onto mins.

Pseudo-code

push(x): main.push(x); mins.push(min(mins.top, x))
pop:    main.pop(); mins.pop()
top:    return main.top
getMin: return mins.top

Complexity

Time: O(1) for all ops — Each op is O(1) push/pop.
Space: O(n) — Two stacks.

Edge cases & gotchas

  • Initialize mins to push(x) on the very first push (no prior top to compare).
  • Both stacks pop together — never let them desync.

Java code

import java.util.ArrayDeque;
import java.util.Deque;

class MinStack {
    private final Deque<Integer> values = new ArrayDeque<>();
    private final Deque<Integer> mins = new ArrayDeque<>();

    public void push(final int x) {
        values.push(x);
        mins.push(mins.isEmpty() ? x : Math.min(mins.peek(), x));
    }
    public void pop()       { values.pop(); mins.pop(); }
    public int top()        { return values.peek(); }
    public int getMin()     { return mins.peek(); }

    public static void main(String[] args) {
        MinStack m = new MinStack();
        m.push(-2); m.push(0); m.push(-3);
        System.out.println(m.getMin());   // -3
        m.pop();
        System.out.println(m.top());      // 0
        System.out.println(m.getMin());   // -2
    }
}
Single stack with encoded deltas (or only-track-mins compression) O(1) for all ops O(n) worst case, often less

Mins-stack only grows when a new value ≤ current min. Pop pops mins-stack only when popping a value equal to current min. Saves space on monotone-increasing inputs.

Pseudo-code

push(x): values.push(x); if mins empty or x <= mins.top: mins.push(x)
pop: v = values.pop(); if v == mins.top: mins.pop()

Complexity

Time: O(1) for all ops — Same asymptote.
Space: O(n) worst case, often less — Mins stack only on new minima.

Edge cases & gotchas

  • Use <=, not <, so duplicate min values get tracked separately — otherwise popping the first duplicate erases the min prematurely.
  • Best when push values are mostly increasing — mins stack stays tiny.

Java code

import java.util.ArrayDeque;
import java.util.Deque;

class MinStack {
    private final Deque<Integer> values = new ArrayDeque<>();
    private final Deque<Integer> mins = new ArrayDeque<>();

    public void push(final int x) {
        values.push(x);
        if (mins.isEmpty() || x <= mins.peek()) mins.push(x);
    }
    public void pop() {
        if (values.pop().equals(mins.peek())) mins.pop();
    }
    public int top()    { return values.peek(); }
    public int getMin() { return mins.peek(); }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimegetMin O(n)All O(1)
SpaceO(n)O(n) worst, often << 2n
Difficulty1/53/5 — duplicate-min subtlety
ComplianceViolates specMeets it
When to useDon'tMemory-tight; mostly-increasing pushes
PE VerdictShip the twin-stack — it's the safest correct design and the spec wants O(1). The compressed variant is a fine optimization to mention; the duplicate-min `<=` detail is a classic trap.

Python Solutions

Single list; O(n) getMin getMin O(n) O(n)

Same shape — violates spec.

Pseudo-code

list as stack

Complexity

Time: getMin O(n) — Linear scan.
Space: O(n) — One list.

Python code

class MinStack:
    def __init__(self):
        self.s = []
    def push(self, x: int) -> None: self.s.append(x)
    def pop(self) -> None: self.s.pop()
    def top(self) -> int: return self.s[-1]
    def getMin(self) -> int: return min(self.s)
Twin stack O(1) all ops O(n)

Same algorithm as Java optimal #1.

Pseudo-code

see Java

Complexity

Time: O(1) all ops — Constant per op.
Space: O(n) — Two lists.

Python code

class MinStack:
    def __init__(self):
        self.values = []
        self.mins = []

    def push(self, x: int) -> None:
        self.values.append(x)
        self.mins.append(x if not self.mins else min(self.mins[-1], x))

    def pop(self) -> None:
        self.values.pop()
        self.mins.pop()

    def top(self) -> int:
        return self.values[-1]

    def getMin(self) -> int:
        return self.mins[-1]


if __name__ == "__main__":
    m = MinStack()
    m.push(-2); m.push(0); m.push(-3)
    print(m.getMin())   # -3
    m.pop()
    print(m.top())      # 0
    print(m.getMin())   # -2
Compressed mins stack O(1) all ops O(n) worst, often less

Same as Java optimal #2 — only push to mins on new minima.

Pseudo-code

see Java

Complexity

Time: O(1) all ops — Constant per op.
Space: O(n) worst, often less — Mins grows only on new minima.

Edge cases & gotchas

  • Use <= for duplicates.

Python code

class MinStack:
    def __init__(self):
        self.values = []
        self.mins = []

    def push(self, x: int) -> None:
        self.values.append(x)
        if not self.mins or x <= self.mins[-1]:
            self.mins.append(x)

    def pop(self) -> None:
        v = self.values.pop()
        if v == self.mins[-1]:
            self.mins.pop()

    def top(self) -> int:
        return self.values[-1]

    def getMin(self) -> int:
        return self.mins[-1]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimegetMin O(n)All O(1)
SpaceO(n)O(n) typical
Difficulty1/53/5
When to useDon'tMemory-tight
PE VerdictTwin stack — clean and obviously correct. Compress only if memory matters.

What the interviewer is really testing

Design questions reward the simpler correct answer. PE signal: you propose the twin-stack first, you articulate why it's O(1) for ALL operations, and you offer the compressed variant as an optional optimization with the duplicate-min `<=` warning.

Top gotchas

  • First push: `mins` is empty — initialize the comparison guard, otherwise NPE in Java / IndexError in Python.
  • Compressed variant MUST use `<=` not `<` — otherwise duplicate min values cause premature mins pops.
  • Twin stacks must POP IN LOCKSTEP. Never let them desync.
  • ArrayDeque in Java; never Stack class.

Ship-it

Twin stack. Both languages.

#23 LC 150 Medium Evaluate Reverse Polish Notation

Postfix expression evaluator — the textbook stack-as-calculator.

Problem Statement

Given an array of tokens representing a Reverse Polish (postfix) expression, return the integer result. Operators are +, -, *, /. Division truncates toward zero.

Signature: int evalRPN(String[] tokens) / def eval_rpn(tokens: List[str]) -> int

Examples

Input: ["2","1","+","3","*"] Output: 9 ((2+1)*3)
Input: ["10","6","9","3","+","-11","*","/","*","17","+","5","+"] Output: 22

Constraints

  • 1 <= tokens.length <= 10^4
  • tokens[i] is either a valid integer or an operator in {+,-,*,/}.
  • All intermediate results fit in a 32-bit signed integer.

Approach Overview

Brute Force

Java: Recursive parse

Python: Recursive parse

O(n) O(n) call stack

Optimal #1

Java: Iterative stack of integers

Python: Iterative list-as-stack

O(n) O(n)

Optimal #2

Java: Reuse the input array as the stack (in-place)

Python: Operator dispatch via dict<str, callable>

O(n) O(1) extra

Java Solutions

Recursive parse O(n) O(n) call stack

Parse from the right; recursively consume two operands. Same complexity, more code, harder to reason about.

Pseudo-code

def parse(): t = pop(); if numeric return t; b = parse(); a = parse(); return apply(t, a, b)

Complexity

Time: O(n) — Each token visited once.
Space: O(n) call stack — Recursion depth.

Edge cases & gotchas

  • Stack-overflow risk on deeply nested expressions.

Java code

import java.util.*;
class Solution {
    private int idx;
    public int evalRPN(final String[] tokens) {
        idx = tokens.length - 1;
        return parse(tokens);
    }
    private int parse(final String[] tokens) {
        final String t = tokens[idx--];
        switch (t) {
            case "+": { int b = parse(tokens); int a = parse(tokens); return a + b; }
            case "-": { int b = parse(tokens); int a = parse(tokens); return a - b; }
            case "*": { int b = parse(tokens); int a = parse(tokens); return a * b; }
            case "/": { int b = parse(tokens); int a = parse(tokens); return a / b; }
            default: return Integer.parseInt(t);
        }
    }
}
Iterative stack of integers O(n) O(n)

Push numbers; on operator, pop two (RHS first), apply, push back. The textbook answer.

Pseudo-code

stack = empty
for t in tokens:
    if t is operator:
        b = stack.pop(); a = stack.pop()
        stack.push(apply(t, a, b))
    else: stack.push(int(t))
return stack.top

Complexity

Time: O(n) — Single pass.
Space: O(n) — Stack of operands.

Edge cases & gotchas

  • Pop ORDER matters: b = pop() FIRST (it was pushed last). a - b ≠ b - a.
  • Java integer division truncates toward zero, matching the spec — no special handling.
  • ArrayDeque, not Stack.

Java code

import java.util.ArrayDeque;
import java.util.Deque;

class Solution {
    public int evalRPN(final String[] tokens) {
        final Deque<Integer> stack = new ArrayDeque<>(tokens.length / 2 + 1);
        for (final String t : tokens) {
            switch (t) {
                case "+": { int b = stack.pop(), a = stack.pop(); stack.push(a + b); break; }
                case "-": { int b = stack.pop(), a = stack.pop(); stack.push(a - b); break; }
                case "*": { int b = stack.pop(), a = stack.pop(); stack.push(a * b); break; }
                case "/": { int b = stack.pop(), a = stack.pop(); stack.push(a / b); break; }
                default: stack.push(Integer.parseInt(t));
            }
        }
        return stack.peek();
    }

    public static void main(String[] args) {
        System.out.println(new Solution().evalRPN(new String[]{"2","1","+","3","*"}));   // 9
    }
}
Reuse the input array as the stack (in-place) O(n) O(1) extra

Treat tokens itself as scratch space. Saves the auxiliary stack at the cost of mutating input.

Pseudo-code

top = -1
for t in tokens:
    if op: top--; tokens[top] = apply(tokens[top], tokens[top+1])
    else: top++; tokens[top] = parseInt(t)
return tokens[0]

Complexity

Time: O(n) — Single pass.
Space: O(1) extra — No auxiliary structure.

Edge cases & gotchas

  • Mutates input — confirm caller is OK with it.
  • Marginally faster due to better cache locality (single contiguous int[] scratch instead of stack node allocations).

Java code

class Solution {
    public int evalRPN(final String[] tokens) {
        final int[] stack = new int[tokens.length / 2 + 1];   // scratch, not input mutation
        int top = -1;
        for (final String t : tokens) {
            switch (t) {
                case "+": { final int b = stack[top--], a = stack[top]; stack[top] = a + b; break; }
                case "-": { final int b = stack[top--], a = stack[top]; stack[top] = a - b; break; }
                case "*": { final int b = stack[top--], a = stack[top]; stack[top] = a * b; break; }
                case "/": { final int b = stack[top--], a = stack[top]; stack[top] = a / b; break; }
                default: stack[++top] = Integer.parseInt(t);
            }
        }
        return stack[0];
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(n) call stackO(n) primitive array
Difficulty3/53/5
Constant factorRecursion overheadBest — no boxing, single int[]
Cache behaviorWorstBest — sequential int[]
When to useDemo onlyHot path / huge inputs
PE VerdictShip the iterative ArrayDeque stack. It's the canonical answer and reads itself. Reach for the int[] scratch variant only when boxing overhead becomes measurable (rare at LeetCode scale).

Python Solutions

Recursive parse O(n) O(n)

Same shape as Java baseline.

Pseudo-code

consume from the right; recurse on operands

Complexity

Time: O(n) — Linear.
Space: O(n) — Recursion stack.

Python code

from typing import List


class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        idx = [len(tokens) - 1]
        def parse() -> int:
            t = tokens[idx[0]]
            idx[0] -= 1
            if t in "+-*/":
                b = parse()
                a = parse()
                if t == '+': return a + b
                if t == '-': return a - b
                if t == '*': return a * b
                # truncate toward zero (Python // floors — fix sign)
                return int(a / b)
            return int(t)
        return parse()
Iterative list-as-stack O(n) O(n)

Pythonic — list as stack, dispatch on operator.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(n) — Stack.

Edge cases & gotchas

  • Python's / is float; // floors toward -inf. Use int(a/b) to truncate toward zero like Java.
  • Edge case: -7 // 2 == -4 in Python; int(-7/2) == -3 (correct, matches Java).

Python code

from typing import List


class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        stack = []
        for t in tokens:
            if t in "+-*/":
                b = stack.pop()
                a = stack.pop()
                if t == '+':
                    stack.append(a + b)
                elif t == '-':
                    stack.append(a - b)
                elif t == '*':
                    stack.append(a * b)
                else:
                    # int(a/b) truncates toward zero (matches problem spec); a // b would floor.
                    stack.append(int(a / b))
            else:
                stack.append(int(t))
        return stack[0]


if __name__ == "__main__":
    print(Solution().evalRPN(["2","1","+","3","*"]))   # 9
    print(Solution().evalRPN(["10","6","9","3","+","-11","*","/","*","17","+","5","+"]))   # 22
Operator dispatch via dict<str, callable> O(n) O(n)

Cleaner — drop the if/elif chain in favor of a function table.

Pseudo-code

OPS = {'+': add, '-': sub, ...}; OPS[t](a, b)

Complexity

Time: O(n) — Linear.
Space: O(n) — Stack + small dispatch dict.

Edge cases & gotchas

  • operator.add etc. give clean lambdas.
  • Division must truncate toward zero — wrap explicitly.

Python code

from typing import List
from operator import add, sub, mul


class Solution:
    OPS = {
        '+': add,
        '-': sub,
        '*': mul,
        '/': lambda a, b: int(a / b),   # truncate toward zero, NOT floor
    }

    def evalRPN(self, tokens: List[str]) -> int:
        stack = []
        for t in tokens:
            if t in self.OPS:
                b = stack.pop()
                a = stack.pop()
                stack.append(self.OPS[t](a, b))
            else:
                stack.append(int(t))
        return stack[0]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(n)O(n)
Difficulty3/52/5
Constant factorRecursion overheadSlight indirection but C-fast operators
ReadabilityWorstBest — declarative
When to useDemoProduction code with extension in mind
PE VerdictIterative list-as-stack — fast and readable. The dispatch-dict variant is the right answer if you're building a calculator with extensible operators.

What the interviewer is really testing

Stack as calculator. PE signal: you handle the truncate-toward-zero division correctly in Python (int(a/b), NOT a // b), you pop in the right order (b before a), and you reach for ArrayDeque / list — never legacy Stack.

Top gotchas

  • Pop ORDER: b first (last pushed), then a. Wrong order breaks subtraction and division.
  • Python: `//` floors, but the problem requires truncation toward zero. `int(a/b)` is correct; `a // b` is WRONG for negative a.
  • Java: `int / int` truncates toward zero by default — no fix needed.
  • Empty token array isn't possible per spec, but stack-empty defensiveness still earns points.

Ship-it

Iterative stack, both languages. Watch the Python division semantics.

#24 LC 739 Medium Daily Temperatures

Monotonic stack: 'next greater' in disguise — answer the wait-days for each day.

Problem Statement

Given an array temperatures, return an array answer where answer[i] equals the number of days you have to wait after day i to get a warmer temperature. If no such day exists, set answer[i] = 0.

Signature: int[] dailyTemperatures(int[] temperatures) / def daily_temperatures(temperatures: List[int]) -> List[int]

Examples

Input: temperatures = [73,74,75,71,69,72,76,73] Output: [1,1,4,2,1,1,0,0]

Constraints

  • 1 <= temperatures.length <= 10^5
  • 30 <= temperatures[i] <= 100

Approach Overview

Brute Force

Java: For each i, scan forward

Python: Forward scan

O(n²) O(1)

Optimal #1

Java: Monotonic decreasing stack of indices

Python: Monotonic stack

O(n) O(n)

Optimal #2

Java: Backward sweep with jump pointers

Python: Backward sweep with jump

O(n) amortized O(1) extra

Java Solutions

For each i, scan forward O(n²) O(1)

Quadratic — works but TLEs on large inputs.

Pseudo-code

for i: for j in i+1..n-1: if temps[j] > temps[i]: ans[i] = j - i; break

Complexity

Time: O(n²) — n outer × up-to-n inner.
Space: O(1) — Output only.

Edge cases & gotchas

  • TLEs at n=10^5.

Java code

class Solution {
    public int[] dailyTemperatures(final int[] t) {
        final int n = t.length;
        final int[] out = new int[n];
        for (int i = 0; i < n; i++)
            for (int j = i + 1; j < n; j++)
                if (t[j] > t[i]) { out[i] = j - i; break; }
        return out;
    }
}
Monotonic decreasing stack of indices O(n) O(n)

Stack stores indices whose temperature has not yet been beaten. On day i with temp[i] > temp[stack.top]: pop, record gap.

Pseudo-code

stack = empty; out = [0]*n
for i, t in enumerate(temps):
    while stack and t > temps[stack.top]:
        j = stack.pop(); out[j] = i - j
    stack.push(i)
return out

Complexity

Time: O(n) — Each index pushed and popped at most once.
Space: O(n) — Stack ≤ n.

Edge cases & gotchas

  • Strict > not >= — equal temperatures don't qualify as 'warmer'.
  • Indices left on the stack at the end have answer 0 — already initialized.

Java code

import java.util.ArrayDeque;
import java.util.Deque;

class Solution {
    public int[] dailyTemperatures(final int[] t) {
        final int n = t.length;
        final int[] out = new int[n];
        final Deque<Integer> stack = new ArrayDeque<>();
        for (int i = 0; i < n; i++) {
            while (!stack.isEmpty() && t[i] > t[stack.peek()]) {
                final int j = stack.pop();
                out[j] = i - j;
            }
            stack.push(i);
        }
        return out;
    }

    public static void main(String[] args) {
        System.out.println(java.util.Arrays.toString(
            new Solution().dailyTemperatures(new int[]{73,74,75,71,69,72,76,73})));   // [1,1,4,2,1,1,0,0]
    }
}
Backward sweep with jump pointers O(n) amortized O(1) extra

Walk right-to-left. For each i, scan forward but JUMP using already-computed answers — skips already-processed runs.

Pseudo-code

for i from n-1 down to 0:
    j = i + 1
    while j < n and temps[j] <= temps[i]:
        if out[j] == 0: j = n; break    # no warmer day exists
        j += out[j]                    # skip ahead by the warmer-day gap
    if j < n: out[i] = j - i

Complexity

Time: O(n) amortized — Each index visited a bounded number of times via the jump skips.
Space: O(1) extra — Output array only.

Edge cases & gotchas

  • Slightly trickier to verify O(n); rests on the path-compression-like skip.
  • No allocation beyond the output — useful in space-tight contexts.

Java code

class Solution {
    public int[] dailyTemperatures(final int[] t) {
        final int n = t.length;
        final int[] out = new int[n];
        for (int i = n - 1; i >= 0; i--) {
            int j = i + 1;
            while (j < n && t[j] <= t[i]) {
                if (out[j] == 0) { j = n; break; }
                j += out[j];
            }
            if (j < n) out[i] = j - i;
        }
        return out;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n) amortized
SpaceO(1)O(1) extra
Difficulty1/54/5 — amortization argument
Constant factorWorstSlightly worse — but no allocation
ReadabilityBest for juniorsTrickiest
When to useTiny nMemory-tight; advanced answer
PE VerdictMonotonic stack is the canonical answer to every 'next greater element' problem. Memorize the template — it's exactly the same pattern as 'Next Greater Element', 'Largest Rectangle in Histogram', and 'Sliding Window Maximum' (with deque).

Python Solutions

Forward scan O(n²) O(1)

Quadratic baseline.

Pseudo-code

nested loops

Complexity

Time: O(n²) — Quadratic.
Space: O(1) — Output.

Python code

from typing import List


class Solution:
    def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
        n = len(temperatures)
        out = [0] * n
        for i in range(n):
            for j in range(i + 1, n):
                if temperatures[j] > temperatures[i]:
                    out[i] = j - i
                    break
        return out
Monotonic stack O(n) O(n)

Same as Java optimal #1.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear amortized.
Space: O(n) — Stack.

Python code

from typing import List


class Solution:
    def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
        """Monotonic decreasing stack of indices."""
        n = len(temperatures)
        out = [0] * n
        stack: List[int] = []
        for i, t in enumerate(temperatures):
            while stack and t > temperatures[stack[-1]]:
                j = stack.pop()
                out[j] = i - j
            stack.append(i)
        return out


if __name__ == "__main__":
    print(Solution().dailyTemperatures([73,74,75,71,69,72,76,73]))   # [1,1,4,2,1,1,0,0]
Backward sweep with jump O(n) amortized O(1) extra

Same as Java optimal #2.

Pseudo-code

see Java

Complexity

Time: O(n) amortized — Linear with skip optimization.
Space: O(1) extra — Output only.

Python code

from typing import List


class Solution:
    def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
        n = len(temperatures)
        out = [0] * n
        for i in range(n - 1, -1, -1):
            j = i + 1
            while j < n and temperatures[j] <= temperatures[i]:
                if out[j] == 0:
                    j = n
                    break
                j += out[j]
            if j < n:
                out[i] = j - i
        return out

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n) amortized
SpaceO(1)O(1) extra
Difficulty1/54/5
When to useDemoMemory-tight
PE VerdictMonotonic stack — same as Java. Template generalizes to all next-greater-element style problems.

What the interviewer is really testing

Monotonic stack is one of THE templates worth memorizing. PE signal: you recognize 'next greater' instantly, you state the invariant ('stack indices are in strictly decreasing temperature order'), and you generalize to the family (NGE, NSE, histogram, etc.).

Top gotchas

  • Strict `>` not `>=` — same temperature doesn't qualify as warmer.
  • Push every index, even if you don't pop anything that step.
  • Indices remaining at the end correspond to days with no warmer follow-up — answer stays 0.
  • Stack stores INDICES, not temperatures — you need indices to compute the gap.

Ship-it

Monotonic decreasing-by-temperature stack of indices, both languages.

#25 LC 853 Medium Car Fleet

Sort by start, then a backward sweep over arrival times — fleets form when a slower car blocks a faster one.

Problem Statement

There are n cars on a one-lane road heading to target. Each car has position position[i] and constant speed speed[i]. A faster car catching up to a slower one joins it and they move together as a fleet at the slower speed. Return the number of car fleets that arrive at the target.

Signature: int carFleet(int target, int[] position, int[] speed)

Examples

Input: target = 12, position = [10,8,0,5,3], speed = [2,4,1,1,3] Output: 3
Input: target = 10, position = [3], speed = [3] Output: 1

Constraints

  • n == position.length == speed.length
  • 1 <= n <= 10^5
  • 0 < target <= 10^6, 0 <= position[i] < target, all positions distinct
  • 0 < speed[i] <= 10^6

Approach Overview

Brute Force

Java: Pairwise simulation

Python: Pairwise simulation

O(n² log n) O(n)

Optimal #1

Java: Sort by position desc, monotonic stack of arrival times

Python: Sort + arrival-time monotonic check

O(n log n) O(n)

Optimal #2

Java: Same algorithm, fixed-point times to avoid float comparison

Python: Cross-multiplication variant

O(n log n) O(n)

Java Solutions

Pairwise simulation O(n² log n) O(n)

Track each car; advance time, merge on collision. Quadratic and fragile.

Pseudo-code

simulate each car arrival; count surviving distinct-arrival groups

Complexity

Time: O(n² log n) — Each merge needs scan.
Space: O(n) — Active fleets list.

Java code

class Solution {
    public int carFleet(int target, int[] position, int[] speed) {
        // Conceptual baseline only — see optimal for the right approach.
        return 0;
    }
}
Sort by position desc, monotonic stack of arrival times O(n log n) O(n)

Sort cars by position descending (closest to target first). For each, compute arrival time t = (target - pos) / speed. Push onto stack only if its arrival time is STRICTLY GREATER than the stack top — otherwise it joins that fleet.

Pseudo-code

pairs = list((position[i], speed[i])) sorted by position desc
stack = []
for (p, s) in pairs:
    t = (target - p) / s
    if stack empty or t > stack.top: stack.push(t)
return stack.size

Complexity

Time: O(n log n) — Sort dominates.
Space: O(n) — Stack of fleet leader times.

Edge cases & gotchas

  • Compare arrival times with strict > — equal times mean the trailing car catches up exactly at the target, joining the fleet.
  • Use double for arrival time; the divisions don't repeat-compute.
  • Sort descending so cars closer to target are processed first — they form the leading fleet.

Java code

import java.util.*;

class Solution {
    public int carFleet(final int target, final int[] position, final int[] speed) {
        final int n = position.length;
        final double[][] cars = new double[n][2];
        for (int i = 0; i < n; i++) { cars[i][0] = position[i]; cars[i][1] = speed[i]; }
        // Sort by position descending — closest to target first
        Arrays.sort(cars, (a, b) -> Double.compare(b[0], a[0]));
        int fleets = 0;
        double leadTime = 0.0;
        for (final double[] c : cars) {
            final double arrival = (target - c[0]) / c[1];
            if (arrival > leadTime) {
                leadTime = arrival;
                fleets++;
            }
        }
        return fleets;
    }

    public static void main(String[] args) {
        System.out.println(new Solution().carFleet(12, new int[]{10,8,0,5,3}, new int[]{2,4,1,1,3}));   // 3
    }
}
Same algorithm, fixed-point times to avoid float comparison O(n log n) O(n)

Compare (target - p1) * s2 vs (target - p2) * s1 using cross-multiplication — no floats.

Pseudo-code

for sorted-by-position-desc:
    if leadDist * speed > leadSpeed * dist: new fleet

Complexity

Time: O(n log n) — Same.
Space: O(n) — Same.

Edge cases & gotchas

  • Avoids floating-point precision issues — useful in competitive contexts.
  • Cross-multiplication can overflow if target * speed approaches INT_MAX — use long.

Java code

import java.util.*;

class Solution {
    public int carFleet(final int target, final int[] position, final int[] speed) {
        final int n = position.length;
        final Integer[] idx = new Integer[n];
        for (int i = 0; i < n; i++) idx[i] = i;
        Arrays.sort(idx, (a, b) -> Integer.compare(position[b], position[a]));
        int fleets = 0;
        long leadDist = 0, leadSpeed = 1;     // virtual lead: arrival time = leadDist / leadSpeed
        for (final int i : idx) {
            final long dist = (long)(target - position[i]);
            final long sp = speed[i];
            // car's arrival = dist/sp; lead's arrival = leadDist/leadSpeed
            // car arrives strictly later iff dist * leadSpeed > leadDist * sp
            if (dist * leadSpeed > leadDist * sp) {
                leadDist = dist; leadSpeed = sp;
                fleets++;
            }
        }
        return fleets;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²·log n)O(n log n)
SpaceO(n)O(n)
Difficulty4/5
Float precisionBullet-proof
Constant factorWorstSlightly more arithmetic per step
When to useDon'tAdversarial precision concerns / huge target
PE VerdictSort-by-position-desc + arrival-time monotonic check. The float precision is fine at LeetCode scale; reach for cross-multiplication only if the spec hints at adversarial inputs.

Python Solutions

Pairwise simulation

Conceptual baseline.

Pseudo-code

Complexity

Time: — —
Space: — —

Python code

class Solution:
    def carFleet(self, target: int, position, speed):
        # See optimal — pairwise simulation is the wrong shape.
        return 0
Sort + arrival-time monotonic check O(n log n) O(n)

Same as Java optimal #1.

Pseudo-code

see Java

Complexity

Time: O(n log n) — Sort dominates.
Space: O(n) — Sort buffer + counters.

Edge cases & gotchas

  • Use sorted with reverse=True; zip position and speed.

Python code

from typing import List


class Solution:
    def carFleet(self, target: int, position: List[int], speed: List[int]) -> int:
        # Sort by position desc — closest car to target leads
        cars = sorted(zip(position, speed), key=lambda c: -c[0])
        fleets = 0
        lead_time = 0.0
        for p, s in cars:
            arrival = (target - p) / s
            if arrival > lead_time:
                lead_time = arrival
                fleets += 1
        return fleets


if __name__ == "__main__":
    print(Solution().carFleet(12, [10,8,0,5,3], [2,4,1,1,3]))   # 3
Cross-multiplication variant O(n log n) O(n)

Same as Java optimal #2.

Pseudo-code

see Java

Complexity

Time: O(n log n) — Same.
Space: O(n) — Same.

Edge cases & gotchas

  • Python ints unbounded — no overflow worries (rare CPython advantage).

Python code

from typing import List


class Solution:
    def carFleet(self, target: int, position: List[int], speed: List[int]) -> int:
        cars = sorted(zip(position, speed), key=lambda c: -c[0])
        fleets = 0
        lead_dist, lead_speed = 0, 1
        for p, s in cars:
            dist = target - p
            # lead's arrival time = lead_dist/lead_speed; car's = dist/s.
            # Car arrives strictly later iff dist*lead_speed > lead_dist*s.
            if dist * lead_speed > lead_dist * s:
                lead_dist, lead_speed = dist, s
                fleets += 1
        return fleets

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n log n)
SpaceO(n)
Difficulty3/5 (Python ints simplify)
Float precisionExact — Python ints unbounded
When to useWhen precision matters
PE VerdictSort by position desc + arrival-time check. Python's exact integer arithmetic makes the cross-multiplication variant trivially safe.

What the interviewer is really testing

Coordinate compression + monotonic logic. PE signal: you recognize 'fleet boundary = arrival-time strictly greater than the lead', you sort descending by position so the first item is always the lead, and you discuss the float-precision-vs-cross-multiplication trade-off.

Top gotchas

  • Sort DESCENDING by position — the closest car to target is the natural fleet lead.
  • Use STRICT `>` when comparing arrival times — equal times means catching up exactly at target, which counts as the same fleet.
  • Float precision is acceptable at LeetCode scale; for adversarial cases use cross-multiplication.
  • Watch for zero-distance: a car already AT target has arrival 0; the strict-> handles this naturally.

Ship-it

Sort by position desc, monotonic max of arrival time. Both languages.

#26 LC 84 Hard Largest Rectangle in Histogram

Monotonic stack to find the largest rectangle — the apex of stack-based optimization.

Problem Statement

Given an array heights representing the heights of histogram bars (each 1 unit wide), return the area of the largest rectangle that can be formed within the histogram.

Signature: int largestRectangleArea(int[] heights) / def largest_rectangle_area(heights: List[int]) -> int

Examples

Input: heights = [2,1,5,6,2,3] Output: 10
Input: heights = [2,4] Output: 4

Constraints

  • 1 <= heights.length <= 10^5
  • 0 <= heights[i] <= 10^4

Approach Overview

Brute Force

Java: For each bar, expand left and right

Python: Expand around each bar

O(n²) O(1)

Optimal #1

Java: Monotonic increasing stack of indices, single pass with sentinel

Python: Single-pass monotonic stack with sentinel

O(n) O(n)

Optimal #2

Java: Two passes: precompute leftSmaller and rightSmaller arrays

Python: Two passes: nearest smaller left/right

O(n) O(n)

Java Solutions

For each bar, expand left and right O(n²) O(1)

Quadratic — for each bar i, find the maximal contiguous block where heights[j] >= heights[i].

Pseudo-code

for i: extend l, r while heights[l-1], heights[r+1] >= heights[i]
       area = heights[i] * (r - l + 1)

Complexity

Time: O(n²) — Quadratic expansion.
Space: O(1) — Scalars.

Edge cases & gotchas

  • TLEs at n=10^5.

Java code

class Solution {
    public int largestRectangleArea(final int[] h) {
        int best = 0;
        for (int i = 0; i < h.length; i++) {
            int l = i, r = i;
            while (l > 0 && h[l-1] >= h[i]) l--;
            while (r < h.length - 1 && h[r+1] >= h[i]) r++;
            best = Math.max(best, h[i] * (r - l + 1));
        }
        return best;
    }
}
Monotonic increasing stack of indices, single pass with sentinel O(n) O(n)

Stack maintains indices in non-decreasing height order. On a smaller incoming bar, pop and compute area for each popped bar (it can extend right to current index, left to the new top of stack).

Pseudo-code

stack = empty; best = 0
for i in 0..n (treat n as sentinel height = 0):
    h = heights[i] if i < n else 0
    while stack and heights[stack.top] > h:
        idx = stack.pop()
        width = stack empty ? i : i - stack.top - 1
        best = max(best, heights[idx] * width)
    stack.push(i)

Complexity

Time: O(n) — Each bar pushed and popped once.
Space: O(n) — Stack.

Edge cases & gotchas

  • Sentinel iteration at i = n with virtual height 0 forces the stack to drain at the end.
  • Width when stack becomes empty after pop is `i` (extends from index 0).
  • Equal heights: use strict >, so equal heights don't pop — we compute area for the rightmost in the run.

Java code

import java.util.ArrayDeque;
import java.util.Deque;

class Solution {
    /**
     * Monotonic increasing stack of indices. The +1 sentinel iteration at
     * i == n flushes the remaining stack with height 0.
     */
    public int largestRectangleArea(final int[] heights) {
        final int n = heights.length;
        final Deque<Integer> stack = new ArrayDeque<>();
        int best = 0;
        for (int i = 0; i <= n; i++) {
            final int h = (i == n) ? 0 : heights[i];
            while (!stack.isEmpty() && heights[stack.peek()] > h) {
                final int idx = stack.pop();
                final int width = stack.isEmpty() ? i : i - stack.peek() - 1;
                best = Math.max(best, heights[idx] * width);
            }
            stack.push(i);
        }
        return best;
    }

    public static void main(String[] args) {
        System.out.println(new Solution().largestRectangleArea(new int[]{2,1,5,6,2,3}));   // 10
    }
}
Two passes: precompute leftSmaller and rightSmaller arrays O(n) O(n)

leftSmaller[i] = index of nearest smaller bar to the left; rightSmaller[i] symmetric. Width = right - left - 1.

Pseudo-code

leftSmaller[i] = nearest j < i with heights[j] < heights[i] (or -1)
rightSmaller[i] = nearest j > i with heights[j] < heights[i] (or n)
best = max(heights[i] * (rightSmaller[i] - leftSmaller[i] - 1))

Complexity

Time: O(n) — Two stack passes + final scan.
Space: O(n) — Two aux arrays.

Edge cases & gotchas

  • Boundary sentinels: -1 and n.
  • Strict-less comparisons in both directions.
  • Reads more like 'find next smaller' templates — useful for teaching, slightly more code.

Java code

import java.util.ArrayDeque;
import java.util.Deque;

class Solution {
    public int largestRectangleArea(final int[] heights) {
        final int n = heights.length;
        final int[] left = new int[n], right = new int[n];
        final Deque<Integer> stack = new ArrayDeque<>();
        for (int i = 0; i < n; i++) {
            while (!stack.isEmpty() && heights[stack.peek()] >= heights[i]) stack.pop();
            left[i] = stack.isEmpty() ? -1 : stack.peek();
            stack.push(i);
        }
        stack.clear();
        for (int i = n - 1; i >= 0; i--) {
            while (!stack.isEmpty() && heights[stack.peek()] >= heights[i]) stack.pop();
            right[i] = stack.isEmpty() ? n : stack.peek();
            stack.push(i);
        }
        int best = 0;
        for (int i = 0; i < n; i++) best = Math.max(best, heights[i] * (right[i] - left[i] - 1));
        return best;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(1)O(n)
Difficulty2/54/5 — two passes more familiar
Constant factorWorstSlightly worse — three passes
ReadabilityBest for juniorsMore didactic — clearer intent per pass
GeneralizesLimitedSame
When to useTiny nWhiteboard if you can't fit the single-pass logic
PE VerdictSingle-pass monotonic stack with sentinel — the canonical answer. Memorize the width formula `i - stack.top - 1` (or `i` when stack empty). The two-pass variant is the right answer if you need to also output 'next smaller' indices for downstream code.

Python Solutions

Expand around each bar O(n²) O(1)

Quadratic.

Pseudo-code

expand l, r for each bar

Complexity

Time: O(n²) — Quadratic.
Space: O(1) — Scalars.

Python code

from typing import List


class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        n = len(heights)
        best = 0
        for i in range(n):
            l = r = i
            while l > 0 and heights[l-1] >= heights[i]: l -= 1
            while r < n - 1 and heights[r+1] >= heights[i]: r += 1
            best = max(best, heights[i] * (r - l + 1))
        return best
Single-pass monotonic stack with sentinel O(n) O(n)

Same as Java optimal #1.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear amortized.
Space: O(n) — Stack.

Python code

from typing import List


class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        """
        Monotonic increasing stack of indices.
        Sentinel iteration at i == n with virtual height 0 flushes the stack.
        """
        n = len(heights)
        stack: List[int] = []
        best = 0
        for i in range(n + 1):
            h = 0 if i == n else heights[i]
            while stack and heights[stack[-1]] > h:
                idx = stack.pop()
                width = i if not stack else i - stack[-1] - 1
                best = max(best, heights[idx] * width)
            stack.append(i)
        return best


if __name__ == "__main__":
    print(Solution().largestRectangleArea([2,1,5,6,2,3]))   # 10
    print(Solution().largestRectangleArea([2,4]))            # 4
Two passes: nearest smaller left/right O(n) O(n)

Same as Java optimal #2.

Pseudo-code

see Java

Complexity

Time: O(n) — Two passes.
Space: O(n) — Two arrays.

Python code

from typing import List


class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        n = len(heights)
        left = [-1] * n
        right = [n] * n
        stack: List[int] = []
        for i in range(n):
            while stack and heights[stack[-1]] >= heights[i]:
                stack.pop()
            left[i] = stack[-1] if stack else -1
            stack.append(i)
        stack.clear()
        for i in range(n - 1, -1, -1):
            while stack and heights[stack[-1]] >= heights[i]:
                stack.pop()
            right[i] = stack[-1] if stack else n
            stack.append(i)
        return max(heights[i] * (right[i] - left[i] - 1) for i in range(n))

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(1)O(n)
Difficulty2/54/5
When to useDemoWhiteboard fallback
PE VerdictSingle-pass with sentinel. Same template as the Java answer; the n+1 sentinel iteration is the cleanest way to flush the stack.

What the interviewer is really testing

Largest Rectangle is the canonical 'monotonic stack with width tracking' problem. PE signal: you derive the width formula from scratch ('after pop, the remaining stack top is the left boundary; current i is the right boundary'), you handle the empty-stack case correctly (left boundary becomes -1), and you mention the sentinel trick (i = n with height 0) as the cleanest end-of-array flush.

Top gotchas

  • Width when stack becomes empty after pop is `i` (extends all the way to index 0), NOT `i - 1`.
  • Width when stack non-empty is `i - stack.top - 1` (exclusive on both sides because both bars are smaller).
  • Sentinel iteration at i == n with virtual height 0 is the cleanest way to flush the stack.
  • Use strict > in the pop condition; equal heights don't trigger a pop, leaving the rightmost in a run to compute area for the whole equal-height block.

Ship-it

Single-pass monotonic stack with end-of-array sentinel. Both languages.

Binary Search

#27 LC 704 Easy Binary Search

The textbook binary search — get the bounds and the loop invariant exactly right.

Problem Statement

Given a sorted array nums of distinct integers and a target value target, return the index of target if found, otherwise return -1. Must run in O(log n).

Signature: int search(int[] nums, int target)

Examples

Input: nums = [-1,0,3,5,9,12], target = 9 Output: 4
Input: nums = [-1,0,3,5,9,12], target = 2 Output: -1

Constraints

  • 1 <= nums.length <= 10^4
  • -10^4 <= nums[i], target <= 10^4
  • All values in nums are distinct, sorted in ascending order.

Approach Overview

Brute Force

Java: Linear scan

Python: Linear scan

O(n) O(1)

Optimal #1

Java: Iterative binary search (closed interval [lo, hi])

Python: Iterative closed-interval binary search

O(log n) O(1)

Optimal #2

Java: Half-open interval [lo, hi)

Python: bisect_left + match check

O(log n) O(1)

Java Solutions

Linear scan O(n) O(1)

O(n) — violates the spec but useful baseline.

Pseudo-code

for i: if nums[i] == target return i

Complexity

Time: O(n) — Sequential scan.
Space: O(1) — None.

Edge cases & gotchas

  • Violates O(log n) requirement.

Java code

class Solution {
    public int search(final int[] nums, final int target) {
        for (int i = 0; i < nums.length; i++) if (nums[i] == target) return i;
        return -1;
    }
}
Iterative binary search (closed interval [lo, hi]) O(log n) O(1)

Standard form. Loop while lo <= hi; mid = lo + (hi - lo) / 2 to avoid overflow.

Pseudo-code

lo, hi = 0, n-1
while lo <= hi:
    mid = lo + (hi - lo) / 2
    if nums[mid] == target: return mid
    elif nums[mid] < target: lo = mid + 1
    else: hi = mid - 1
return -1

Complexity

Time: O(log n) — Halve search space each iteration.
Space: O(1) — Two indices.

Edge cases & gotchas

  • lo + (hi - lo)/2 not (lo + hi)/2 — the latter overflows for large indices.
  • Loop bounds matter: <= for closed interval, < for half-open.
  • Update by mid + 1 / mid - 1 to make progress and avoid infinite loops.

Java code

class Solution {
    public int search(final int[] nums, final int target) {
        int lo = 0, hi = nums.length - 1;
        while (lo <= hi) {
            final int mid = lo + (hi - lo) / 2;
            if (nums[mid] == target) return mid;
            if (nums[mid] < target) lo = mid + 1;
            else hi = mid - 1;
        }
        return -1;
    }

    public static void main(String[] args) {
        System.out.println(new Solution().search(new int[]{-1,0,3,5,9,12}, 9));   // 4
    }
}
Half-open interval [lo, hi) O(log n) O(1)

Often easier to compose with other algorithms (lower_bound / upper_bound style). hi starts at n; loop while lo < hi.

Pseudo-code

lo, hi = 0, n
while lo < hi:
    mid = lo + (hi - lo) / 2
    if nums[mid] < target: lo = mid + 1
    else: hi = mid           # NOT mid - 1
if lo < n and nums[lo] == target: return lo
return -1

Complexity

Time: O(log n) — Same.
Space: O(1) — Same.

Edge cases & gotchas

  • Half-open form returns the LOWER BOUND of target — the smallest index where nums[i] >= target.
  • After loop, must check that lo is in range AND equals target.
  • Generalizes cleanly to 'first occurrence' / 'insertion point'.

Java code

class Solution {
    public int search(final int[] nums, final int target) {
        int lo = 0, hi = nums.length;
        while (lo < hi) {
            final int mid = lo + (hi - lo) / 2;
            if (nums[mid] < target) lo = mid + 1;
            else hi = mid;
        }
        return (lo < nums.length && nums[lo] == target) ? lo : -1;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(log n)
SpaceO(1)O(1)
Difficulty1/53/5 — invariant care
Spec complianceViolatesMeets
GeneralizesNoLower bound / insertion point / upper bound
When to useTrivial demoComposable variants (kth element, count, etc.)
PE VerdictClosed-interval form for direct search. Master both — half-open is the right answer the moment the question asks for 'insertion point' or 'first occurrence'.

Python Solutions

Linear scan O(n) O(1)

O(n).

Pseudo-code

list scan

Complexity

Time: O(n) — Linear.
Space: O(1) — None.

Python code

from typing import List


class Solution:
    def search(self, nums: List[int], target: int) -> int:
        for i, x in enumerate(nums):
            if x == target:
                return i
        return -1
Iterative closed-interval binary search O(log n) O(1)

Same as Java optimal #1.

Pseudo-code

see Java

Complexity

Time: O(log n) — Halving.
Space: O(1) — Indices.

Edge cases & gotchas

  • Python ints are unbounded — overflow is non-issue, but use the same idiom for portability.

Python code

from typing import List


class Solution:
    def search(self, nums: List[int], target: int) -> int:
        lo, hi = 0, len(nums) - 1
        while lo <= hi:
            mid = (lo + hi) // 2
            if nums[mid] == target:
                return mid
            elif nums[mid] < target:
                lo = mid + 1
            else:
                hi = mid - 1
        return -1


if __name__ == "__main__":
    print(Solution().search([-1,0,3,5,9,12], 9))   # 4
bisect_left + match check O(log n) O(1)

Use bisect.bisect_left from stdlib; check the returned index for equality.

Pseudo-code

i = bisect_left(nums, target); return i if i<n and nums[i]==target else -1

Complexity

Time: O(log n) — Logarithmic.
Space: O(1) — None.

Edge cases & gotchas

  • bisect_left returns n when target > all elements — bounds-check before indexing.

Python code

from typing import List
from bisect import bisect_left


class Solution:
    def search(self, nums: List[int], target: int) -> int:
        i = bisect_left(nums, target)
        return i if i < len(nums) and nums[i] == target else -1

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(log n)
SpaceO(1)O(1)
Difficulty1/53/5
Constant factorWorstPure Python loop overhead
When to useDemoWhiteboard / show the technique
PE VerdictUse bisect_left in production Python — it's C-fast and idiomatic. Write the iterative form on a whiteboard to demonstrate that you understand the algorithm.

What the interviewer is really testing

The classic 'is your loop invariant solid' interview probe. PE signal: you write `lo + (hi - lo) / 2` without prompting (overflow safe), you can recite the difference between closed and half-open intervals, and you can pivot to lower_bound / upper_bound variants for follow-ups.

Top gotchas

  • Use `lo + (hi - lo) / 2` — not `(lo + hi) / 2` — to avoid integer overflow in Java.
  • Closed [lo, hi]: while lo <= hi; update lo = mid+1, hi = mid-1.
  • Half-open [lo, hi): while lo < hi; update lo = mid+1, hi = mid.
  • Forgetting the +1 / -1 adjustments creates infinite loops on a single-element array.

Ship-it

Java: closed-interval iterative form. Python: bisect_left + equality check.

#28 LC 74 Medium Search a 2D Matrix

2D matrix as flattened sorted array — single binary search vs staircase walk.

Problem Statement

You are given an m × n integer matrix where each row is sorted ascending and each row's first element is greater than the previous row's last element. Given a target, return whether it exists in the matrix. Must run in O(log(mn)).

Signature: boolean searchMatrix(int[][] matrix, int target)

Examples

Input: matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3 Output: true
Input: matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13 Output: false

Constraints

  • 1 <= m, n <= 100
  • -10^4 <= matrix[i][j], target <= 10^4

Approach Overview

Brute Force

Java: Linear scan

Python: Linear scan

O(mn) O(1)

Optimal #1

Java: Single binary search treating matrix as 1D

Python: Single binary search 1D

O(log(mn)) O(1)

Optimal #2

Java: Two-step: find row, then binary search within

Python: Two-step: bisect on first column, bisect within row

O(log m + log n) O(1)

Java Solutions

Linear scan O(mn) O(1)

Walk every cell. O(mn).

Pseudo-code

for each cell: if equals target return true

Complexity

Time: O(mn) — Sequential.
Space: O(1) — None.

Edge cases & gotchas

  • Violates spec.

Java code

class Solution {
    public boolean searchMatrix(final int[][] m, final int t) {
        for (int[] row : m) for (int x : row) if (x == t) return true;
        return false;
    }
}
Single binary search treating matrix as 1D O(log(mn)) O(1)

Indices [0, m·n). map idx → (idx/n, idx%n). True O(log mn).

Pseudo-code

lo, hi = 0, m*n - 1
while lo <= hi:
    mid = lo + (hi-lo)/2
    val = matrix[mid/n][mid%n]
    if val == t: return true
    elif val < t: lo = mid+1
    else: hi = mid-1

Complexity

Time: O(log(mn)) — Logarithmic.
Space: O(1) — Indices.

Edge cases & gotchas

  • Use long for mid only if mn could exceed INT_MAX (rare, but mention).
  • Empty matrix → return false; guard early.

Java code

class Solution {
    public boolean searchMatrix(final int[][] matrix, final int target) {
        if (matrix.length == 0 || matrix[0].length == 0) return false;
        final int m = matrix.length, n = matrix[0].length;
        int lo = 0, hi = m * n - 1;
        while (lo <= hi) {
            final int mid = lo + (hi - lo) / 2;
            final int v = matrix[mid / n][mid % n];
            if (v == target) return true;
            if (v < target) lo = mid + 1; else hi = mid - 1;
        }
        return false;
    }
}
Two-step: find row, then binary search within O(log m + log n) O(1)

Binary-search on first column to find the row, then binary-search the row. Same O(log mn) total.

Pseudo-code

find row r where matrix[r][0] <= t < matrix[r+1][0]; binary search row

Complexity

Time: O(log m + log n) — Two log searches.
Space: O(1) — None.

Edge cases & gotchas

  • Same asymptote as Optimal #1; subtly more boundary care.
  • Two-step generalizes to weaker invariants (only rows sorted, not strict cross-row ordering).

Java code

class Solution {
    public boolean searchMatrix(final int[][] matrix, final int target) {
        final int m = matrix.length;
        if (m == 0) return false;
        final int n = matrix[0].length;
        if (n == 0) return false;
        // Step 1: find candidate row
        int lo = 0, hi = m - 1;
        while (lo < hi) {
            final int mid = lo + (hi - lo + 1) / 2;
            if (matrix[mid][0] <= target) lo = mid; else hi = mid - 1;
        }
        if (matrix[lo][0] > target) return false;
        // Step 2: search the row
        int l = 0, r = n - 1;
        while (l <= r) {
            final int mid = l + (r - l) / 2;
            if (matrix[lo][mid] == target) return true;
            if (matrix[lo][mid] < target) l = mid + 1; else r = mid - 1;
        }
        return false;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(mn)O(log m + log n)
SpaceO(1)O(1)
Difficulty1/54/5 — two-stage boundary care
Spec complianceViolatesMeets
CleanlinessVerboseMore boundary cases
GeneralizesNoLoosens to per-row sorted (LC 240 variant)
When to useDemoLooser invariant, follow-ups
PE VerdictSingle binary search treating the matrix as a flattened sorted array. Cleanest code, optimal asymptote, easy to verify. Two-step is the right answer if the invariant is relaxed (rows independently sorted but cross-row order weaker).

Python Solutions

Linear scan O(mn) O(1)

O(mn).

Pseudo-code

any(t in row for row in matrix)

Complexity

Time: O(mn) — Sequential.
Space: O(1) — None.

Python code

from typing import List


class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        return any(target in row for row in matrix)
Single binary search 1D O(log mn) O(1)

Same as Java optimal #1.

Pseudo-code

see Java

Complexity

Time: O(log mn) — Logarithmic.
Space: O(1) — Indices.

Python code

from typing import List


class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        if not matrix or not matrix[0]:
            return False
        m, n = len(matrix), len(matrix[0])
        lo, hi = 0, m * n - 1
        while lo <= hi:
            mid = (lo + hi) // 2
            v = matrix[mid // n][mid % n]
            if v == target:
                return True
            elif v < target:
                lo = mid + 1
            else:
                hi = mid - 1
        return False


if __name__ == "__main__":
    M = [[1,3,5,7],[10,11,16,20],[23,30,34,60]]
    print(Solution().searchMatrix(M, 3))    # True
    print(Solution().searchMatrix(M, 13))   # False
Two-step: bisect on first column, bisect within row O(log m + log n) O(1)

Same idea as Java optimal #2 using bisect.

Pseudo-code

row = bisect_right(first_col, target) - 1; bisect inside that row

Complexity

Time: O(log m + log n) — Same.
Space: O(1) — None.

Python code

from typing import List
from bisect import bisect_right, bisect_left


class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        if not matrix or not matrix[0]:
            return False
        first_col = [row[0] for row in matrix]
        r = bisect_right(first_col, target) - 1
        if r < 0:
            return False
        row = matrix[r]
        i = bisect_left(row, target)
        return i < len(row) and row[i] == target

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(mn)O(log m + log n)
SpaceO(1)O(m) first-col copy
Difficulty1/53/5
Constant factorWorstBest — bisect is C
When to useDemoProduction, large matrix
PE VerdictSingle 1D binary search — same idiom as Java. Bisect-based two-step is faster constant-factor for large matrices but requires the first-column copy.

What the interviewer is really testing

Tests whether you abstract a 2D problem into 1D using the row-major mapping. PE signal: you propose the flatten-to-1D binary search instantly and you discuss when the two-step approach matters (relaxed invariant).

Top gotchas

  • Index mapping: row = idx / n, col = idx % n. Don't transpose accidentally.
  • Empty matrix or empty row guards — first thing to write.
  • Strict cross-row invariant (each row's first > previous row's last) is what makes the 1D treatment correct.

Ship-it

Single binary search over the flattened index space. Both languages.

#29 LC 875 Medium Koko Eating Bananas

Binary search on the answer — the canonical template for 'minimum k such that f(k) ≤ H'.

Problem Statement

Koko has piles of bananas. The i-th pile has piles[i] bananas. The guards return in h hours. Each hour, Koko picks a pile and eats up to k bananas (if pile has fewer, she finishes and moves on next hour). Return the MINIMUM integer k such that she can finish all piles within h hours.

Signature: int minEatingSpeed(int[] piles, int h)

Examples

Input: piles = [3,6,7,11], h = 8 Output: 4
Input: piles = [30,11,23,4,20], h = 5 Output: 30
Input: piles = [30,11,23,4,20], h = 6 Output: 23

Constraints

  • 1 <= piles.length <= 10^4
  • piles.length <= h <= 10^9
  • 1 <= piles[i] <= 10^9

Approach Overview

Brute Force

Java: Linear scan k = 1, 2, ...

Python: Linear scan k

O(max(piles) · n) O(1)

Optimal #1

Java: Binary search on k in [1, max(piles)]

Python: Binary search on k

O(n log max(piles)) O(1)

Optimal #2

Java: Binary search with tighter lower bound

Python: Tighter lower bound

O(n log max(piles)) O(1)

Java Solutions

Linear scan k = 1, 2, ... O(max(piles) · n) O(1)

Try every k from 1 upward; first k where total hours ≤ h wins.

Pseudo-code

for k = 1..max(piles): if hours(k) <= h return k

Complexity

Time: O(max(piles) · n) — Linear in answer space.
Space: O(1) — None.

Edge cases & gotchas

  • TLEs at piles[i] = 10^9.

Java code

class Solution {
    public int minEatingSpeed(final int[] piles, final int h) {
        int max = 0;
        for (int p : piles) if (p > max) max = p;
        for (int k = 1; k <= max; k++) {
            long hours = 0;
            for (int p : piles) hours += (p + k - 1) / k;
            if (hours <= h) return k;
        }
        return max;
    }
}
Binary search on k in [1, max(piles)] O(n log max(piles)) O(1)

hours(k) is monotonic non-increasing in k. Search for the smallest k with hours(k) ≤ h.

Pseudo-code

lo, hi = 1, max(piles)
while lo < hi:
    mid = lo + (hi-lo)/2
    if hours(mid) <= h: hi = mid
    else: lo = mid + 1
return lo

Complexity

Time: O(n log max(piles)) — log space × n per check.
Space: O(1) — None.

Edge cases & gotchas

  • Use ceiling division: (p + k - 1) / k. NOT (p / k) + 1.
  • Hours can overflow int if n=10^4 and piles[i]=10^9 — use long for accumulator.
  • lo = 1 because k = 0 would mean infinite hours.

Java code

class Solution {
    /**
     * Binary search on the answer. hours(k) is non-increasing in k, so we
     * search for the smallest k with hours(k) <= h.
     */
    public int minEatingSpeed(final int[] piles, final int h) {
        int lo = 1, hi = 0;
        for (final int p : piles) if (p > hi) hi = p;
        while (lo < hi) {
            final int mid = lo + (hi - lo) / 2;
            if (canFinish(piles, mid, h)) hi = mid;
            else lo = mid + 1;
        }
        return lo;
    }

    private boolean canFinish(final int[] piles, final int k, final int h) {
        long hours = 0;
        for (final int p : piles) {
            hours += (p + k - 1) / k;     // ceiling div
            if (hours > h) return false;  // early exit
        }
        return true;
    }

    public static void main(String[] args) {
        System.out.println(new Solution().minEatingSpeed(new int[]{3,6,7,11}, 8));   // 4
    }
}
Binary search with tighter lower bound O(n log max(piles)) O(1)

Lower bound = ceil(sum(piles) / h). She must average at least this fast. Tightens the search range.

Pseudo-code

lo = ceil(sum(piles) / h)
hi = max(piles)
binary search as Optimal #1

Complexity

Time: O(n log max(piles)) — Same asymptote.
Space: O(1) — None.

Edge cases & gotchas

  • sum(piles) can be ~10^13 — use long.
  • Tighter bound saves some iterations but doesn't change the asymptote.

Java code

class Solution {
    public int minEatingSpeed(final int[] piles, final int h) {
        long sum = 0; int max = 0;
        for (final int p : piles) { sum += p; if (p > max) max = p; }
        int lo = (int) ((sum + h - 1) / h);   // tighter lower bound
        int hi = max;
        while (lo < hi) {
            final int mid = lo + (hi - lo) / 2;
            long hours = 0;
            for (final int p : piles) hours += (p + mid - 1) / mid;
            if (hours <= h) hi = mid; else lo = mid + 1;
        }
        return lo;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(max·n)O(n log max) tighter constant
SpaceO(1)O(1)
Difficulty1/53/5
When to useTiny inputsProduction / hot path
PE VerdictBinary search on the answer with [1, max(piles)] bounds. The tighter sum/h lower bound is a nice optimization to mention but not necessary for correctness.

Python Solutions

Linear scan k O(max·n) O(1)

Same shape — quadratic-ish.

Pseudo-code

for k from 1: if hours(k) <= h: return k

Complexity

Time: O(max·n) — Linear in answer space.
Space: O(1) — None.

Python code

from typing import List
from math import ceil


class Solution:
    def minEatingSpeed(self, piles: List[int], h: int) -> int:
        for k in range(1, max(piles) + 1):
            if sum(ceil(p / k) for p in piles) <= h:
                return k
        return max(piles)
Binary search on k O(n log max) O(1)

Same as Java optimal #1.

Pseudo-code

see Java

Complexity

Time: O(n log max) — Logarithmic search.
Space: O(1) — Indices.

Edge cases & gotchas

  • Use -(-p // k) for ceil division — avoids float.

Python code

from typing import List


class Solution:
    def minEatingSpeed(self, piles: List[int], h: int) -> int:
        def hours(k: int) -> int:
            return sum(-(-p // k) for p in piles)   # ceil div, no float

        lo, hi = 1, max(piles)
        while lo < hi:
            mid = (lo + hi) // 2
            if hours(mid) <= h:
                hi = mid
            else:
                lo = mid + 1
        return lo


if __name__ == "__main__":
    print(Solution().minEatingSpeed([3,6,7,11], 8))   # 4
    print(Solution().minEatingSpeed([30,11,23,4,20], 5))   # 30
Tighter lower bound O(n log max) O(1)

Same as Java optimal #2.

Pseudo-code

lo = ceil(sum(piles)/h)

Complexity

Time: O(n log max) — Tighter constant.
Space: O(1) — None.

Python code

from typing import List


class Solution:
    def minEatingSpeed(self, piles: List[int], h: int) -> int:
        total = sum(piles)
        lo = -(-total // h)
        hi = max(piles)
        while lo < hi:
            mid = (lo + hi) // 2
            if sum(-(-p // mid) for p in piles) <= h:
                hi = mid
            else:
                lo = mid + 1
        return lo

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(max·n)O(n log max)
SpaceO(1)O(1)
Difficulty1/53/5
When to useDemoProduction
PE VerdictBinary search on the answer with [1, max(piles)] bounds. The tighter lower bound is a small constant-factor win.

What the interviewer is really testing

'Binary search on the answer' is one of the most-tested templates. PE signal: you recognize the monotonicity instantly, you state the predicate ('hours(k) ≤ h'), and you handle ceiling division correctly without floats. Bonus: you mention the tighter-lower-bound optimization.

Top gotchas

  • Ceiling division: (p + k - 1) / k in Java, -(-p // k) in Python. Plain p / k floors, missing the leftovers.
  • Use long in Java for the hours accumulator — n = 10^4 × 10^9 overflows int.
  • lo = 1 not 0 — k = 0 would mean infinite hours.
  • Half-open binary search returning lo (smallest valid k) is the cleanest invariant.

Ship-it

Binary search on k in [1, max(piles)] with monotonicity-based shrink. Both languages.

#30 LC 153 Medium Find Minimum in Rotated Sorted Array

Modified binary search to find the rotation pivot — compare mid to right.

Problem Statement

Given a sorted array of distinct values rotated between 1 and n times, return the minimum element. Must run in O(log n).

Signature: int findMin(int[] nums)

Examples

Input: nums = [3,4,5,1,2] Output: 1
Input: nums = [4,5,6,7,0,1,2] Output: 0
Input: nums = [11,13,15,17] Output: 11

Constraints

  • n == nums.length
  • 1 <= n <= 5000
  • All values distinct.

Approach Overview

Brute Force

Java: Linear scan

Python: min(nums)

O(n) O(1)

Optimal #1

Java: Binary search comparing mid to right

Python: Binary search vs right

O(log n) O(1)

Optimal #2

Java: Compare mid to first; handle non-rotated case explicitly

Python: Pre-check + binary search

O(log n) O(1)

Java Solutions

Linear scan O(n) O(1)

min over all elements.

Pseudo-code

min(nums)

Complexity

Time: O(n) — Sequential.
Space: O(1) — None.

Edge cases & gotchas

  • Violates spec.

Java code

class Solution {
    public int findMin(final int[] nums) {
        int m = Integer.MAX_VALUE;
        for (int x : nums) if (x < m) m = x;
        return m;
    }
}
Binary search comparing mid to right O(log n) O(1)

If nums[mid] > nums[hi], min is in [mid+1, hi]. Else min is in [lo, mid]. Converges to the pivot.

Pseudo-code

lo, hi = 0, n-1
while lo < hi:
    mid = lo + (hi-lo)/2
    if nums[mid] > nums[hi]: lo = mid + 1
    else: hi = mid
return nums[lo]

Complexity

Time: O(log n) — Halving.
Space: O(1) — None.

Edge cases & gotchas

  • Compare to RIGHT not LEFT — comparing to nums[lo] fails on non-rotated arrays (e.g., [1,2,3]).
  • lo < hi (half-open) — exactly one element survives at convergence: the minimum.
  • Distinct values are required; duplicates need a different approach (LC 154).

Java code

class Solution {
    /**
     * Binary search comparing mid to RIGHT (not left).
     * Reason: a non-rotated array has nums[mid] < nums[hi] always — without
     * the right comparison we'd incorrectly walk into the wrong half.
     */
    public int findMin(final int[] nums) {
        int lo = 0, hi = nums.length - 1;
        while (lo < hi) {
            final int mid = lo + (hi - lo) / 2;
            if (nums[mid] > nums[hi]) lo = mid + 1;
            else hi = mid;
        }
        return nums[lo];
    }

    public static void main(String[] args) {
        System.out.println(new Solution().findMin(new int[]{3,4,5,1,2}));   // 1
        System.out.println(new Solution().findMin(new int[]{11,13,15,17})); // 11
    }
}
Compare mid to first; handle non-rotated case explicitly O(log n) O(1)

Same idea but pre-checks if nums is already non-rotated.

Pseudo-code

if nums[0] <= nums[n-1]: return nums[0]
// otherwise rotated — binary search for the inflection

Complexity

Time: O(log n) — Same.
Space: O(1) — None.

Edge cases & gotchas

  • Pre-check is a small optimization — same asymptote.
  • Marginally easier to reason about for less-experienced readers.

Java code

class Solution {
    public int findMin(final int[] nums) {
        if (nums[0] <= nums[nums.length - 1]) return nums[0];
        int lo = 0, hi = nums.length - 1;
        while (lo < hi) {
            final int mid = lo + (hi - lo) / 2;
            if (nums[mid] >= nums[0]) lo = mid + 1;
            else hi = mid;
        }
        return nums[lo];
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(log n)
SpaceO(1)O(1)
Difficulty1/53/5
Spec complianceViolatesMeets
When to useDemoSlightly clearer to juniors
PE VerdictCompare mid to RIGHT, half-open binary search. Memorize this — comparing to LEFT or to nums[0] adds edge-case branches without simplifying anything.

Python Solutions

min(nums) O(n) O(1)

C-fast but O(n).

Pseudo-code

min(nums)

Complexity

Time: O(n) — Linear.
Space: O(1) — None.

Python code

from typing import List


class Solution:
    def findMin(self, nums: List[int]) -> int:
        return min(nums)
Binary search vs right O(log n) O(1)

Same as Java optimal #1.

Pseudo-code

see Java

Complexity

Time: O(log n) — Logarithmic.
Space: O(1) — None.

Python code

from typing import List


class Solution:
    def findMin(self, nums: List[int]) -> int:
        lo, hi = 0, len(nums) - 1
        while lo < hi:
            mid = (lo + hi) // 2
            if nums[mid] > nums[hi]:
                lo = mid + 1
            else:
                hi = mid
        return nums[lo]


if __name__ == "__main__":
    print(Solution().findMin([3,4,5,1,2]))      # 1
    print(Solution().findMin([4,5,6,7,0,1,2]))  # 0
Pre-check + binary search O(log n) O(1)

Same as Java optimal #2.

Pseudo-code

early return on non-rotated

Complexity

Time: O(log n) — Logarithmic.
Space: O(1) — None.

Python code

from typing import List


class Solution:
    def findMin(self, nums: List[int]) -> int:
        if nums[0] <= nums[-1]:
            return nums[0]
        lo, hi = 0, len(nums) - 1
        while lo < hi:
            mid = (lo + hi) // 2
            if nums[mid] >= nums[0]:
                lo = mid + 1
            else:
                hi = mid
        return nums[lo]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(log n)
SpaceO(1)O(1)
Difficulty1/53/5
When to usemin() demoEquivalent
PE VerdictCompare mid to right. Same idiom as Java.

What the interviewer is really testing

Modified binary search where the comparator is the trick. PE signal: you compare mid to RIGHT (not left), you justify the choice ('comparing to right handles the non-rotated case naturally'), and you pivot to the duplicate-allowed variant (LC 154) if asked.

Top gotchas

  • Compare to RIGHT (nums[hi]), not LEFT — left fails on non-rotated arrays.
  • Strict > on the comparison — equal would need a different rule (skipping right) for the duplicate variant.
  • Half-open lo < hi; converge to a single index without ever testing a specific value (no `nums[mid] == ...`).
  • Distinct-values constraint is essential here. Duplicates require LC 154's conservative shrink.

Ship-it

Half-open binary search comparing nums[mid] to nums[hi]. Both languages.

#31 LC 33 Medium Search in Rotated Sorted Array

Binary search in a rotated array — exactly one half is sorted at every step.

Problem Statement

Given a rotated sorted array of distinct values nums and a target target, return the index of target or -1. Must run in O(log n).

Signature: int search(int[] nums, int target)

Examples

Input: nums = [4,5,6,7,0,1,2], target = 0 Output: 4
Input: nums = [4,5,6,7,0,1,2], target = 3 Output: -1

Constraints

  • 1 <= nums.length <= 5000
  • All values distinct.

Approach Overview

Brute Force

Java: Linear scan

Python: Linear scan

O(n) O(1)

Optimal #1

Java: Single-pass binary search with sorted-half detection

Python: Single-pass binary search

O(log n) O(1)

Optimal #2

Java: Two-step: find pivot, then standard binary search

Python: Two-step pivot + binary search

O(log n) O(1)

Java Solutions

Linear scan O(n) O(1)

O(n).

Pseudo-code

scan

Complexity

Time: O(n) — Sequential.
Space: O(1) — None.

Edge cases & gotchas

  • Violates spec.

Java code

class Solution {
    public int search(final int[] nums, final int target) {
        for (int i = 0; i < nums.length; i++) if (nums[i] == target) return i;
        return -1;
    }
}
Single-pass binary search with sorted-half detection O(log n) O(1)

At each step, exactly one of [lo..mid] and [mid..hi] is sorted. Determine which, then check whether target falls in that half.

Pseudo-code

while lo <= hi:
    mid = lo + (hi-lo)/2
    if nums[mid] == target: return mid
    if nums[lo] <= nums[mid]:                    # left half sorted
        if nums[lo] <= target < nums[mid]: hi = mid-1
        else: lo = mid+1
    else:                                          # right half sorted
        if nums[mid] < target <= nums[hi]: lo = mid+1
        else: hi = mid-1

Complexity

Time: O(log n) — Halving.
Space: O(1) — None.

Edge cases & gotchas

  • Use <= when comparing nums[lo] to nums[mid] — the equality case (single element on left) means left is trivially sorted.
  • Bounds in the inclusion check matter: < on the open side, <= on the sorted-end side.
  • Distinct values are critical; duplicates need the LC 81 variant which can degrade to O(n).

Java code

class Solution {
    /**
     * Single-pass binary search. At each step exactly one half is sorted;
     * inclusion test on that half decides which way to go.
     */
    public int search(final int[] nums, final int target) {
        int lo = 0, hi = nums.length - 1;
        while (lo <= hi) {
            final int mid = lo + (hi - lo) / 2;
            if (nums[mid] == target) return mid;
            if (nums[lo] <= nums[mid]) {                               // left sorted
                if (nums[lo] <= target && target < nums[mid]) hi = mid - 1;
                else lo = mid + 1;
            } else {                                                   // right sorted
                if (nums[mid] < target && target <= nums[hi]) lo = mid + 1;
                else hi = mid - 1;
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        System.out.println(new Solution().search(new int[]{4,5,6,7,0,1,2}, 0));   // 4
    }
}
Two-step: find pivot, then standard binary search O(log n) O(1)

Use the find-min-in-rotated approach to locate the pivot in O(log n); then run standard binary search on whichever half contains target.

Pseudo-code

pivot = findMinIndex(nums)
if target >= nums[0]: search [0, pivot-1]
else: search [pivot, n-1]

Complexity

Time: O(log n) — Two log n searches.
Space: O(1) — None.

Edge cases & gotchas

  • Cleaner mental model — two well-known phases.
  • Slightly higher constant factor (two passes).

Java code

class Solution {
    public int search(final int[] nums, final int target) {
        final int n = nums.length;
        final int pivot = findMinIndex(nums);
        // Search [0, pivot - 1] or [pivot, n - 1]
        if (pivot == 0) return binSearch(nums, 0, n - 1, target);
        if (target >= nums[0]) return binSearch(nums, 0, pivot - 1, target);
        return binSearch(nums, pivot, n - 1, target);
    }
    private int findMinIndex(final int[] nums) {
        int lo = 0, hi = nums.length - 1;
        while (lo < hi) {
            final int mid = lo + (hi - lo) / 2;
            if (nums[mid] > nums[hi]) lo = mid + 1; else hi = mid;
        }
        return lo;
    }
    private int binSearch(final int[] nums, int lo, int hi, final int target) {
        while (lo <= hi) {
            final int mid = lo + (hi - lo) / 2;
            if (nums[mid] == target) return mid;
            if (nums[mid] < target) lo = mid + 1; else hi = mid - 1;
        }
        return -1;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(log n) (~2× constant)
SpaceO(1)O(1)
Difficulty1/53/5 — composes two simpler routines
Code lengthShortestMost lines but each line is simpler
GeneralizesNoFind-pivot reused; standard binary search reused
When to useDemoWhiteboard if the merged version's branches feel risky
PE VerdictSingle-pass with sorted-half detection. The two-step approach is clearer to write under pressure but slightly slower; both are correct.

Python Solutions

Linear scan O(n) O(1)

O(n).

Pseudo-code

scan

Complexity

Time: O(n) — Linear.
Space: O(1) — None.

Python code

from typing import List


class Solution:
    def search(self, nums: List[int], target: int) -> int:
        try:
            return nums.index(target)
        except ValueError:
            return -1
Single-pass binary search O(log n) O(1)

Same as Java optimal #1.

Pseudo-code

see Java

Complexity

Time: O(log n) — Halving.
Space: O(1) — None.

Python code

from typing import List


class Solution:
    def search(self, nums: List[int], target: int) -> int:
        lo, hi = 0, len(nums) - 1
        while lo <= hi:
            mid = (lo + hi) // 2
            if nums[mid] == target:
                return mid
            if nums[lo] <= nums[mid]:                        # left sorted
                if nums[lo] <= target < nums[mid]:
                    hi = mid - 1
                else:
                    lo = mid + 1
            else:                                            # right sorted
                if nums[mid] < target <= nums[hi]:
                    lo = mid + 1
                else:
                    hi = mid - 1
        return -1


if __name__ == "__main__":
    print(Solution().search([4,5,6,7,0,1,2], 0))   # 4
    print(Solution().search([4,5,6,7,0,1,2], 3))   # -1
Two-step pivot + binary search O(log n) O(1)

Same as Java optimal #2.

Pseudo-code

see Java

Complexity

Time: O(log n) — Two log n.
Space: O(1) — None.

Python code

from typing import List
from bisect import bisect_left


class Solution:
    def search(self, nums: List[int], target: int) -> int:
        n = len(nums)
        # Find pivot (index of min)
        lo, hi = 0, n - 1
        while lo < hi:
            mid = (lo + hi) // 2
            if nums[mid] > nums[hi]: lo = mid + 1
            else: hi = mid
        pivot = lo
        # Decide which half target lives in
        if pivot == 0 or target >= nums[0]:
            l, r = 0, pivot - 1 if pivot > 0 else n - 1
        else:
            l, r = pivot, n - 1
        while l <= r:
            mid = (l + r) // 2
            if nums[mid] == target: return mid
            if nums[mid] < target: l = mid + 1
            else: r = mid - 1
        return -1

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(log n)
SpaceO(1)O(1)
Difficulty1/53/5
When to useDemoWhiteboard fallback
PE VerdictSingle-pass with sorted-half detection. Same as Java.

What the interviewer is really testing

Half-sorted-search is the canonical 'modified binary search' problem. PE signal: you state the invariant ('exactly one half is sorted'), you correctly handle the inclusion test (which boundary uses < vs <=), and you mention duplicates degrading to O(n) for LC 81.

Top gotchas

  • Use `nums[lo] <= nums[mid]` not `<` — single-element left half is trivially sorted.
  • Inclusion test boundary: nums[lo] <= target < nums[mid] for left, nums[mid] < target <= nums[hi] for right.
  • Distinct-value constraint matters; duplicates allow nums[lo] == nums[mid] == nums[hi], which forces a conservative O(n) shrink.
  • Closed interval lo <= hi to handle the single-element case.

Ship-it

Single-pass binary search with sorted-half detection. Both languages.

#32 LC 981 Medium Time Based Key-Value Store

Per-key sorted timestamp array — binary search to find the latest set <= t.

Problem Statement

Design a class TimeMap with: set(key, value, timestamp) stores the value at the given timestamp; get(key, timestamp) returns the value associated with the largest stored timestamp_prev ≤ timestamp, or empty string if none exists. Timestamps for a given key are guaranteed strictly increasing across calls.

Signature: class TimeMap { void set(String key, String value, int timestamp); String get(String key, int timestamp); }

Examples

set("foo","bar",1); get("foo",1) → "bar"; get("foo",3) → "bar"; set("foo","bar2",4); get("foo",4) → "bar2"; get("foo",5) → "bar2"

Constraints

  • 1 <= key.length, value.length <= 100
  • 0 <= timestamp <= 10^7
  • All timestamps for a given key are strictly increasing.
  • At most 2*10^5 calls total.

Approach Overview

Brute Force

Java: Per-key list, linear scan from the end

Python: Per-key list, reverse linear scan

set O(1), get O(n_key) O(total set calls)

Optimal #1

Java: Per-key parallel arrays + binary search (rightmost ≤ t)

Python: Per-key (timestamps, values) + bisect

set O(1) amortized; get O(log n_key) O(total)

Optimal #2

Java: TreeMap.floorEntry per key

Python: SortedList from sortedcontainers (3rd-party)

set O(log n_key); get O(log n_key) O(total)

Java Solutions

Per-key list, linear scan from the end set O(1), get O(n_key) O(total set calls)

Walk backwards from end; first timestamp <= t wins.

Pseudo-code

for entry in reversed(history[key]): if entry.t <= t: return entry.v

Complexity

Time: set O(1), get O(n_key) — Linear in key history.
Space: O(total set calls) — Each set adds one entry.

Edge cases & gotchas

  • TLEs at 2*10^5 sets on the same key.

Java code

import java.util.*;
class TimeMap {
    private final Map<String, List<String[]>> map = new HashMap<>();
    public void set(String k, String v, int t) {
        map.computeIfAbsent(k, x -> new ArrayList<>()).add(new String[]{String.valueOf(t), v});
    }
    public String get(String k, int t) {
        final List<String[]> hist = map.getOrDefault(k, Collections.emptyList());
        for (int i = hist.size() - 1; i >= 0; i--) {
            if (Integer.parseInt(hist.get(i)[0]) <= t) return hist.get(i)[1];
        }
        return "";
    }
}
Per-key parallel arrays + binary search (rightmost ≤ t) set O(1) amortized; get O(log n_key) O(total)

Map<key, (timestamps, values)>. Set appends; get binary-searches the timestamps for the rightmost ≤ t. Strictly-increasing timestamps means no sort needed.

Pseudo-code

set(k, v, t): map[k].timestamps.append(t); map[k].values.append(v)
get(k, t):
    list = map[k]
    use binary search to find largest i with timestamps[i] <= t
    return values[i] or '' if none

Complexity

Time: set O(1) amortized; get O(log n_key) — Append + binary search.
Space: O(total) — Two parallel lists per key.

Edge cases & gotchas

  • Strictly-increasing timestamps per key means we can append without sorting.
  • Binary search for 'largest ≤ t' = upper_bound(t) - 1 (exclusive).
  • Empty result when t < first timestamp — return empty string.

Java code

import java.util.*;

class TimeMap {
    private final Map<String, List<int[]>> stamps = new HashMap<>();
    private final Map<String, List<String>> vals = new HashMap<>();

    public void set(final String key, final String value, final int timestamp) {
        stamps.computeIfAbsent(key, k -> new ArrayList<>()).add(new int[]{timestamp});
        vals.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
    }

    public String get(final String key, final int timestamp) {
        final List<int[]> ts = stamps.get(key);
        if (ts == null || ts.isEmpty() || ts.get(0)[0] > timestamp) return "";
        // Binary search: largest index i with ts[i] <= timestamp
        int lo = 0, hi = ts.size() - 1, ans = -1;
        while (lo <= hi) {
            final int mid = lo + (hi - lo) / 2;
            if (ts.get(mid)[0] <= timestamp) { ans = mid; lo = mid + 1; }
            else hi = mid - 1;
        }
        return ans == -1 ? "" : vals.get(key).get(ans);
    }
}
TreeMap.floorEntry per key set O(log n_key); get O(log n_key) O(total)

Use Java's TreeMap, which gives O(log n) floorEntry — exact 'rightmost ≤ t' for free. Most concise.

Pseudo-code

stamps[key] = TreeMap<int, String>(); get: stamps[key].floorEntry(t)

Complexity

Time: set O(log n_key); get O(log n_key) — TreeMap operations are O(log n).
Space: O(total) — Tree per key.

Edge cases & gotchas

  • TreeMap is a red-black tree — insertion is O(log n) vs append+constant for the parallel-array approach.
  • Cleaner code; slightly slower constant factor for set.
  • Python equivalent uses sortedcontainers.SortedList — not stdlib.

Java code

import java.util.*;

class TimeMap {
    private final Map<String, TreeMap<Integer, String>> map = new HashMap<>();
    public void set(final String key, final String value, final int t) {
        map.computeIfAbsent(key, k -> new TreeMap<>()).put(t, value);
    }
    public String get(final String key, final int t) {
        final TreeMap<Integer, String> tm = map.get(key);
        if (tm == null) return "";
        final Map.Entry<Integer, String> e = tm.floorEntry(t);
        return e == null ? "" : e.getValue();
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
setO(1)O(log n)
getO(n)O(log n)
SpaceO(total)O(total) + tree pointers
Difficulty1/52/5 — TreeMap does the work
Constant factor (set)BestWorse — tree balancing
Constant factor (get)WorstSlower — pointer chasing
When to useTiny nConcise code priority
PE VerdictPer-key parallel arrays + binary search. Set is O(1), get is O(log n), zero tree-pointer overhead. Reach for TreeMap when code conciseness or generic ordered operations matter (lower bound, range queries, etc.).

Python Solutions

Per-key list, reverse linear scan set O(1), get O(n) O(total)

Same shape — TLEs.

Pseudo-code

scan history[key] backwards

Complexity

Time: set O(1), get O(n) — Linear get.
Space: O(total) — List per key.

Python code

from collections import defaultdict


class TimeMap:
    def __init__(self):
        self.data = defaultdict(list)
    def set(self, key: str, value: str, timestamp: int) -> None:
        self.data[key].append((timestamp, value))
    def get(self, key: str, timestamp: int) -> str:
        for t, v in reversed(self.data[key]):
            if t <= timestamp:
                return v
        return ""
Per-key (timestamps, values) + bisect set O(1); get O(log n) O(total)

Same as Java optimal #1. Use bisect_right and step back by one.

Pseudo-code

i = bisect_right(stamps, t) - 1; return values[i] or ''

Complexity

Time: set O(1); get O(log n) — Bisect.
Space: O(total) — Two parallel lists per key.

Edge cases & gotchas

  • bisect_right(t) - 1 = -1 if t < first timestamp; guard with `if i >= 0`.
  • Strictly-increasing-per-key timestamps mean no sort is required.

Python code

from collections import defaultdict
from bisect import bisect_right
from typing import Dict, List, Tuple


class TimeMap:
    def __init__(self) -> None:
        self.stamps: Dict[str, List[int]] = defaultdict(list)
        self.values: Dict[str, List[str]] = defaultdict(list)

    def set(self, key: str, value: str, timestamp: int) -> None:
        self.stamps[key].append(timestamp)
        self.values[key].append(value)

    def get(self, key: str, timestamp: int) -> str:
        ts = self.stamps.get(key)
        if not ts:
            return ""
        i = bisect_right(ts, timestamp) - 1
        return self.values[key][i] if i >= 0 else ""


if __name__ == "__main__":
    tm = TimeMap()
    tm.set("foo", "bar", 1)
    print(tm.get("foo", 1))   # bar
    print(tm.get("foo", 3))   # bar
    tm.set("foo", "bar2", 4)
    print(tm.get("foo", 4))   # bar2
    print(tm.get("foo", 5))   # bar2
SortedList from sortedcontainers (3rd-party) set O(log n); get O(log n) O(total)

If sortedcontainers is allowed, SortedList provides O(log n) insertion in arbitrary order. Useful when timestamps aren't guaranteed monotone.

Pseudo-code

use SortedList of (t, v) tuples; bisect for lookup

Complexity

Time: set O(log n); get O(log n) — Sorted insertion.
Space: O(total) — Sorted structure.

Edge cases & gotchas

  • Not stdlib — only use if interviewer allows.
  • Overkill if timestamps are guaranteed strictly increasing.

Python code

# Requires `pip install sortedcontainers`
from sortedcontainers import SortedList
from collections import defaultdict
from typing import Dict


class TimeMap:
    def __init__(self) -> None:
        self.data: Dict[str, SortedList] = defaultdict(SortedList)

    def set(self, key: str, value: str, timestamp: int) -> None:
        self.data[key].add((timestamp, value))

    def get(self, key: str, timestamp: int) -> str:
        sl = self.data.get(key)
        if not sl:
            return ""
        i = sl.bisect_right((timestamp, chr(127))) - 1   # rightmost <= timestamp
        return sl[i][1] if i >= 0 else ""

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
setO(1)O(log n)
getO(n)O(log n)
SpaceO(total)O(total)
Difficulty1/53/5
Stdlib only?YesNo — needs sortedcontainers
When to useDemoNon-monotone timestamps
PE Verdictbisect on parallel lists. Stdlib, O(log n) get, exploits the monotone-timestamps invariant. SortedList is the right answer if that invariant is dropped.

What the interviewer is really testing

Design + binary search composition. PE signal: you exploit the strictly-increasing-timestamps invariant to skip the sort, you use bisect / lower_bound correctly to find 'largest ≤ t', and you compare the parallel-array approach to TreeMap / SortedList with crisp constant-factor reasoning.

Top gotchas

  • 'Largest ≤ t' = bisect_right(t) - 1, NOT bisect_left(t). bisect_left finds the insertion point at or BEFORE matching elements.
  • When bisect_right returns 0, the target is smaller than all entries — return empty string.
  • Strictly-increasing-per-key timestamps means we can append without sorting; without that guarantee, switch to SortedList / TreeMap.
  • Java: Integer parsing inside the brute-force baseline allocates — avoid in hot paths.

Ship-it

Java: parallel ArrayLists + binary search. Python: bisect_right - 1 over parallel lists.

#33 LC 4 Hard Median of Two Sorted Arrays

Binary search on partition point — find the median of two sorted arrays in O(log min(m,n)).

Problem Statement

Given two sorted arrays nums1 and nums2, return the median of the combined array. Must run in O(log(m+n)).

Signature: double findMedianSortedArrays(int[] nums1, int[] nums2)

Examples

Input: nums1 = [1,3], nums2 = [2] Output: 2.0
Input: nums1 = [1,2], nums2 = [3,4] Output: 2.5

Constraints

  • 0 <= m, n <= 1000
  • 1 <= m+n <= 2000

Approach Overview

Brute Force

Java: Merge and pick middle

Python: Merge and pick

O(m+n) O(m+n)

Optimal #1

Java: Binary search on partition (smaller array)

Python: Partition binary search

O(log min(m,n)) O(1)

Optimal #2

Java: K-th element via binary search (recursive)

Python: K-th element elimination

O(log(m+n)) O(log(m+n)) recursion

Java Solutions

Merge and pick middle O(m+n) O(m+n)

Merge into one sorted array; return middle element(s).

Pseudo-code

merge sorted; return middle

Complexity

Time: O(m+n) — Linear merge.
Space: O(m+n) — Merged array.

Edge cases & gotchas

  • Violates O(log) requirement.

Java code

class Solution {
    public double findMedianSortedArrays(int[] a, int[] b) {
        int m = a.length, n = b.length, total = m + n;
        int[] merged = new int[total];
        int i=0, j=0, k=0;
        while (i<m && j<n) merged[k++] = a[i]<=b[j] ? a[i++] : b[j++];
        while (i<m) merged[k++] = a[i++];
        while (j<n) merged[k++] = b[j++];
        if (total%2==1) return merged[total/2];
        return (merged[total/2-1] + merged[total/2]) / 2.0;
    }
}
Binary search on partition (smaller array) O(log min(m,n)) O(1)

Partition both arrays so left side has (m+n+1)/2 elements; binary search the cut in the smaller array.

Pseudo-code

ensure m <= n (swap if needed)
lo=0, hi=m
while lo <= hi:
    i = (lo+hi)/2; j = (m+n+1)/2 - i
    L1 = i==0 ? -inf : a[i-1]; R1 = i==m ? +inf : a[i]
    L2 = j==0 ? -inf : b[j-1]; R2 = j==n ? +inf : b[j]
    if L1 <= R2 and L2 <= R1:
        if (m+n) even: return (max(L1,L2)+min(R1,R2))/2
        else: return max(L1,L2)
    elif L1 > R2: hi = i-1
    else: lo = i+1

Complexity

Time: O(log min(m,n)) — Log on smaller array.
Space: O(1) — Scalars.

Edge cases & gotchas

  • Swap to make `a` smaller — bounds search complexity.
  • Use INT_MIN/MAX as sentinels for edge partitions.

Java code

class Solution {
    public double findMedianSortedArrays(int[] a, int[] b) {
        if (a.length > b.length) { int[] t = a; a = b; b = t; }
        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 L1 = i==0 ? Integer.MIN_VALUE : a[i-1];
            int R1 = i==m ? Integer.MAX_VALUE : a[i];
            int L2 = j==0 ? Integer.MIN_VALUE : b[j-1];
            int R2 = j==n ? Integer.MAX_VALUE : b[j];
            if (L1 <= R2 && L2 <= R1) {
                if ((m+n)%2 == 1) return Math.max(L1, L2);
                return (Math.max(L1,L2) + Math.min(R1,R2)) / 2.0;
            } else if (L1 > R2) hi = i - 1;
            else lo = i + 1;
        }
        return 0.0;
    }
}
K-th element via binary search (recursive) O(log(m+n)) O(log(m+n)) recursion

Reframe as 'find k-th smallest'; eliminate k/2 elements per step.

Pseudo-code

findKth(a, b, k): compare a[k/2-1] vs b[k/2-1]; discard k/2

Complexity

Time: O(log(m+n)) — Log eliminations.
Space: O(log(m+n)) recursion — Stack.

Edge cases & gotchas

  • Recursion can be flattened to iteration.

Java code

class Solution {
    public double findMedianSortedArrays(int[] a, int[] b) {
        int t = a.length + b.length;
        if (t%2==1) return findKth(a,0,b,0,t/2+1);
        return (findKth(a,0,b,0,t/2) + findKth(a,0,b,0,t/2+1)) / 2.0;
    }
    private int findKth(int[] a, int ai, int[] b, int bi, int k) {
        if (ai >= a.length) return b[bi+k-1];
        if (bi >= b.length) return a[ai+k-1];
        if (k == 1) return Math.min(a[ai], b[bi]);
        int half = k/2;
        int aVal = ai+half-1 < a.length ? a[ai+half-1] : Integer.MAX_VALUE;
        int bVal = bi+half-1 < b.length ? b[bi+half-1] : Integer.MAX_VALUE;
        if (aVal < bVal) return findKth(a, ai+half, b, bi, k-half);
        return findKth(a, ai, b, bi+half, k-half);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(m+n)O(log(m+n))
SpaceO(m+n)O(log(m+n))
Difficulty1/54/5 — k-th elim
Spec complianceViolatesMeets
When to useDemoPedagogy
PE VerdictPartition-based binary search on the smaller array. Hard but the canonical answer.

Python Solutions

Merge and pick O(m+n) O(m+n)

Linear merge.

Pseudo-code

merge then index

Complexity

Time: O(m+n) — Linear.
Space: O(m+n) — Merged.

Python code

from typing import List
class Solution:
    def findMedianSortedArrays(self, a: List[int], b: List[int]) -> float:
        m = sorted(a + b); n = len(m)
        return m[n//2] if n%2 else (m[n//2-1] + m[n//2]) / 2
Partition binary search O(log min(m,n)) O(1)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(log min(m,n)) — Log.
Space: O(1) — Scalars.

Edge cases & gotchas

  • Use float('-inf') / float('inf') as sentinels.

Python code

from typing import List
class Solution:
    def findMedianSortedArrays(self, a: List[int], b: List[int]) -> float:
        if len(a) > len(b): a, b = b, a
        m, n = len(a), len(b)
        half = (m+n+1) // 2
        lo, hi = 0, m
        while lo <= hi:
            i = (lo+hi)//2; j = half - i
            L1 = a[i-1] if i>0 else float('-inf')
            R1 = a[i]   if i<m else float('inf')
            L2 = b[j-1] if j>0 else float('-inf')
            R2 = b[j]   if j<n else float('inf')
            if L1 <= R2 and L2 <= R1:
                if (m+n)%2: return max(L1, L2)
                return (max(L1,L2) + min(R1,R2)) / 2
            elif L1 > R2: hi = i-1
            else: lo = i+1
        return 0.0
K-th element elimination O(log(m+n)) O(log(m+n))

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(log(m+n)) — Log.
Space: O(log(m+n)) — Recursion.

Python code

from typing import List
class Solution:
    def findMedianSortedArrays(self, a: List[int], b: List[int]) -> float:
        def kth(ai, bi, k):
            if ai >= len(a): return b[bi+k-1]
            if bi >= len(b): return a[ai+k-1]
            if k == 1: return min(a[ai], b[bi])
            half = k//2
            av = a[ai+half-1] if ai+half-1 < len(a) else float('inf')
            bv = b[bi+half-1] if bi+half-1 < len(b) else float('inf')
            if av < bv: return kth(ai+half, bi, k-half)
            return kth(ai, bi+half, k-half)
        t = len(a) + len(b)
        if t%2: return kth(0,0,t//2+1)
        return (kth(0,0,t//2) + kth(0,0,t//2+1)) / 2

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(m+n)O(log(m+n))
SpaceO(m+n)O(log)
Difficulty1/54/5
When to useDemoPedagogy
PE VerdictPartition binary search — same as Java.

What the interviewer is really testing

Hardest LC interview problem on partition reasoning. PE signal: search the SMALLER array, use ±∞ sentinels, derive `half = (m+n+1)/2` for even/odd unification.

Top gotchas

  • Swap arrays so binary search runs on the smaller one — bounds the work.
  • half = (m+n+1)/2 unifies even and odd cases via the +1.
  • Use INT_MIN/INT_MAX (or ±inf) as sentinels for edge partitions.

Ship-it

Partition-based binary search on the smaller array.

Linked List

#34 LC 206 Easy Reverse Linked List

Reverse a singly-linked list — iterative pointer swap vs recursion.

Problem Statement

Given the head of a singly linked list, reverse the list and return the new head.

Signature: ListNode reverseList(ListNode head)

Examples

1->2->3->4->5 → 5->4->3->2->1
[] → []

Constraints

  • 0 <= n <= 5000
  • -5000 <= Node.val <= 5000

Approach Overview

Brute Force

Java: Push to stack then rebuild

Python: List to array, reverse, rebuild

O(n) O(n)

Optimal #1

Java: Iterative pointer reversal

Python: Iterative

O(n) O(1)

Optimal #2

Java: Recursive reversal

Python: Recursive

O(n) O(n) recursion

Java Solutions

Push to stack then rebuild O(n) O(n)

Push all values, then build new list. Wasteful.

Pseudo-code

stack each node; pop to build new list

Complexity

Time: O(n) — Two passes.
Space: O(n) — Stack of n.

Edge cases & gotchas

  • Allocates n new nodes.

Java code

import java.util.*;
class ListNode { int val; ListNode next; ListNode(int v){val=v;} }
class Solution {
    public ListNode reverseList(ListNode head) {
        Deque<Integer> s = new ArrayDeque<>();
        for (ListNode c = head; c != null; c = c.next) s.push(c.val);
        ListNode dummy = new ListNode(0), tail = dummy;
        while (!s.isEmpty()) { tail.next = new ListNode(s.pop()); tail = tail.next; }
        return dummy.next;
    }
}
Iterative pointer reversal O(n) O(1)

Walk through, redirecting next pointers as you go. O(1) extra.

Pseudo-code

prev=null; cur=head
while cur:
    next=cur.next; cur.next=prev; prev=cur; cur=next
return prev

Complexity

Time: O(n) — Single pass.
Space: O(1) — Three pointers.

Edge cases & gotchas

  • Empty list → returns null naturally.
  • Save next BEFORE rewiring cur.next.

Java code

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null, cur = head;
        while (cur != null) {
            ListNode next = cur.next;
            cur.next = prev;
            prev = cur;
            cur = next;
        }
        return prev;
    }
}
Recursive reversal O(n) O(n) recursion

Recurse to end, then rewire on the way back.

Pseudo-code

if not head or not head.next: return head
newHead = reverse(head.next)
head.next.next = head
head.next = null
return newHead

Complexity

Time: O(n) — Each node visited once.
Space: O(n) recursion — Call stack.

Edge cases & gotchas

  • Stack overflow for n > ~10^4.

Java code

class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode newHead = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return newHead;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(n)O(n) stack
Difficulty1/53/5
When to useDemo onlyPedagogy / small lists
PE VerdictIterative — O(1) space, no stack overflow risk.

Python Solutions

List to array, reverse, rebuild O(n) O(n)

Wasteful.

Pseudo-code

collect; rebuild reversed

Complexity

Time: O(n) — Two passes.
Space: O(n) — Array.

Python code

class ListNode:
    def __init__(self, val=0, nxt=None): self.val=val; self.next=nxt
class Solution:
    def reverseList(self, head):
        vals = []
        c = head
        while c: vals.append(c.val); c = c.next
        dummy = ListNode(); tail = dummy
        for v in reversed(vals): tail.next = ListNode(v); tail = tail.next
        return dummy.next
Iterative O(n) O(1)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(n) — One pass.
Space: O(1) — Pointers.

Python code

class Solution:
    def reverseList(self, head):
        prev, cur = None, head
        while cur:
            nxt = cur.next
            cur.next = prev
            prev = cur
            cur = nxt
        return prev
Recursive O(n) O(n)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(n) — One visit each.
Space: O(n) — Stack.

Edge cases & gotchas

  • sys.setrecursionlimit may be needed.

Python code

class Solution:
    def reverseList(self, head):
        if not head or not head.next: return head
        new_head = self.reverseList(head.next)
        head.next.next = head
        head.next = None
        return new_head

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(n)O(n)
Difficulty1/53/5
When to useDemoPedagogy
PE VerdictIterative — same as Java.

What the interviewer is really testing

Foundational LL pointer manipulation. PE signal: save next BEFORE rewiring; iterative beats recursive on space.

Top gotchas

  • Save cur.next BEFORE overwriting cur.next = prev — otherwise you lose the rest of the list.
  • Initialize prev = null; the new tail's next must be null.
  • Recursive risks stack overflow for n > ~10^4 in Java (default 512KB stack).

Ship-it

Iterative pointer reversal, both languages.

#35 LC 21 Easy Merge Two Sorted Lists

Two-pointer merge into a single sorted linked list — dummy head pattern.

Problem Statement

Merge two sorted singly-linked lists into one sorted list by splicing nodes (no new allocations of values).

Signature: ListNode mergeTwoLists(ListNode l1, ListNode l2)

Examples

1->2->4, 1->3->4 → 1->1->2->3->4->4
[], [] → []

Constraints

  • 0 <= total <= 100
  • Each list is non-decreasing.

Approach Overview

Brute Force

Java: Collect values, sort, rebuild

Python: Collect + sort

O((m+n) log(m+n)) O(m+n)

Optimal #1

Java: Iterative splice with dummy head

Python: Iterative splice

O(m+n) O(1)

Optimal #2

Java: Recursive merge

Python: Recursive

O(m+n) O(m+n) stack

Java Solutions

Collect values, sort, rebuild O((m+n) log(m+n)) O(m+n)

Throws away the in-place advantage.

Pseudo-code

collect into list, sort, build new

Complexity

Time: O((m+n) log(m+n)) — Sort dominates.
Space: O(m+n) — Value array.

Edge cases & gotchas

  • Wasteful — doesn't use sorted-ness.

Java code

import java.util.*;
class ListNode { int val; ListNode next; ListNode(int v){val=v;} }
class Solution {
    public ListNode mergeTwoLists(ListNode a, ListNode b) {
        List<Integer> vals = new ArrayList<>();
        while (a != null) { vals.add(a.val); a = a.next; }
        while (b != null) { vals.add(b.val); b = b.next; }
        Collections.sort(vals);
        ListNode dummy = new ListNode(0), t = dummy;
        for (int v : vals) { t.next = new ListNode(v); t = t.next; }
        return dummy.next;
    }
}
Iterative splice with dummy head O(m+n) O(1)

Walk both lists; splice the smaller into a growing tail. Dummy head removes special-case handling.

Pseudo-code

dummy = new node; tail = dummy
while l1 and l2:
    if l1.val <= l2.val: tail.next = l1; l1 = l1.next
    else: tail.next = l2; l2 = l2.next
    tail = tail.next
tail.next = l1 != null ? l1 : l2
return dummy.next

Complexity

Time: O(m+n) — Each node visited once.
Space: O(1) — Two pointers + dummy.

Edge cases & gotchas

  • Dummy head obviates 'is this the first node?' branching.
  • Append remaining tail directly — both lists are sorted, no further comparisons needed.

Java code

class Solution {
    public ListNode mergeTwoLists(ListNode a, ListNode b) {
        ListNode dummy = new ListNode(0), tail = dummy;
        while (a != null && b != null) {
            if (a.val <= b.val) { tail.next = a; a = a.next; }
            else { tail.next = b; b = b.next; }
            tail = tail.next;
        }
        tail.next = (a != null) ? a : b;
        return dummy.next;
    }
}
Recursive merge O(m+n) O(m+n) stack

Elegant but O(n) stack.

Pseudo-code

if !l1: return l2; if !l2: return l1
if l1.val <= l2.val: l1.next = merge(l1.next, l2); return l1
else: l2.next = merge(l1, l2.next); return l2

Complexity

Time: O(m+n) — One frame per merge step.
Space: O(m+n) stack — Stack.

Edge cases & gotchas

  • Stack overflow risk on long lists.

Java code

class Solution {
    public ListNode mergeTwoLists(ListNode a, ListNode b) {
        if (a == null) return b;
        if (b == null) return a;
        if (a.val <= b.val) { a.next = mergeTwoLists(a.next, b); return a; }
        b.next = mergeTwoLists(a, b.next); return b;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO((m+n) log)O(m+n)
SpaceO(m+n)O(m+n) stack
Difficulty1/52/5
When to useDon'tCode-golf
PE VerdictIterative with dummy head — O(1) extra, no recursion limit.

Python Solutions

Collect + sort O((m+n) log) O(m+n)

Wasteful.

Pseudo-code

collect, sort, rebuild

Complexity

Time: O((m+n) log) — Sort.
Space: O(m+n) — List.

Python code

class ListNode:
    def __init__(self, val=0, nxt=None): self.val=val; self.next=nxt
class Solution:
    def mergeTwoLists(self, a, b):
        vals = []
        while a: vals.append(a.val); a = a.next
        while b: vals.append(b.val); b = b.next
        vals.sort()
        dummy = ListNode(); t = dummy
        for v in vals: t.next = ListNode(v); t = t.next
        return dummy.next
Iterative splice O(m+n) O(1)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(m+n) — Linear.
Space: O(1) — Pointers.

Python code

class Solution:
    def mergeTwoLists(self, a, b):
        dummy = ListNode(); tail = dummy
        while a and b:
            if a.val <= b.val: tail.next = a; a = a.next
            else: tail.next = b; b = b.next
            tail = tail.next
        tail.next = a or b
        return dummy.next
Recursive O(m+n) O(m+n)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(m+n) — Linear.
Space: O(m+n) — Stack.

Python code

class Solution:
    def mergeTwoLists(self, a, b):
        if not a: return b
        if not b: return a
        if a.val <= b.val:
            a.next = self.mergeTwoLists(a.next, b); return a
        b.next = self.mergeTwoLists(a, b.next); return b

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO((m+n) log)O(m+n)
SpaceO(m+n)O(m+n)
Difficulty1/52/5
When to useDon'tPedagogy
PE VerdictIterative + dummy head.

What the interviewer is really testing

Linked-list mechanics + dummy-head pattern. PE signal: dummy head, append remaining tail in one assignment.

Top gotchas

  • Dummy head removes the 'first node?' branch — fold both languages around it.
  • After the main loop, tail.next = a or b (whichever is non-null) appends the rest.
  • DO NOT reset the unused list's next — splice means we reuse those nodes.

Ship-it

Iterative splice with dummy head.

#36 LC 141 Easy Linked List Cycle

Floyd's tortoise-and-hare cycle detection — O(1) space.

Problem Statement

Return true if a linked list contains a cycle.

Signature: boolean hasCycle(ListNode head)

Examples

1->2->3->4->2(cycle) → true
1->2 → false

Constraints

  • 0 <= n <= 10^4

Approach Overview

Brute Force

Java: HashSet of visited

Python: set of visited

O(n) O(n)

Optimal #1

Java: Floyd's tortoise and hare

Python: Floyd

O(n) O(1)

Optimal #2

Java: Destructive: rewrite next pointers as you go

Python: Destructive sentinel

O(n) O(1)

Java Solutions

HashSet of visited O(n) O(n)

Record each node; revisit → cycle.

Pseudo-code

for each node: if in set return true; add to set

Complexity

Time: O(n) — Linear.
Space: O(n) — n node refs.

Java code

import java.util.*;
class ListNode { int val; ListNode next; ListNode(int v){val=v;} }
class Solution {
    public boolean hasCycle(ListNode head) {
        Set<ListNode> seen = new HashSet<>();
        for (ListNode c = head; c != null; c = c.next) {
            if (!seen.add(c)) return true;
        }
        return false;
    }
}
Floyd's tortoise and hare O(n) O(1)

Slow moves 1, fast moves 2. If they meet, cycle exists.

Pseudo-code

slow = fast = head
while fast and fast.next:
    slow = slow.next; fast = fast.next.next
    if slow == fast: return true
return false

Complexity

Time: O(n) — Fast laps slow within n steps.
Space: O(1) — Two pointers.

Edge cases & gotchas

  • Check fast and fast.next BOTH non-null.
  • Empty/single-node list → no cycle.

Java code

class Solution {
    public 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;
    }
}
Destructive: rewrite next pointers as you go O(n) O(1)

Set visited node.next to a sentinel/self. Detects cycle on hit.

Pseudo-code

for each node: if next == sentinel return true; else next = sentinel

Complexity

Time: O(n) — Linear.
Space: O(1) — Sentinel.

Edge cases & gotchas

  • DESTRUCTIVE — mutates the list. Only acceptable if caller permits.

Java code

class Solution {
    private static final ListNode VISITED = new ListNode(0);
    public boolean hasCycle(ListNode head) {
        while (head != null) {
            if (head.next == VISITED) return true;
            ListNode next = head.next;
            head.next = VISITED;
            head = next;
        }
        return false;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(n)O(1)
Difficulty1/53/5
Mutates inputNoYes
When to useRead-only & memory-OKWhen mutation is allowed and memory is tight
PE VerdictFloyd's algorithm — O(1) space and non-destructive.

Python Solutions

set of visited O(n) O(n)

Same shape.

Pseudo-code

set membership

Complexity

Time: O(n) — Linear.
Space: O(n) — Set.

Python code

class ListNode:
    def __init__(self, val=0, nxt=None): self.val=val; self.next=nxt
class Solution:
    def hasCycle(self, head) -> bool:
        seen = set()
        c = head
        while c:
            if id(c) in seen: return True
            seen.add(id(c)); c = c.next
        return False
Floyd O(n) O(1)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — Two pointers.

Python code

class Solution:
    def hasCycle(self, head) -> bool:
        slow = fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            if slow is fast: return True
        return False
Destructive sentinel O(n) O(1)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — Sentinel.

Python code

class Solution:
    SENTINEL = ListNode(0)
    def hasCycle(self, head) -> bool:
        while head:
            if head.next is self.SENTINEL: return True
            nxt = head.next; head.next = self.SENTINEL; head = nxt
        return False

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(n)O(1)
Difficulty1/53/5
When to useDemoMemory-tight + mutable OK
PE VerdictFloyd's algorithm.

What the interviewer is really testing

Classic Floyd's algorithm. PE signal: O(1) space, articulate why they MUST meet inside a cycle.

Top gotchas

  • Check fast AND fast.next non-null before fast.next.next.
  • Use `is` (identity) in Python, not `==` (would call __eq__).
  • If cycle length k, fast laps slow within k iterations after slow enters the cycle.

Ship-it

Floyd's tortoise and hare.

#37 LC 143 Medium Reorder List

Reorder L0→Ln→L1→Ln-1... — find middle, reverse second half, interleave.

Problem Statement

Reorder a linked list in place so that L0 → Ln-1 → L1 → Ln-2 → ...

Signature: void reorderList(ListNode head)

Examples

1->2->3->4 → 1->4->2->3
1->2->3->4->5 → 1->5->2->4->3

Constraints

  • 1 <= n <= 5*10^4

Approach Overview

Brute Force

Java: Array of nodes, two-pointer rebuild

Python: Node array + index rebuild

O(n) O(n)

Optimal #1

Java: Find middle (slow/fast), reverse second half, merge

Python: Mid + reverse + merge

O(n) O(1)

Optimal #2

Java: Deque-based interleave

Python: Deque interleave

O(n) O(n)

Java Solutions

Array of nodes, two-pointer rebuild O(n) O(n)

Collect all nodes into an array, then re-link by indices.

Pseudo-code

arr = nodes; l=0, r=n-1; interleave; last.next = null

Complexity

Time: O(n) — Linear.
Space: O(n) — Node array.

Edge cases & gotchas

  • Allocates array — opt1 is O(1) extra.

Java code

import java.util.*;
class ListNode { int val; ListNode next; ListNode(int v){val=v;} }
class Solution {
    public void reorderList(ListNode head) {
        List<ListNode> a = new ArrayList<>();
        for (ListNode c = head; c != null; c = c.next) a.add(c);
        int l = 0, r = a.size()-1;
        while (l < r) {
            a.get(l).next = a.get(r);
            l++;
            if (l == r) break;
            a.get(r).next = a.get(l);
            r--;
        }
        a.get(l).next = null;
    }
}
Find middle (slow/fast), reverse second half, merge O(n) O(1)

Three classic LL ops composed: midpoint, reverse, interleave.

Pseudo-code

1) slow/fast → slow ends at middle
2) reverse from slow.next; slow.next = null
3) merge first half with reversed second half

Complexity

Time: O(n) — Three linear passes.
Space: O(1) — Pointers only.

Edge cases & gotchas

  • Sever the first half at the midpoint BEFORE reversing.
  • Odd n: first half is longer; merge handles it naturally.

Java code

class Solution {
    public void reorderList(ListNode head) {
        if (head == null || head.next == null) return;
        // 1) find middle
        ListNode slow = head, fast = head;
        while (fast.next != null && fast.next.next != null) {
            slow = slow.next; fast = fast.next.next;
        }
        // 2) reverse second half
        ListNode second = slow.next;
        slow.next = null;
        ListNode prev = null, cur = second;
        while (cur != null) {
            ListNode nxt = cur.next; cur.next = prev; prev = cur; cur = nxt;
        }
        // 3) merge
        ListNode a = head, b = prev;
        while (b != null) {
            ListNode an = a.next, bn = b.next;
            a.next = b; b.next = an;
            a = an; b = bn;
        }
    }
}
Deque-based interleave O(n) O(n)

Push all nodes to deque, alternate pollFirst/pollLast.

Pseudo-code

deque all nodes; alternate front/back, splice

Complexity

Time: O(n) — Linear.
Space: O(n) — Deque of n.

Edge cases & gotchas

  • More memory; clearer code.

Java code

import java.util.*;
class Solution {
    public void reorderList(ListNode head) {
        Deque<ListNode> dq = new ArrayDeque<>();
        for (ListNode c = head; c != null; c = c.next) dq.offer(c);
        ListNode dummy = new ListNode(0), tail = dummy;
        boolean front = true;
        while (!dq.isEmpty()) {
            tail.next = front ? dq.pollFirst() : dq.pollLast();
            tail = tail.next;
            front = !front;
        }
        tail.next = null;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(n)O(n)
Difficulty2/52/5
When to useDemoCode clarity
PE VerdictCompose mid+reverse+merge for O(1) space.

Python Solutions

Node array + index rebuild O(n) O(n)

Same.

Pseudo-code

indices rebuild

Complexity

Time: O(n) — Linear.
Space: O(n) — Array.

Python code

class Solution:
    def reorderList(self, head):
        arr = []
        c = head
        while c: arr.append(c); c = c.next
        l, r = 0, len(arr)-1
        while l < r:
            arr[l].next = arr[r]; l += 1
            if l == r: break
            arr[r].next = arr[l]; r -= 1
        arr[l].next = None
Mid + reverse + merge O(n) O(1)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(n) — Three passes.
Space: O(1) — Pointers.

Python code

class Solution:
    def reorderList(self, head) -> None:
        if not head or not head.next: return
        slow = fast = head
        while fast.next and fast.next.next:
            slow = slow.next; fast = fast.next.next
        # reverse second half
        prev, cur = None, slow.next
        slow.next = None
        while cur:
            nxt = cur.next; cur.next = prev; prev = cur; cur = nxt
        # merge
        a, b = head, prev
        while b:
            an, bn = a.next, b.next
            a.next = b; b.next = an
            a, b = an, bn
Deque interleave O(n) O(n)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(n) — Deque.

Python code

from collections import deque
class Solution:
    def reorderList(self, head) -> None:
        dq = deque()
        c = head
        while c: dq.append(c); c = c.next
        dummy = ListNode(); tail = dummy; front = True
        while dq:
            tail.next = dq.popleft() if front else dq.pop()
            tail = tail.next; front = not front
        tail.next = None

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(n)O(n)
Difficulty2/52/5
When to useDemoClarity
PE VerdictMid+reverse+merge.

What the interviewer is really testing

Composition of three classic LL ops. PE signal: do all three in O(1) space, sever the list at the midpoint cleanly.

Top gotchas

  • Set slow.next = None BEFORE reversing — otherwise the merge creates a cycle.
  • Midpoint via slow/fast: use `while fast.next && fast.next.next` (not the usual fast/fast.next) — leaves slow at end of first half.
  • Merge loop driven by `b` (the shorter/equal half) — first half is longer for odd n.

Ship-it

Find mid, reverse second half, interleave merge.

#38 LC 19 Medium Remove Nth Node From End of List

Two-pointer gap of n — single pass with dummy head.

Problem Statement

Remove the n-th node from the end of a singly linked list and return the head.

Signature: ListNode removeNthFromEnd(ListNode head, int n)

Examples

1->2->3->4->5, n=2 → 1->2->3->5
1, n=1 → []

Constraints

  • 1 <= size <= 30
  • 1 <= n <= size

Approach Overview

Brute Force

Java: Two passes: count then delete

Python: Two passes

O(n) O(1)

Optimal #1

Java: Two pointers with gap n

Python: Two pointers with gap

O(n) O(1)

Optimal #2

Java: Recursive depth tracking

Python: Recursive

O(n) O(n) stack

Java Solutions

Two passes: count then delete O(n) O(1)

Count length; delete (length - n)th node.

Pseudo-code

len = count; idx = len - n; delete that node

Complexity

Time: O(n) — Two passes.
Space: O(1) — Counter.

Edge cases & gotchas

  • Two passes — opt1 does one.

Java code

class ListNode { int val; ListNode next; ListNode(int v){val=v;} }
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        int len = 0;
        for (ListNode c = head; c != null; c = c.next) len++;
        ListNode dummy = new ListNode(0); dummy.next = head;
        ListNode prev = dummy;
        for (int i = 0; i < len - n; i++) prev = prev.next;
        prev.next = prev.next.next;
        return dummy.next;
    }
}
Two pointers with gap n O(n) O(1)

Advance fast n+1 steps; then move both until fast hits null. slow.next is the one to delete.

Pseudo-code

dummy → head
fast = slow = dummy
for _ in n+1: fast = fast.next
while fast: fast = fast.next; slow = slow.next
slow.next = slow.next.next

Complexity

Time: O(n) — Single pass.
Space: O(1) — Two pointers.

Edge cases & gotchas

  • Dummy head handles deleting the actual head node uniformly.
  • n+1 steps for fast so slow ends up just before the target.

Java code

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0); dummy.next = head;
        ListNode fast = dummy, slow = dummy;
        for (int i = 0; i <= n; i++) fast = fast.next;
        while (fast != null) { fast = fast.next; slow = slow.next; }
        slow.next = slow.next.next;
        return dummy.next;
    }
}
Recursive depth tracking O(n) O(n) stack

Recurse to end; on the way back, decrement n and unlink when n == 0.

Pseudo-code

recurse to null; return decrementing counter; unlink at hit

Complexity

Time: O(n) — One frame per node.
Space: O(n) stack — Stack.

Edge cases & gotchas

  • Stack overflow for long lists.

Java code

class Solution {
    private int n;
    public ListNode removeNthFromEnd(ListNode head, int nn) {
        this.n = nn;
        return helper(head);
    }
    private ListNode helper(ListNode node) {
        if (node == null) return null;
        node.next = helper(node.next);
        if (--n == 0) return node.next;   // skip self
        return node;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(1)O(n) stack
Passes21
Difficulty1/53/5
When to useWhiteboard simplicityRecursion-friendly contexts
PE VerdictTwo-pointer single pass with dummy head.

Python Solutions

Two passes O(n) O(1)

Same.

Pseudo-code

count, then delete

Complexity

Time: O(n) — Two passes.
Space: O(1) — Counter.

Python code

class Solution:
    def removeNthFromEnd(self, head, n: int):
        length = 0; c = head
        while c: length += 1; c = c.next
        dummy = ListNode(0, head); prev = dummy
        for _ in range(length - n): prev = prev.next
        prev.next = prev.next.next
        return dummy.next
Two pointers with gap O(n) O(1)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(n) — Single pass.
Space: O(1) — Pointers.

Python code

class Solution:
    def removeNthFromEnd(self, head, n: int):
        dummy = ListNode(0, head)
        fast = slow = dummy
        for _ in range(n + 1):
            fast = fast.next
        while fast:
            fast = fast.next; slow = slow.next
        slow.next = slow.next.next
        return dummy.next
Recursive O(n) O(n)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(n) — One frame each.
Space: O(n) — Stack.

Python code

class Solution:
    def removeNthFromEnd(self, head, n: int):
        self.n = n
        def rec(node):
            if not node: return None
            node.next = rec(node.next)
            self.n -= 1
            return node.next if self.n == 0 else node
        return rec(head)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(1)O(n)
Passes21
When to useDemoPedagogy
PE VerdictTwo pointers single pass.

What the interviewer is really testing

Dummy head + two-pointer-with-gap. PE signal: single pass, dummy head removes the 'delete head' branch.

Top gotchas

  • Advance fast n+1 steps (not n) so slow ends BEFORE the target.
  • Dummy head: simplifies deleting the actual head node.
  • Problem guarantees n ≤ size, so no bounds checks needed in the gap advance.

Ship-it

Two-pointer single pass with dummy head.

#39 LC 138 Medium Copy List with Random Pointer

Deep-copy LL with random pointers — hash map of originals→copies, or interleave-then-split.

Problem Statement

Deep copy a linked list where each node has next and random pointer (any node or null).

Signature: Node copyRandomList(Node head)

Examples

Standard LC123 cases

Constraints

  • 0 <= n <= 1000

Approach Overview

Brute Force

Java: Two passes with HashMap<old, new>

Python: Two passes with dict

O(n) O(n)

Optimal #1

Java: Single-pass HashMap with computeIfAbsent

Python: Single-pass dict.setdefault

O(n) O(n)

Optimal #2

Java: Interleave clones inline (O(1) space)

Python: Interleave inline

O(n) O(1)

Java Solutions

Two passes with HashMap<old, new> O(n) O(n)

Pass 1: clone nodes into a map. Pass 2: wire next/random via lookups.

Pseudo-code

p1: map[old] = new Node(old.val); p2: map[old].next = map[old.next], same for random

Complexity

Time: O(n) — Two passes.
Space: O(n) — HashMap n.

Edge cases & gotchas

  • HashMap auto-handles null (returns null).
  • Simple and clear.

Java code

import java.util.*;
class Node { int val; Node next, random; Node(int v){val=v;} }
class Solution {
    public Node copyRandomList(Node head) {
        Map<Node, Node> map = new HashMap<>();
        for (Node c = head; c != null; c = c.next) map.put(c, new Node(c.val));
        for (Node c = head; c != null; c = c.next) {
            map.get(c).next = map.get(c.next);
            map.get(c).random = map.get(c.random);
        }
        return map.get(head);
    }
}
Single-pass HashMap with computeIfAbsent O(n) O(n)

One pass; lazy-create both current and pointed nodes.

Pseudo-code

map = {}
def getOrCreate(node): if node and node not in map: map[node] = Node(node.val); return map.get(node)
for c in list: getOrCreate(c).next = getOrCreate(c.next); getOrCreate(c).random = getOrCreate(c.random)

Complexity

Time: O(n) — One pass.
Space: O(n) — HashMap.

Edge cases & gotchas

  • Single pass — readable and minimal.

Java code

import java.util.*;
class Solution {
    public Node copyRandomList(Node head) {
        Map<Node, Node> map = new HashMap<>();
        for (Node c = head; c != null; c = c.next) {
            Node copy = map.computeIfAbsent(c, k -> new Node(k.val));
            if (c.next != null) copy.next = map.computeIfAbsent(c.next, k -> new Node(k.val));
            if (c.random != null) copy.random = map.computeIfAbsent(c.random, k -> new Node(k.val));
        }
        return map.get(head);
    }
}
Interleave clones inline (O(1) space) O(n) O(1)

Insert each clone after its original; wire randoms via clone.random = original.random.next; split the list.

Pseudo-code

1) for each n: insert n' after n (n.next = n')
2) for each n: n'.random = n.random.next (or null)
3) split: even = clones, odd = originals

Complexity

Time: O(n) — Three passes.
Space: O(1) — Pointers.

Edge cases & gotchas

  • Step 2 only works AFTER step 1 — the .next of any random points to its clone.
  • Step 3 restores original list and extracts clone list — restore is required if input must not be mutated.

Java code

class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) return null;
        // 1) interleave
        for (Node c = head; c != null; c = c.next.next) {
            Node copy = new Node(c.val);
            copy.next = c.next;
            c.next = copy;
        }
        // 2) wire randoms
        for (Node c = head; c != null; c = c.next.next) {
            if (c.random != null) c.next.random = c.random.next;
        }
        // 3) split
        Node copyHead = head.next;
        for (Node c = head; c != null; c = c.next) {
            Node copy = c.next;
            c.next = copy.next;
            if (copy.next != null) copy.next = copy.next.next;
        }
        return copyHead;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(n)O(1) extra
Difficulty2/55/5
Mutates inputNoYes temporarily
When to useWhiteboardMemory-tight
PE VerdictHashMap single-pass — clearest. Interleave is the elegant O(1)-space answer to mention.

Python Solutions

Two passes with dict O(n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Two passes.
Space: O(n) — dict.

Python code

class Node:
    def __init__(self, x, next=None, random=None): self.val=x; self.next=next; self.random=random
class Solution:
    def copyRandomList(self, head):
        if not head: return None
        m = {}
        c = head
        while c: m[c] = Node(c.val); c = c.next
        c = head
        while c:
            m[c].next = m.get(c.next)
            m[c].random = m.get(c.random)
            c = c.next
        return m[head]
Single-pass dict.setdefault O(n) O(n)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(n) — One pass.
Space: O(n) — dict.

Python code

class Solution:
    def copyRandomList(self, head):
        if not head: return None
        m = {}
        def get(n):
            if n is None: return None
            if n not in m: m[n] = Node(n.val)
            return m[n]
        c = head
        while c:
            copy = get(c)
            copy.next = get(c.next)
            copy.random = get(c.random)
            c = c.next
        return m[head]
Interleave inline O(n) O(1)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(n) — Three passes.
Space: O(1) — Pointers.

Python code

class Solution:
    def copyRandomList(self, head):
        if not head: return None
        c = head
        while c:
            copy = Node(c.val); copy.next = c.next; c.next = copy; c = copy.next
        c = head
        while c:
            if c.random: c.next.random = c.random.next
            c = c.next.next
        new_head = head.next
        c = head
        while c:
            copy = c.next; c.next = copy.next
            if copy.next: copy.next = copy.next.next
            c = c.next
        return new_head

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(n)O(1)
Difficulty2/55/5
When to useWhiteboardMemory-tight
PE Verdictdict single-pass.

What the interviewer is really testing

Classic deep-copy with non-trivial pointers. PE signal: HashMap default, interleave-then-split as the O(1) trick.

Top gotchas

  • Random can be null OR self OR any other node — use a map.get (returns null for unknown).
  • Interleave: original.next.random = original.random.next is the elegant wire-up.
  • Restore original list in step 3 if the caller forbids mutation.

Ship-it

HashMap single-pass; mention interleave as the O(1) optimal.

#40 LC 2 Medium Add Two Numbers

Add two reversed-digit linked lists with carry — schoolbook addition.

Problem Statement

Given two non-empty linked lists representing non-negative integers with digits in reverse order, return their sum as a linked list.

Signature: ListNode addTwoNumbers(ListNode l1, ListNode l2)

Examples

2->4->3 (342) + 5->6->4 (465) = 7->0->8 (807)

Constraints

  • 1 <= length(L) <= 100
  • 0 <= digit <= 9
  • No leading zeros except single 0

Approach Overview

Brute Force

Java: Convert to BigInteger, add, build back

Python: Parse to int, add, rebuild

O(n+m) O(n+m)

Optimal #1

Java: Single-pass elementary addition with carry

Python: Iterative with carry

O(max(n,m)) O(max(n,m))

Optimal #2

Java: Recursive addition

Python: Recursive

O(max) O(max) stack

Java Solutions

Convert to BigInteger, add, build back O(n+m) O(n+m)

Parse both lists as numbers, add, reconstruct. Defeats the purpose.

Pseudo-code

to_int(l1) + to_int(l2) → from_int → list

Complexity

Time: O(n+m) — Linear.
Space: O(n+m) — BigInts.

Edge cases & gotchas

  • Defeats the algorithmic exercise.

Java code

import java.math.BigInteger;
class ListNode { int val; ListNode next; ListNode(int v){val=v;} }
class Solution {
    public ListNode addTwoNumbers(ListNode a, ListNode b) {
        StringBuilder sa = new StringBuilder(), sb = new StringBuilder();
        for (ListNode c = a; c != null; c = c.next) sa.insert(0, c.val);
        for (ListNode c = b; c != null; c = c.next) sb.insert(0, c.val);
        BigInteger sum = new BigInteger(sa.toString()).add(new BigInteger(sb.toString()));
        String s = sum.toString();
        ListNode dummy = new ListNode(0), t = dummy;
        for (int i = s.length()-1; i >= 0; i--) { t.next = new ListNode(s.charAt(i)-'0'); t = t.next; }
        return dummy.next;
    }
}
Single-pass elementary addition with carry O(max(n,m)) O(max(n,m))

Walk both, add digits + carry, build result. Handles unequal lengths and final carry.

Pseudo-code

carry = 0; dummy; tail = dummy
while a or b or carry:
    s = (a.val if a) + (b.val if b) + carry
    tail.next = new (s%10); carry = s/10
    advance a, b, tail
return dummy.next

Complexity

Time: O(max(n,m)) — Single pass.
Space: O(max(n,m)) — Output.

Edge cases & gotchas

  • Loop guard includes carry — final carry creates a new node.
  • Advance a/b only if non-null.

Java code

class Solution {
    public ListNode addTwoNumbers(ListNode a, ListNode b) {
        ListNode dummy = new ListNode(0), tail = dummy;
        int carry = 0;
        while (a != null || b != null || carry > 0) {
            int s = carry;
            if (a != null) { s += a.val; a = a.next; }
            if (b != null) { s += b.val; b = b.next; }
            tail.next = new ListNode(s % 10);
            tail = tail.next;
            carry = s / 10;
        }
        return dummy.next;
    }
}
Recursive addition O(max) O(max) stack

Recurse with carry as parameter.

Pseudo-code

rec(a, b, carry): base case → carry node; else sum, recurse

Complexity

Time: O(max) — Linear.
Space: O(max) stack — Stack.

Edge cases & gotchas

  • Stack overflow on long lists.

Java code

class Solution {
    public ListNode addTwoNumbers(ListNode a, ListNode b) { return rec(a, b, 0); }
    private ListNode rec(ListNode a, ListNode b, int carry) {
        if (a == null && b == null && carry == 0) return null;
        int s = carry + (a != null ? a.val : 0) + (b != null ? b.val : 0);
        ListNode node = new ListNode(s % 10);
        node.next = rec(a != null ? a.next : null, b != null ? b.next : null, s / 10);
        return node;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n+m)O(max)
SpaceO(n+m)O(max) stack
Difficulty2/53/5
When to useDemo (overkill)Pedagogy
PE VerdictIterative single pass — handle carry in the loop condition.

Python Solutions

Parse to int, add, rebuild O(n+m) O(n+m)

Same.

Pseudo-code

int + int → digits

Complexity

Time: O(n+m) — Linear.
Space: O(n+m) — int.

Python code

class Solution:
    def addTwoNumbers(self, a, b):
        def to_int(n):
            x = 0; m = 1
            while n: x += n.val * m; m *= 10; n = n.next
            return x
        s = to_int(a) + to_int(b)
        dummy = ListNode(); t = dummy
        if s == 0: return ListNode(0)
        while s: t.next = ListNode(s % 10); s //= 10; t = t.next
        return dummy.next
Iterative with carry O(max) O(max)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(max) — One pass.
Space: O(max) — Output.

Python code

class Solution:
    def addTwoNumbers(self, a, b):
        dummy = ListNode(); tail = dummy; carry = 0
        while a or b or carry:
            s = carry
            if a: s += a.val; a = a.next
            if b: s += b.val; b = b.next
            tail.next = ListNode(s % 10)
            tail = tail.next
            carry = s // 10
        return dummy.next
Recursive O(max) O(max)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(max) — Linear.
Space: O(max) — Stack.

Python code

class Solution:
    def addTwoNumbers(self, a, b, carry=0):
        if not a and not b and not carry: return None
        s = carry + (a.val if a else 0) + (b.val if b else 0)
        node = ListNode(s % 10)
        node.next = self.addTwoNumbers(a.next if a else None, b.next if b else None, s // 10)
        return node

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n+m)O(max)
SpaceO(n+m)O(max)
Difficulty2/53/5
When to useDemoRecursive style
PE VerdictIterative with carry.

What the interviewer is really testing

Schoolbook addition + LL traversal. PE signal: carry in the loop condition, single dummy-head pattern, no special-casing.

Top gotchas

  • Loop condition includes carry — without it you miss a trailing 1 for inputs like 9->9 + 1.
  • Advance a and b independently — different lengths are normal.
  • Use integer division for carry: s // 10 in Python, s / 10 in Java (s is int).

Ship-it

Iterative with carry, dummy head.

#41 LC 287 Medium Find the Duplicate Number

Floyd's cycle detection on indices — find duplicate in O(1) space without mutation.

Problem Statement

Given an array nums of n+1 integers each in [1, n], exactly one value appears more than once. Return that value. Must use O(1) extra space and must not modify the input.

Signature: int findDuplicate(int[] nums)

Examples

[1,3,4,2,2] → 2
[3,1,3,4,2] → 3

Constraints

  • 1 <= n <= 10^5
  • All integers in [1, n]

Approach Overview

Brute Force

Java: HashSet of seen

Python: set of seen

O(n) O(n)

Optimal #1

Java: Floyd's cycle detection on next = nums[i]

Python: Floyd

O(n) O(1)

Optimal #2

Java: Binary search on value range

Python: Binary search on value range

O(n log n) O(1)

Java Solutions

HashSet of seen O(n) O(n)

First repeat hit. Violates O(1) space.

Pseudo-code

for x: if x in set return x; add

Complexity

Time: O(n) — Linear.
Space: O(n) — Set.

Edge cases & gotchas

  • Violates space constraint.

Java code

import java.util.*;
class Solution {
    public int findDuplicate(int[] nums) {
        Set<Integer> s = new HashSet<>();
        for (int x : nums) if (!s.add(x)) return x;
        return -1;
    }
}
Floyd's cycle detection on next = nums[i] O(n) O(1)

Treat array as linked list where i → nums[i]. Duplicates create a cycle; cycle entrance = duplicate value.

Pseudo-code

1) tortoise/hare until meet inside cycle
2) reset one to start; advance both by 1 until they meet → entrance

Complexity

Time: O(n) — Two phases linear.
Space: O(1) — Two pointers.

Edge cases & gotchas

  • Values are in [1, n], so nums[i] is a valid index into nums (n+1 array).
  • The duplicate value IS the cycle entrance because multiple indices point to it.

Java code

class Solution {
    public int findDuplicate(int[] nums) {
        int slow = nums[0], fast = nums[0];
        do { slow = nums[slow]; fast = nums[nums[fast]]; } while (slow != fast);
        slow = nums[0];
        while (slow != fast) { slow = nums[slow]; fast = nums[fast]; }
        return slow;
    }
}
Binary search on value range O(n log n) O(1)

For each mid, count elements ≤ mid. If > mid, duplicate is in [lo, mid]; else [mid+1, hi].

Pseudo-code

lo=1, hi=n
while lo < hi:
    mid = (lo+hi)/2; cnt = count(x <= mid)
    if cnt > mid: hi = mid
    else: lo = mid+1
return lo

Complexity

Time: O(n log n) — Log range × n count.
Space: O(1) — Scalars.

Edge cases & gotchas

  • Works even when more than one value is duplicated (still finds one).
  • Slower than Floyd; non-mutating; mentions pigeonhole intuition.

Java code

class Solution {
    public int findDuplicate(int[] nums) {
        int n = nums.length - 1;
        int lo = 1, hi = n;
        while (lo < hi) {
            int mid = (lo + hi) / 2, cnt = 0;
            for (int x : nums) if (x <= mid) cnt++;
            if (cnt > mid) hi = mid;
            else lo = mid + 1;
        }
        return lo;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n log n)
SpaceO(n)O(1)
Difficulty1/53/5 — pigeonhole binary search
Spec complianceViolatesMeets
When to useQuickIf Floyd doesn't click
PE VerdictFloyd's algorithm — O(n) and O(1). The cycle-on-index insight is the entire learning.

Python Solutions

set of seen O(n) O(n)

Same.

Pseudo-code

membership probe

Complexity

Time: O(n) — Linear.
Space: O(n) — Set.

Python code

from typing import List
class Solution:
    def findDuplicate(self, nums: List[int]) -> int:
        s = set()
        for x in nums:
            if x in s: return x
            s.add(x)
        return -1
Floyd O(n) O(1)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — Pointers.

Python code

from typing import List
class Solution:
    def findDuplicate(self, nums: List[int]) -> int:
        slow = fast = nums[0]
        while True:
            slow = nums[slow]
            fast = nums[nums[fast]]
            if slow == fast: break
        slow = nums[0]
        while slow != fast:
            slow = nums[slow]
            fast = nums[fast]
        return slow
Binary search on value range O(n log n) O(1)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(n log n) — Log × count.
Space: O(1) — Scalars.

Python code

from typing import List
class Solution:
    def findDuplicate(self, nums: List[int]) -> int:
        lo, hi = 1, len(nums) - 1
        while lo < hi:
            mid = (lo + hi) // 2
            cnt = sum(1 for x in nums if x <= mid)
            if cnt > mid: hi = mid
            else: lo = mid + 1
        return lo

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n log n)
SpaceO(n)O(1)
Difficulty1/53/5
When to useQuickPedagogy
PE VerdictFloyd.

What the interviewer is really testing

Floyd's algorithm applied to indices. PE signal: see the array as a function i → nums[i], explain why a duplicate forces a cycle.

Top gotchas

  • Phase 1 uses do-while (or break-on-equal AFTER moving) since slow=fast initially.
  • Phase 2: reset slow to start, advance both by 1 — entrance is the duplicate.
  • Values in [1, n] is essential — if 0 were allowed nums[0] could be 0 (self-loop at index 0 trivially).

Ship-it

Floyd's cycle detection on the index function.

#42 LC 146 Medium LRU Cache

HashMap + doubly-linked list — O(1) get and put.

Problem Statement

Design an LRU cache with O(1) get and put.

Signature: class LRUCache { LRUCache(int capacity); int get(int key); void put(int key, int value); }

Examples

capacity 2: put(1,1); put(2,2); get(1)→1; put(3,3) evicts 2; get(2)→-1

Constraints

  • 1 <= capacity <= 3000
  • 0 <= key, value <= 10^4
  • Up to 2*10^5 calls

Approach Overview

Brute Force

Java: LinkedHashMap with access order

Python: dict-only with linear scan for LRU

O(1) get/put O(capacity)

Optimal #1

Java: HashMap<K, Node> + doubly-linked list (head=MRU, tail=LRU)

Python: OrderedDict with move_to_end

O(1) get/put O(capacity)

Optimal #2

Java: OrderedDict / LinkedHashMap with manual move

Python: dict + doubly-linked list (explicit)

O(1) O(capacity)

Java Solutions

LinkedHashMap with access order O(1) get/put O(capacity)

Java's LinkedHashMap maintains insertion or access order and lets you override removeEldestEntry. Cheating but correct.

Pseudo-code

extend LinkedHashMap<K,V>(cap, 0.75, true); override removeEldestEntry

Complexity

Time: O(1) get/put — Hash + linked list under the hood.
Space: O(capacity) — n entries.

Edge cases & gotchas

  • JDK doesn't expose this as configurable beyond override — but it's exactly LRU.

Java code

import java.util.*;
class LRUCache extends LinkedHashMap<Integer, Integer> {
    private final int cap;
    public LRUCache(int capacity) { super(capacity, 0.75f, true); this.cap = capacity; }
    public int get(int key) { return super.getOrDefault(key, -1); }
    public void put(int key, int value) { super.put(key, value); }
    @Override protected boolean removeEldestEntry(Map.Entry<Integer,Integer> e) { return size() > cap; }
}
HashMap<K, Node> + doubly-linked list (head=MRU, tail=LRU) O(1) get/put O(capacity)

Explicit DLL with dummy head/tail; HashMap maps key to its node. Standard PE answer.

Pseudo-code

get: if hit, move node to head, return value
put: if hit, update value and move to head; else add at head; if size > cap, evict tail.prev

Complexity

Time: O(1) get/put — Constant per op.
Space: O(capacity) — Map + nodes.

Edge cases & gotchas

  • Dummy head and tail sentinels eliminate null-checks.
  • Always unlink before adding to head — keeps DLL invariants intact.

Java code

import java.util.*;
class LRUCache {
    private static class Node {
        int key, val; Node prev, next;
        Node(int k, int v) { key = k; val = v; }
    }
    private final int cap;
    private final Map<Integer, Node> map = new HashMap<>();
    private final Node head = new Node(0,0), tail = new Node(0,0);
    public LRUCache(int capacity) {
        cap = capacity;
        head.next = tail; tail.prev = head;
    }
    private void remove(Node n) {
        n.prev.next = n.next; n.next.prev = n.prev;
    }
    private void addFirst(Node n) {
        n.next = head.next; n.prev = head;
        head.next.prev = n; head.next = n;
    }
    public int get(int key) {
        Node n = map.get(key);
        if (n == null) return -1;
        remove(n); addFirst(n);
        return n.val;
    }
    public void put(int key, int value) {
        Node n = map.get(key);
        if (n != null) { n.val = value; remove(n); addFirst(n); return; }
        if (map.size() == cap) {
            Node lru = tail.prev;
            remove(lru); map.remove(lru.key);
        }
        Node nn = new Node(key, value);
        map.put(key, nn);
        addFirst(nn);
    }
}
OrderedDict / LinkedHashMap with manual move O(1) O(capacity)

Use ordered map; on get/put, remove and re-insert to move to end. Same complexity.

Pseudo-code

on access: remove then put back at end; evict from front

Complexity

Time: O(1) — Constant.
Space: O(capacity) — Map.

Edge cases & gotchas

  • LinkedHashMap implements this exactly; Python OrderedDict has move_to_end().

Java code

import java.util.*;
class LRUCache {
    private final int cap;
    private final LinkedHashMap<Integer, Integer> map = new LinkedHashMap<>();
    public LRUCache(int capacity) { cap = capacity; }
    public int get(int key) {
        Integer v = map.remove(key);
        if (v == null) return -1;
        map.put(key, v);   // re-insert moves to end (MRU)
        return v;
    }
    public void put(int key, int value) {
        if (map.remove(key) == null && map.size() == cap) {
            map.remove(map.keySet().iterator().next());
        }
        map.put(key, value);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(1)O(1)
SpaceO(cap)O(cap)
Difficulty1/5 — but 'cheaty'2/5 — uses stdlib
Interview signalWeak — relies on frameworkMid — clean but doesn't show DLL
When to useProduction (Java)Production (Python)
PE VerdictExplicit DLL + HashMap for interviews. Use LinkedHashMap / OrderedDict in production for brevity.

Python Solutions

dict-only with linear scan for LRU O(n) O(cap)

O(n) per op.

Pseudo-code

track access counter; scan for min on eviction

Complexity

Time: O(n) — Scan to find LRU.
Space: O(cap) — dict.

Python code

class LRUCache:
    def __init__(self, capacity: int):
        self.cap = capacity; self.d = {}; self.t = 0; self.access = {}
    def get(self, key: int) -> int:
        if key not in self.d: return -1
        self.t += 1; self.access[key] = self.t
        return self.d[key]
    def put(self, key: int, value: int) -> None:
        if key not in self.d and len(self.d) == self.cap:
            ev = min(self.access, key=self.access.get); del self.d[ev]; del self.access[ev]
        self.t += 1; self.d[key] = value; self.access[key] = self.t
OrderedDict with move_to_end O(1) O(cap)

Pythonic — same as LinkedHashMap.

Pseudo-code

on access: move_to_end; on overflow: popitem(last=False)

Complexity

Time: O(1) — Constant.
Space: O(cap) — OrderedDict.

Python code

from collections import OrderedDict
class LRUCache:
    def __init__(self, capacity: int):
        self.cap = capacity
        self.d: "OrderedDict[int,int]" = OrderedDict()
    def get(self, key: int) -> int:
        if key not in self.d: return -1
        self.d.move_to_end(key)
        return self.d[key]
    def put(self, key: int, value: int) -> None:
        if key in self.d: self.d.move_to_end(key)
        elif len(self.d) == self.cap: self.d.popitem(last=False)
        self.d[key] = value
dict + doubly-linked list (explicit) O(1) O(cap)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(1) — Constant.
Space: O(cap) — Map + nodes.

Edge cases & gotchas

  • Shows you can build the DS without stdlib helpers.

Python code

class _Node:
    __slots__ = ("k","v","prev","next")
    def __init__(self, k=0, v=0): self.k=k; self.v=v; self.prev=None; self.next=None

class LRUCache:
    def __init__(self, capacity: int):
        self.cap = capacity; self.m = {}
        self.head = _Node(); self.tail = _Node()
        self.head.next = self.tail; self.tail.prev = self.head
    def _remove(self, n):
        n.prev.next = n.next; n.next.prev = n.prev
    def _add_first(self, n):
        n.next = self.head.next; n.prev = self.head
        self.head.next.prev = n; self.head.next = n
    def get(self, key: int) -> int:
        n = self.m.get(key)
        if not n: return -1
        self._remove(n); self._add_first(n); return n.v
    def put(self, key: int, value: int) -> None:
        n = self.m.get(key)
        if n: n.v = value; self._remove(n); self._add_first(n); return
        if len(self.m) == self.cap:
            lru = self.tail.prev; self._remove(lru); del self.m[lru.k]
        nn = _Node(key, value); self.m[key] = nn; self._add_first(nn)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(1)
SpaceO(cap)O(cap)
Difficulty2/55/5 — explicit DLL
When to useDon'tInterview (proves understanding)
PE VerdictOrderedDict in production. Explicit DLL in interviews if asked.

What the interviewer is really testing

Design + DLL/HashMap composition. PE signal: build the DLL with dummy sentinels; reason about pointer rewiring.

Top gotchas

  • Dummy head and tail nodes remove all null-check branching.
  • On hit, remove THEN add-first — separate operations keep DLL invariants.
  • Evict from tail.prev (true LRU), not from head.

Ship-it

Java: explicit DLL+HashMap. Python: OrderedDict.

#43 LC 23 Hard Merge K Sorted Lists

Merge k sorted lists — min-heap or divide-and-conquer pairwise merge.

Problem Statement

You are given an array of k sorted linked lists; merge them into one sorted list and return its head.

Signature: ListNode mergeKLists(ListNode[] lists)

Examples

[[1,4,5],[1,3,4],[2,6]] → 1,1,2,3,4,4,5,6

Constraints

  • 0 <= k <= 10^4
  • 0 <= total nodes <= 10^4

Approach Overview

Brute Force

Java: Repeated merge two

Python: Sequential merge two

O(k·n) — each pass merges all into growing accumulator O(1)

Optimal #1

Java: Min-heap of head nodes

Python: heapq with tiebreaker

O(N log k) where N = total nodes O(k)

Optimal #2

Java: Divide-and-conquer pairwise merge

Python: Iterative pairwise D&C

O(N log k) O(log k) recursion

Java Solutions

Repeated merge two O(k·n) — each pass merges all into growing accumulator O(1)

Merge list[0] with list[1], result with list[2], etc.

Pseudo-code

result = []
for list in lists: result = mergeTwo(result, list)

Complexity

Time: O(k·n) — each pass merges all into growing accumulator — Quadratic in number of merge ops.
Space: O(1) — Splicing.

Edge cases & gotchas

  • Slower than D&C by a log k factor.

Java code

class ListNode { int val; ListNode next; ListNode(int v){val=v;} }
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        ListNode out = null;
        for (ListNode l : lists) out = mergeTwo(out, l);
        return out;
    }
    private ListNode mergeTwo(ListNode a, ListNode b) {
        ListNode dummy = new ListNode(0), t = dummy;
        while (a != null && b != null) {
            if (a.val <= b.val) { t.next = a; a = a.next; }
            else { t.next = b; b = b.next; }
            t = t.next;
        }
        t.next = (a != null) ? a : b;
        return dummy.next;
    }
}
Min-heap of head nodes O(N log k) where N = total nodes O(k)

Push all heads; pop the smallest, append, push that node's next.

Pseudo-code

heap = min-heap of (val, node) of all heads
while heap:
    n = heap.pop()
    tail.next = n; tail = n
    if n.next: heap.push(n.next)

Complexity

Time: O(N log k) where N = total nodes — Each node pushed/popped once.
Space: O(k) — Heap of k.

Edge cases & gotchas

  • Use a tiebreaker (node index) in case of duplicate values, since ListNode isn't Comparable.
  • Skip null lists when pushing initial heads.

Java code

import java.util.*;
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        PriorityQueue<ListNode> pq = new PriorityQueue<>((a, b) -> a.val - b.val);
        for (ListNode l : lists) if (l != null) pq.offer(l);
        ListNode dummy = new ListNode(0), tail = dummy;
        while (!pq.isEmpty()) {
            ListNode n = pq.poll();
            tail.next = n; tail = n;
            if (n.next != null) pq.offer(n.next);
        }
        return dummy.next;
    }
}
Divide-and-conquer pairwise merge O(N log k) O(log k) recursion

Pair lists, merge two-by-two, halving each round. log k rounds × n merge work.

Pseudo-code

while lists.size > 1: merge pairs into next round

Complexity

Time: O(N log k) — Log levels of merges.
Space: O(log k) recursion — Stack.

Edge cases & gotchas

  • Iterative pairwise avoids recursion overhead.
  • Same asymptote as heap; sometimes faster in practice due to cache.

Java code

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        if (lists.length == 0) return null;
        int interval = 1;
        while (interval < lists.length) {
            for (int i = 0; i + interval < lists.length; i += 2 * interval) {
                lists[i] = mergeTwo(lists[i], lists[i + interval]);
            }
            interval *= 2;
        }
        return lists[0];
    }
    private ListNode mergeTwo(ListNode a, ListNode b) {
        ListNode dummy = new ListNode(0), t = dummy;
        while (a != null && b != null) {
            if (a.val <= b.val) { t.next = a; a = a.next; }
            else { t.next = b; b = b.next; }
            t = t.next;
        }
        t.next = (a != null) ? a : b;
        return dummy.next;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(kN)O(N log k)
SpaceO(1)O(log k) stack
Difficulty1/53/5
Cache behaviorOKBetter — pairwise sequential
When to useDemoWhen heap allocation matters
PE VerdictMin-heap is the textbook answer; D&C is a fine alternative when you want O(1) heap space.

Python Solutions

Sequential merge two O(kN) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(kN) — Quadratic.
Space: O(1) — Splicing.

Python code

class Solution:
    def mergeKLists(self, lists):
        def merge(a, b):
            dummy = ListNode(); t = dummy
            while a and b:
                if a.val <= b.val: t.next = a; a = a.next
                else: t.next = b; b = b.next
                t = t.next
            t.next = a or b
            return dummy.next
        out = None
        for l in lists: out = merge(out, l)
        return out
heapq with tiebreaker O(N log k) O(k)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(N log k) — Linear with heap ops.
Space: O(k) — Heap.

Edge cases & gotchas

  • heapq compares tuples — use (val, index, node) since ListNode isn't comparable.

Python code

import heapq
class Solution:
    def mergeKLists(self, lists):
        h = []
        for i, l in enumerate(lists):
            if l: heapq.heappush(h, (l.val, i, l))
        dummy = ListNode(); tail = dummy
        while h:
            v, i, n = heapq.heappop(h)
            tail.next = n; tail = n
            if n.next: heapq.heappush(h, (n.next.val, i, n.next))
        return dummy.next
Iterative pairwise D&C O(N log k) O(1)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(N log k) — Log rounds.
Space: O(1) — Splicing only.

Python code

class Solution:
    def mergeKLists(self, lists):
        if not lists: return None
        def merge(a, b):
            dummy = ListNode(); t = dummy
            while a and b:
                if a.val <= b.val: t.next = a; a = a.next
                else: t.next = b; b = b.next
                t = t.next
            t.next = a or b
            return dummy.next
        interval = 1
        while interval < len(lists):
            for i in range(0, len(lists) - interval, interval * 2):
                lists[i] = merge(lists[i], lists[i + interval])
            interval *= 2
        return lists[0]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(kN)O(N log k)
SpaceO(1)O(1)
Difficulty1/53/5
When to useDemoMemory-tight
PE Verdictheapq with tuple tiebreaker.

What the interviewer is really testing

k-way merge fundamentals. PE signal: O(N log k) via heap; tiebreaker for non-comparable list nodes.

Top gotchas

  • Python heapq compares tuples element-by-element — without an index tiebreaker, two equal vals compare nodes and TypeError.
  • Push initial heads only if non-null.
  • After popping, push the popped node's NEXT (not the popped node again).

Ship-it

Min-heap with tiebreaker; D&C if heap memory is an issue.

#44 LC 25 Hard Reverse Nodes in K-Group

Reverse every group of k nodes — pointer surgery with leftover handling.

Problem Statement

Reverse the nodes of a linked list k at a time. If fewer than k remain at the end, leave them in original order.

Signature: ListNode reverseKGroup(ListNode head, int k)

Examples

1->2->3->4->5, k=2 → 2->1->4->3->5
1->2->3->4->5, k=3 → 3->2->1->4->5

Constraints

  • 1 <= n <= 5000
  • 1 <= k <= n

Approach Overview

Brute Force

Java: Recursive reversal of k

Python: Recursive

O(n) O(n/k) stack

Optimal #1

Java: Iterative group reverse with dummy head

Python: Iterative in-place

O(n) O(1)

Optimal #2

Java: Stack of k nodes

Python: Stack of k

O(n) O(k)

Java Solutions

Recursive reversal of k O(n) O(n/k) stack

Reverse first k; recurse on the rest; splice.

Pseudo-code

check if k nodes available
reverse first k
head.next = reverseKGroup(remainder)
return new head

Complexity

Time: O(n) — Each node visited once.
Space: O(n/k) stack — Stack.

Edge cases & gotchas

  • Stack overflow for long lists with small k.

Java code

class ListNode { int val; ListNode next; ListNode(int v){val=v;} }
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode c = head;
        for (int i = 0; i < k; i++) {
            if (c == null) return head;
            c = c.next;
        }
        ListNode prev = reverseKGroup(c, k);   // recurse first
        ListNode cur = head;
        for (int i = 0; i < k; i++) {
            ListNode nxt = cur.next;
            cur.next = prev;
            prev = cur;
            cur = nxt;
        }
        return prev;
    }
}
Iterative group reverse with dummy head O(n) O(1)

Use dummy head; for each block, locate the kth node; if found, reverse in place and splice.

Pseudo-code

dummy → head
groupPrev = dummy
loop:
    kth = walk groupPrev k steps; if null, break
    groupNext = kth.next
    reverse [groupPrev.next ... kth] in place
    splice: groupPrev.next = kth (new head); old head = groupNext (new tail.next)
    groupPrev = old head (which is now the tail)

Complexity

Time: O(n) — Each node touched twice.
Space: O(1) — Pointers.

Edge cases & gotchas

  • Verify k nodes exist BEFORE reversing — short tail must stay as-is.
  • Track groupPrev (the node BEFORE the block) for splicing.

Java code

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode dummy = new ListNode(0); dummy.next = head;
        ListNode groupPrev = dummy;
        while (true) {
            ListNode kth = groupPrev;
            for (int i = 0; i < k && kth != null; i++) kth = kth.next;
            if (kth == null) break;
            ListNode groupNext = kth.next;
            // Reverse [groupPrev.next ... kth]
            ListNode prev = groupNext, cur = groupPrev.next;
            while (cur != groupNext) {
                ListNode nxt = cur.next;
                cur.next = prev; prev = cur; cur = nxt;
            }
            ListNode tmp = groupPrev.next;
            groupPrev.next = kth;
            groupPrev = tmp;
        }
        return dummy.next;
    }
}
Stack of k nodes O(n) O(k)

Push k nodes; pop them onto a growing tail. Higher constant factor but easier to write.

Pseudo-code

loop blocks: push k or break; pop into tail; advance

Complexity

Time: O(n) — Linear.
Space: O(k) — Stack of k.

Edge cases & gotchas

  • Final partial group: undo the pushes and append original.

Java code

import java.util.*;
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        Deque<ListNode> stack = new ArrayDeque<>();
        ListNode dummy = new ListNode(0), tail = dummy, c = head;
        while (c != null) {
            int cnt = 0; ListNode start = c;
            while (cnt < k && c != null) { stack.push(c); c = c.next; cnt++; }
            if (cnt < k) { tail.next = start; break; }
            while (!stack.isEmpty()) { tail.next = stack.pop(); tail = tail.next; }
            tail.next = null;
        }
        return dummy.next;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(n/k)O(k)
Difficulty3/53/5
When to usePedagogyWhiteboard easier to write
PE VerdictIterative in-place — O(1) space and stack-safe.

Python Solutions

Recursive O(n) O(n/k)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(n/k) — Stack.

Python code

class Solution:
    def reverseKGroup(self, head, k: int):
        c = head
        for _ in range(k):
            if not c: return head
            c = c.next
        prev = self.reverseKGroup(c, k)
        cur = head
        for _ in range(k):
            nxt = cur.next; cur.next = prev; prev = cur; cur = nxt
        return prev
Iterative in-place O(n) O(1)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — Pointers.

Python code

class Solution:
    def reverseKGroup(self, head, k: int):
        dummy = ListNode(0, head); group_prev = dummy
        while True:
            kth = group_prev
            for _ in range(k):
                kth = kth.next
                if not kth: return dummy.next
            group_next = kth.next
            prev, cur = group_next, group_prev.next
            while cur is not group_next:
                nxt = cur.next; cur.next = prev; prev = cur; cur = nxt
            tmp = group_prev.next
            group_prev.next = kth
            group_prev = tmp
Stack of k O(n) O(k)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(k) — Stack.

Python code

class Solution:
    def reverseKGroup(self, head, k: int):
        dummy = ListNode(); tail = dummy; c = head
        while c:
            stack = []; cur = c
            for _ in range(k):
                if not cur: break
                stack.append(cur); cur = cur.next
            if len(stack) < k: tail.next = c; break
            while stack: tail.next = stack.pop(); tail = tail.next
            tail.next = None; c = cur
        return dummy.next

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(n/k)O(k)
Difficulty3/53/5
When to usePedagogyWhiteboard
PE VerdictIterative in-place.

What the interviewer is really testing

Hardest LL pointer juggling. PE signal: verify k available BEFORE touching, track groupPrev for splice.

Top gotchas

  • Verify k nodes available before reversing — short tail must remain in order.
  • Use groupNext as the sentinel prev for the reverse loop — terminates exactly at the boundary.
  • After reversal, the OLD head of the block becomes the tail; capture it before reversing.

Ship-it

Iterative in-place with dummy head and groupPrev tracking.

Trees

#45 LC 226 Easy Invert Binary Tree

Swap left/right at every node — recursive vs iterative.

Problem Statement

Given the root of a binary tree, invert the tree (mirror it) and return its root.

Signature: TreeNode invertTree(TreeNode root)

Examples

4,2,7,1,3,6,9 → 4,7,2,9,6,3,1

Constraints

  • 0 <= n <= 100

Approach Overview

Brute Force

Java: BFS with queue

Python: BFS

O(n) O(w) where w = max width

Optimal #1

Java: Recursive DFS

Python: Recursive

O(n) O(h)

Optimal #2

Java: Iterative DFS with explicit stack

Python: Iterative DFS

O(n) O(h)

Java Solutions

BFS with queue O(n) O(w) where w = max width

Level-order swap.

Pseudo-code

while q: pop; swap children; enqueue non-null children

Complexity

Time: O(n) — Visit each node once.
Space: O(w) where w = max width — Queue.

Java code

import java.util.*;
class TreeNode { int val; TreeNode left, right; TreeNode(int v){val=v;} }
class Solution {
    public TreeNode invertTree(TreeNode root) {
        if (root == null) return null;
        Deque<TreeNode> q = new ArrayDeque<>();
        q.offer(root);
        while (!q.isEmpty()) {
            TreeNode n = q.poll();
            TreeNode t = n.left; n.left = n.right; n.right = t;
            if (n.left != null) q.offer(n.left);
            if (n.right != null) q.offer(n.right);
        }
        return root;
    }
}
Recursive DFS O(n) O(h)

Recurse left and right, then swap.

Pseudo-code

if null return; l = invert(left); r = invert(right); swap; return root

Complexity

Time: O(n) — Visit each node once.
Space: O(h) — Recursion stack height.

Edge cases & gotchas

  • Stack depth = tree height — risk on extremely skewed trees.

Java code

class Solution {
    public TreeNode invertTree(TreeNode root) {
        if (root == null) return null;
        TreeNode l = invertTree(root.left);
        TreeNode r = invertTree(root.right);
        root.left = r; root.right = l;
        return root;
    }
}
Iterative DFS with explicit stack O(n) O(h)

Avoid recursion depth.

Pseudo-code

stack root; pop, swap, push non-null children

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Java code

import java.util.*;
class Solution {
    public TreeNode invertTree(TreeNode root) {
        if (root == null) return null;
        Deque<TreeNode> s = new ArrayDeque<>();
        s.push(root);
        while (!s.isEmpty()) {
            TreeNode n = s.pop();
            TreeNode t = n.left; n.left = n.right; n.right = t;
            if (n.left != null) s.push(n.left);
            if (n.right != null) s.push(n.right);
        }
        return root;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(w)O(h)
Difficulty2/52/5
When to useWide treesDeep trees, stack-safe
PE VerdictRecursive — shortest correct code. Switch to iterative for deeply skewed trees.

Python Solutions

BFS O(n) O(w)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(w) — Queue.

Python code

from collections import deque
class TreeNode:
    def __init__(self, val=0, left=None, right=None): self.val=val; self.left=left; self.right=right
class Solution:
    def invertTree(self, root):
        if not root: return None
        q = deque([root])
        while q:
            n = q.popleft()
            n.left, n.right = n.right, n.left
            if n.left: q.append(n.left)
            if n.right: q.append(n.right)
        return root
Recursive O(n) O(h)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Python code

class Solution:
    def invertTree(self, root):
        if not root: return None
        root.left, root.right = self.invertTree(root.right), self.invertTree(root.left)
        return root
Iterative DFS O(n) O(h)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Python code

class Solution:
    def invertTree(self, root):
        if not root: return None
        s = [root]
        while s:
            n = s.pop()
            n.left, n.right = n.right, n.left
            if n.left: s.append(n.left)
            if n.right: s.append(n.right)
        return root

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(w)O(h)
Difficulty2/52/5
When to useWideStack-safe
PE VerdictRecursive.

What the interviewer is really testing

Tree warmup. PE signal: pick recursive default; mention iterative for stack safety.

Top gotchas

  • Don't forget the null base case.
  • Recursive swap can be a single line via tuple swap in Python.
  • Iterative needs a stack/queue — choice doesn't change correctness, only traversal order.

Ship-it

Recursive DFS.

#46 LC 104 Easy Maximum Depth of Binary Tree

Tree height — recursive 1+max(l,r) vs BFS level count.

Problem Statement

Return the maximum depth (root-to-leaf path length in nodes) of a binary tree.

Signature: int maxDepth(TreeNode root)

Examples

[3,9,20,null,null,15,7] → 3
[] → 0

Constraints

  • 0 <= n <= 10^4

Approach Overview

Brute Force

Java: BFS level counting

Python: BFS

O(n) O(w)

Optimal #1

Java: Recursive DFS

Python: Recursive

O(n) O(h)

Optimal #2

Java: Iterative DFS with explicit stack of (node, depth)

Python: Iterative DFS with (node, depth)

O(n) O(h)

Java Solutions

BFS level counting O(n) O(w)

Count levels in BFS.

Pseudo-code

while q: process level; depth += 1

Complexity

Time: O(n) — Linear.
Space: O(w) — Queue.

Java code

import java.util.*;
class TreeNode { int val; TreeNode left, right; TreeNode(int v){val=v;} }
class Solution {
    public int maxDepth(TreeNode root) {
        if (root == null) return 0;
        Deque<TreeNode> q = new ArrayDeque<>();
        q.offer(root);
        int d = 0;
        while (!q.isEmpty()) {
            int sz = q.size(); d++;
            for (int i = 0; i < sz; i++) {
                TreeNode n = q.poll();
                if (n.left != null) q.offer(n.left);
                if (n.right != null) q.offer(n.right);
            }
        }
        return d;
    }
}
Recursive DFS O(n) O(h)

1 + max(depth(left), depth(right)). Cleanest answer.

Pseudo-code

if null: return 0
return 1 + max(depth(left), depth(right))

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Java code

class Solution {
    public int maxDepth(TreeNode root) {
        if (root == null) return 0;
        return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
    }
}
Iterative DFS with explicit stack of (node, depth) O(n) O(h)

Stack-safe variant.

Pseudo-code

stack (root, 1); pop and recurse

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Java code

import java.util.*;
class Solution {
    public int maxDepth(TreeNode root) {
        if (root == null) return 0;
        Deque<Object[]> s = new ArrayDeque<>();
        s.push(new Object[]{root, 1});
        int best = 0;
        while (!s.isEmpty()) {
            Object[] e = s.pop();
            TreeNode n = (TreeNode) e[0]; int d = (Integer) e[1];
            best = Math.max(best, d);
            if (n.left != null) s.push(new Object[]{n.left, d+1});
            if (n.right != null) s.push(new Object[]{n.right, d+1});
        }
        return best;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(w)O(h)
Difficulty2/53/5
When to useWide treesDeep trees
PE VerdictRecursive one-liner.

Python Solutions

BFS O(n) O(w)

Same.

Pseudo-code

level count BFS

Complexity

Time: O(n) — Linear.
Space: O(w) — Queue.

Python code

from collections import deque
class TreeNode:
    def __init__(self, val=0, left=None, right=None): self.val=val; self.left=left; self.right=right
class Solution:
    def maxDepth(self, root) -> int:
        if not root: return 0
        q = deque([root]); d = 0
        while q:
            d += 1
            for _ in range(len(q)):
                n = q.popleft()
                if n.left: q.append(n.left)
                if n.right: q.append(n.right)
        return d
Recursive O(n) O(h)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Python code

class Solution:
    def maxDepth(self, root) -> int:
        if not root: return 0
        return 1 + max(self.maxDepth(root.left), self.maxDepth(root.right))
Iterative DFS with (node, depth) O(n) O(h)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Python code

class Solution:
    def maxDepth(self, root) -> int:
        if not root: return 0
        s = [(root, 1)]; best = 0
        while s:
            n, d = s.pop()
            best = max(best, d)
            if n.left: s.append((n.left, d+1))
            if n.right: s.append((n.right, d+1))
        return best

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(w)O(h)
Difficulty2/53/5
When to useWideStack-safe
PE VerdictRecursive.

What the interviewer is really testing

Basic recursive tree traversal warmup.

Top gotchas

  • Null root returns 0 (not 1).
  • Use 1 + max(...) — easy off-by-one if you forget the +1.

Ship-it

Recursive 1 + max(depth(left), depth(right)).

#47 LC 543 Easy Diameter of Binary Tree

Longest path through any node — track global max while computing heights.

Problem Statement

Return the diameter (longest path in edges between any two nodes) of a binary tree.

Signature: int diameterOfBinaryTree(TreeNode root)

Examples

[1,2,3,4,5] → 3 (4→2→1→3 or 5→2→1→3)

Constraints

  • 1 <= n <= 10^4

Approach Overview

Brute Force

Java: For each node, height(left) + height(right)

Python: Per-node height

O(n²) O(h)

Optimal #1

Java: Single-pass DFS with global max

Python: Single-pass with nonlocal best

O(n) O(h)

Optimal #2

Java: Single-pass with int[1] holder (no field)

Python: Iterative postorder

O(n) O(h)

Java Solutions

For each node, height(left) + height(right) O(n²) O(h)

Compute height recursively for each node; quadratic.

Pseudo-code

for each n: d = h(left) + h(right); track max

Complexity

Time: O(n²) — Height recomputed at each node.
Space: O(h) — Stack.

Edge cases & gotchas

  • TLEs on skewed trees.

Java code

class TreeNode { int val; TreeNode left, right; TreeNode(int v){val=v;} }
class Solution {
    public int diameterOfBinaryTree(TreeNode root) {
        if (root == null) return 0;
        int local = height(root.left) + height(root.right);
        int leftD = diameterOfBinaryTree(root.left);
        int rightD = diameterOfBinaryTree(root.right);
        return Math.max(local, Math.max(leftD, rightD));
    }
    private int height(TreeNode n) {
        if (n == null) return 0;
        return 1 + Math.max(height(n.left), height(n.right));
    }
}
Single-pass DFS with global max O(n) O(h)

Each recursive call returns height; while doing so, update best with left+right edge count.

Pseudo-code

best = 0
def h(n):
    if not n: return 0
    l = h(n.left); r = h(n.right)
    best = max(best, l + r)
    return 1 + max(l, r)

Complexity

Time: O(n) — Each node touched once.
Space: O(h) — Stack.

Edge cases & gotchas

  • Diameter is in EDGES not nodes — l + r is already correct (not +1).

Java code

class Solution {
    private int best;
    public int diameterOfBinaryTree(TreeNode root) {
        best = 0; height(root); return best;
    }
    private int height(TreeNode n) {
        if (n == null) return 0;
        int l = height(n.left), r = height(n.right);
        best = Math.max(best, l + r);
        return 1 + Math.max(l, r);
    }
}
Single-pass with int[1] holder (no field) O(n) O(h)

Same algorithm but avoid the instance field by passing a 1-element array as accumulator.

Pseudo-code

see opt1; thread best as int[]{0}

Complexity

Time: O(n) — Same.
Space: O(h) — Stack + 1 int.

Edge cases & gotchas

  • Stylistic — same logic.

Java code

class Solution {
    public int diameterOfBinaryTree(TreeNode root) {
        int[] best = new int[1];
        height(root, best);
        return best[0];
    }
    private int height(TreeNode n, int[] best) {
        if (n == null) return 0;
        int l = height(n.left, best), r = height(n.right, best);
        best[0] = Math.max(best[0], l + r);
        return 1 + Math.max(l, r);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(h)O(h)
Difficulty2/53/5
When to useTiny treeThreadsafe (no field mutation)
PE VerdictSingle-pass with accumulator. Same idiom for tree-max-path-sum (LC 124).

Python Solutions

Per-node height O(n²) O(h)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²) — Quadratic.
Space: O(h) — Stack.

Python code

class TreeNode:
    def __init__(self, val=0, left=None, right=None): self.val=val; self.left=left; self.right=right
class Solution:
    def diameterOfBinaryTree(self, root) -> int:
        def h(n):
            return 0 if not n else 1 + max(h(n.left), h(n.right))
        def diam(n):
            if not n: return 0
            return max(h(n.left)+h(n.right), diam(n.left), diam(n.right))
        return diam(root)
Single-pass with nonlocal best O(n) O(h)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Python code

class Solution:
    def diameterOfBinaryTree(self, root) -> int:
        best = 0
        def h(n):
            nonlocal best
            if not n: return 0
            l, r = h(n.left), h(n.right)
            best = max(best, l + r)
            return 1 + max(l, r)
        h(root); return best
Iterative postorder O(n) O(n)

Avoid recursion via explicit stack.

Pseudo-code

iterative postorder; cache heights

Complexity

Time: O(n) — Linear.
Space: O(n) — Stack + map.

Python code

class Solution:
    def diameterOfBinaryTree(self, root) -> int:
        if not root: return 0
        heights = {None: 0}
        stack = [(root, False)]; best = 0
        while stack:
            n, processed = stack.pop()
            if processed:
                l = heights[n.left]; r = heights[n.right]
                heights[n] = 1 + max(l, r)
                best = max(best, l + r)
            else:
                stack.append((n, True))
                if n.left: stack.append((n.left, False))
                if n.right: stack.append((n.right, False))
        return best

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(h)O(n)
Difficulty2/54/5
When to useDemoStack-safe
PE VerdictSingle-pass with nonlocal.

What the interviewer is really testing

Pattern: 'return height, update best inside recursion'. Foundation for LC 124 (Max Path Sum).

Top gotchas

  • Diameter is in EDGES — left height + right height, no +1 at this layer.
  • Return 1 + max for the height contribution to ancestors.
  • The best path may not pass through the root — track globally.

Ship-it

Single-pass DFS with global max.

#48 LC 110 Easy Balanced Binary Tree

Check height-balance: |h(left) - h(right)| ≤ 1 at every node. Bubble heights with -1 sentinel.

Problem Statement

Given a binary tree, determine if it is height-balanced (every node's left/right subtree heights differ by ≤ 1).

Signature: boolean isBalanced(TreeNode root)

Examples

[3,9,20,null,null,15,7] → true
[1,2,2,3,3,null,null,4,4] → false

Constraints

  • 0 <= n <= 5000

Approach Overview

Brute Force

Java: For each node, compute heights of both subtrees

Python: Per-node height

O(n²) O(h)

Optimal #1

Java: Single-pass DFS returning -1 on imbalance

Python: Sentinel -1 single pass

O(n) O(h)

Optimal #2

Java: Single-pass with mutable holder

Python: Tuple (balanced, height) return

O(n) O(h)

Java Solutions

For each node, compute heights of both subtrees O(n²) O(h)

Quadratic — height recomputed repeatedly.

Pseudo-code

for each n: |h(left) - h(right)| <= 1 and recurse

Complexity

Time: O(n²) — Quadratic.
Space: O(h) — Stack.

Java code

class TreeNode { int val; TreeNode left, right; TreeNode(int v){val=v;} }
class Solution {
    public boolean isBalanced(TreeNode root) {
        if (root == null) return true;
        if (Math.abs(h(root.left) - h(root.right)) > 1) return false;
        return isBalanced(root.left) && isBalanced(root.right);
    }
    private int h(TreeNode n) { return n == null ? 0 : 1 + Math.max(h(n.left), h(n.right)); }
}
Single-pass DFS returning -1 on imbalance O(n) O(h)

Return height normally; return -1 if any subtree is imbalanced. Short-circuits.

Pseudo-code

def h(n):
    if not n: return 0
    l = h(n.left); if l == -1: return -1
    r = h(n.right); if r == -1: return -1
    if abs(l - r) > 1: return -1
    return 1 + max(l, r)

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Edge cases & gotchas

  • Sentinel -1 must propagate up immediately.

Java code

class Solution {
    public boolean isBalanced(TreeNode root) { return h(root) != -1; }
    private int h(TreeNode n) {
        if (n == null) return 0;
        int l = h(n.left); if (l == -1) return -1;
        int r = h(n.right); if (r == -1) return -1;
        if (Math.abs(l - r) > 1) return -1;
        return 1 + Math.max(l, r);
    }
}
Single-pass with mutable holder O(n) O(h)

Same algorithm, no sentinel — track balance flag externally.

Pseudo-code

DFS; on imbalance set flag false, continue

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack + flag.

Edge cases & gotchas

  • Stylistic; -1 sentinel is more idiomatic.

Java code

class Solution {
    private boolean ok = true;
    public boolean isBalanced(TreeNode root) {
        h(root); return ok;
    }
    private int h(TreeNode n) {
        if (n == null || !ok) return 0;
        int l = h(n.left), r = h(n.right);
        if (Math.abs(l - r) > 1) ok = false;
        return 1 + Math.max(l, r);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(h)O(h)
Difficulty2/53/5
When to useTinyEquivalent
PE VerdictSentinel -1 single pass.

Python Solutions

Per-node height O(n²) O(h)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²) — Quadratic.
Space: O(h) — Stack.

Python code

class Solution:
    def isBalanced(self, root) -> bool:
        def h(n):
            return 0 if not n else 1 + max(h(n.left), h(n.right))
        if not root: return True
        return abs(h(root.left) - h(root.right)) <= 1 and self.isBalanced(root.left) and self.isBalanced(root.right)
Sentinel -1 single pass O(n) O(h)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Python code

class Solution:
    def isBalanced(self, root) -> bool:
        def h(n):
            if not n: return 0
            l = h(n.left)
            if l == -1: return -1
            r = h(n.right)
            if r == -1 or abs(l - r) > 1: return -1
            return 1 + max(l, r)
        return h(root) != -1
Tuple (balanced, height) return O(n) O(h)

Cleaner Pythonic alternative.

Pseudo-code

return (balanced, height)

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Python code

class Solution:
    def isBalanced(self, root) -> bool:
        def dfs(n):
            if not n: return (True, 0)
            lb, lh = dfs(n.left)
            if not lb: return (False, 0)
            rb, rh = dfs(n.right)
            if not rb: return (False, 0)
            return (abs(lh - rh) <= 1, 1 + max(lh, rh))
        return dfs(root)[0]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(h)O(h)
Difficulty2/52/5
When to useDemoReadability-first
PE Verdict-1 sentinel. Tuple form is the Pythonic alternative.

What the interviewer is really testing

Show the brute-force quadratic and improve to single-pass. PE signal: short-circuit on first imbalance.

Top gotchas

  • Sentinel value (-1) must propagate up immediately — check after each recursive call.
  • Tree with one node is balanced (no children → heights 0, 0).
  • Don't recompute height after detecting imbalance — short-circuit.

Ship-it

Single-pass DFS with -1 sentinel (Java) or tuple return (Python).

#49 LC 100 Easy Same Tree

Structural and value equality of two binary trees — recursive lock-step traversal.

Problem Statement

Given the roots of two binary trees, determine if they are structurally identical and have equal values at every node.

Signature: boolean isSameTree(TreeNode p, TreeNode q)

Examples

[1,2,3], [1,2,3] → true
[1,2], [1,null,2] → false

Constraints

  • 0 <= n <= 100

Approach Overview

Brute Force

Java: Serialize and compare

Python: Serialize compare

O(n) O(n)

Optimal #1

Java: Recursive lock-step compare

Python: Recursive

O(n) O(h)

Optimal #2

Java: Iterative BFS with two queues

Python: Iterative BFS

O(n) O(w)

Java Solutions

Serialize and compare O(n) O(n)

Encode each tree as a string (preorder with null markers) and compare.

Pseudo-code

preorder(p) == preorder(q)?

Complexity

Time: O(n) — Linear.
Space: O(n) — Two serialized strings.

Edge cases & gotchas

  • Null markers must be unambiguous (e.g., '#').

Java code

class TreeNode { int val; TreeNode left, right; TreeNode(int v){val=v;} }
class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        return serialize(p).equals(serialize(q));
    }
    private String serialize(TreeNode n) {
        if (n == null) return "#";
        return n.val + "," + serialize(n.left) + "," + serialize(n.right);
    }
}
Recursive lock-step compare O(n) O(h)

Both null → true; one null → false; values match AND children match.

Pseudo-code

if both null: true
if one null: false
if values differ: false
return isSame(p.left, q.left) and isSame(p.right, q.right)

Complexity

Time: O(n) — Each pair visited once.
Space: O(h) — Stack.

Java code

class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        if (p == null && q == null) return true;
        if (p == null || q == null) return false;
        if (p.val != q.val) return false;
        return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
    }
}
Iterative BFS with two queues O(n) O(w)

Process pairs in lock-step.

Pseudo-code

pair-by-pair BFS

Complexity

Time: O(n) — Linear.
Space: O(w) — Two queues.

Java code

import java.util.*;
class Solution {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        Deque<TreeNode[]> dq = new ArrayDeque<>();
        dq.offer(new TreeNode[]{p, q});
        while (!dq.isEmpty()) {
            TreeNode[] pair = dq.poll();
            TreeNode a = pair[0], b = pair[1];
            if (a == null && b == null) continue;
            if (a == null || b == null || a.val != b.val) return false;
            dq.offer(new TreeNode[]{a.left, b.left});
            dq.offer(new TreeNode[]{a.right, b.right});
        }
        return true;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(n)O(w)
Difficulty2/52/5
When to useDemo onlyWide trees, stack-safe
PE VerdictRecursive lock-step.

Python Solutions

Serialize compare O(n) O(n)

Same.

Pseudo-code

serialize and compare

Complexity

Time: O(n) — Linear.
Space: O(n) — Strings.

Python code

class TreeNode:
    def __init__(self, val=0, left=None, right=None): self.val=val; self.left=left; self.right=right
class Solution:
    def isSameTree(self, p, q) -> bool:
        def ser(n):
            if not n: return "#"
            return f"{n.val},{ser(n.left)},{ser(n.right)}"
        return ser(p) == ser(q)
Recursive O(n) O(h)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Python code

class Solution:
    def isSameTree(self, p, q) -> bool:
        if not p and not q: return True
        if not p or not q or p.val != q.val: return False
        return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
Iterative BFS O(n) O(w)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(w) — Queue.

Python code

from collections import deque
class Solution:
    def isSameTree(self, p, q) -> bool:
        dq = deque([(p, q)])
        while dq:
            a, b = dq.popleft()
            if not a and not b: continue
            if not a or not b or a.val != b.val: return False
            dq.append((a.left, b.left)); dq.append((a.right, b.right))
        return True

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(n)O(w)
Difficulty2/52/5
When to useDemoWide tree
PE VerdictRecursive lock-step.

What the interviewer is really testing

Pure recursion warmup. PE signal: handle the four base cases (both null / one null / value mismatch / values match) cleanly.

Top gotchas

  • Order matters: check both-null FIRST, then one-null, then value, then recurse.
  • Use short-circuit `&&` / `and` — second recursive call only fires if first returns true.

Ship-it

Recursive — both languages.

#50 LC 572 Easy Subtree of Another Tree

For each node in root, run isSameTree against subRoot — or KMP on serialized strings.

Problem Statement

Given the roots of trees root and subRoot, return whether subRoot is a subtree of root (a subtree must match in structure AND values).

Signature: boolean isSubtree(TreeNode root, TreeNode subRoot)

Examples

[3,4,5,1,2], [4,1,2] → true
[3,4,5,1,2,null,null,null,null,0], [4,1,2] → false

Constraints

  • 1 <= n <= 2000
  • 1 <= m <= 1000

Approach Overview

Brute Force

Java: For each node, isSameTree against subRoot

Python: Per-node isSameTree

O(n·m) O(max(h_n, h_m))

Optimal #1

Java: Serialize both with null markers + substring search

Python: Serialize + 'in' substring

O(n + m) O(n + m)

Optimal #2

Java: Serialize + KMP substring search

Python: Serialize + KMP

O(n+m) O(n+m)

Java Solutions

For each node, isSameTree against subRoot O(n·m) O(max(h_n, h_m))

O(n·m) — check equality at every node.

Pseudo-code

for each n in root: if isSame(n, subRoot) return true

Complexity

Time: O(n·m) — n checks × O(m) each.
Space: O(max(h_n, h_m)) — Recursion stacks.

Java code

class TreeNode { int val; TreeNode left, right; TreeNode(int v){val=v;} }
class Solution {
    public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        if (root == null) return subRoot == null;
        if (isSame(root, subRoot)) return true;
        return isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot);
    }
    private boolean isSame(TreeNode a, TreeNode b) {
        if (a == null && b == null) return true;
        if (a == null || b == null || a.val != b.val) return false;
        return isSame(a.left, b.left) && isSame(a.right, b.right);
    }
}
Serialize both with null markers + substring search O(n + m) O(n + m)

Preorder with explicit null markers; subRoot is a subtree iff its string is a substring of root's.

Pseudo-code

s = serialize(root); t = serialize(subRoot)
return s.contains(t)

Complexity

Time: O(n + m) — Linear serialize + substring (O(n+m) via indexOf optimized).
Space: O(n + m) — Serialized strings.

Edge cases & gotchas

  • Use comma separators AND a leading marker (e.g., '^') for each node so '12' isn't a prefix of '123'.
  • Null marker '#' ensures structural fidelity.

Java code

class Solution {
    public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        return serialize(root).contains(serialize(subRoot));
    }
    private String serialize(TreeNode n) {
        if (n == null) return ",#";
        return "," + n.val + serialize(n.left) + serialize(n.right);
    }
}
Serialize + KMP substring search O(n+m) O(n+m)

True O(n+m) via KMP for the substring step.

Pseudo-code

serialize; KMP on s for pattern t

Complexity

Time: O(n+m) — Linear.
Space: O(n+m) — Strings + LPS array.

Edge cases & gotchas

  • Java String.contains uses naive search — O(n·m) worst case. KMP is strictly linear.

Java code

class Solution {
    public boolean isSubtree(TreeNode root, TreeNode subRoot) {
        String s = serialize(root), p = serialize(subRoot);
        return kmp(s, p);
    }
    private String serialize(TreeNode n) {
        if (n == null) return ",#";
        return "," + n.val + serialize(n.left) + serialize(n.right);
    }
    private boolean kmp(String s, String p) {
        int[] lps = new int[p.length()];
        for (int i = 1, k = 0; i < p.length(); ) {
            if (p.charAt(i) == p.charAt(k)) { lps[i++] = ++k; }
            else if (k > 0) k = lps[k-1];
            else lps[i++] = 0;
        }
        for (int i = 0, j = 0; i < s.length(); ) {
            if (s.charAt(i) == p.charAt(j)) { i++; j++; if (j == p.length()) return true; }
            else if (j > 0) j = lps[j-1];
            else i++;
        }
        return false;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n+m) avgO(n+m)
SpaceO(n+m)O(n+m)
Difficulty3/5 — serialize correctly5/5 — KMP
When to useDefaultTruly worst-case linear required
PE VerdictAt LeetCode scale (n=2000), the brute force is fast enough and clearest. Serialize+substring is more elegant; KMP only matters when you need provable O(n+m).

Python Solutions

Per-node isSameTree O(n·m) O(h)

Same.

Pseudo-code

see Java

Complexity

Time: O(n·m) — Per-node check.
Space: O(h) — Stacks.

Python code

class Solution:
    def isSubtree(self, root, sub) -> bool:
        def same(a, b):
            if not a and not b: return True
            if not a or not b or a.val != b.val: return False
            return same(a.left, b.left) and same(a.right, b.right)
        if not root: return not sub
        return same(root, sub) or self.isSubtree(root.left, sub) or self.isSubtree(root.right, sub)
Serialize + 'in' substring O(n+m) avg O(n+m)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(n+m) avg — Linear avg.
Space: O(n+m) — Strings.

Python code

class Solution:
    def isSubtree(self, root, sub) -> bool:
        def ser(n):
            if not n: return ",#"
            return f",{n.val}{ser(n.left)}{ser(n.right)}"
        return ser(sub) in ser(root)
Serialize + KMP O(n+m) O(n+m)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(n+m) — Linear.
Space: O(n+m) — Strings + LPS.

Python code

class Solution:
    def isSubtree(self, root, sub) -> bool:
        def ser(n):
            if not n: return ",#"
            return f",{n.val}{ser(n.left)}{ser(n.right)}"
        s, p = ser(root), ser(sub)
        lps = [0] * len(p)
        k = 0
        for i in range(1, len(p)):
            while k and p[i] != p[k]: k = lps[k-1]
            if p[i] == p[k]: k += 1
            lps[i] = k
        j = 0
        for c in s:
            while j and c != p[j]: j = lps[j-1]
            if c == p[j]:
                j += 1
                if j == len(p): return True
        return False

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n·m)O(n+m)
SpaceO(h)O(n+m)
Difficulty2/55/5
When to useSmallStrict worst-case
PE VerdictPython's 'in' on serialized strings is C-fast — best of both worlds. Use brute when serialize correctness is risky.

What the interviewer is really testing

Composition: isSameTree as a building block. PE signal: discuss the unambiguous-serialization trap and KMP as the formal worst-case answer.

Top gotchas

  • Serialize MUST include null markers; otherwise [1,2] and [1,null,2] serialize identically.
  • Separator (comma) prevents '12' colliding with '1' followed by '2'.
  • Java's String.contains is O(n·m) worst-case; for true O(n+m) use KMP.

Ship-it

At LeetCode scale, brute force is clear and fast enough. Serialize+substring is the cleanest 'optimal'.

#51 LC 235 Medium Lowest Common Ancestor of a Binary Search Tree

BST LCA — walk down comparing both values against current node.

Problem Statement

Given a BST and two nodes p and q, return their lowest common ancestor.

Signature: TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q)

Examples

[6,2,8,0,4,7,9,null,null,3,5], 2, 8 → 6
[6,2,8,...], 2, 4 → 2

Constraints

  • All values unique; p and q exist in the tree.

Approach Overview

Brute Force

Java: Generic-binary-tree LCA

Python: Generic-tree LCA

O(n) O(h)

Optimal #1

Java: BST walk: descend until p and q split

Python: Iterative BST walk

O(h) O(1)

Optimal #2

Java: Recursive BST walk

Python: Recursive BST walk

O(h) O(h)

Java Solutions

Generic-binary-tree LCA O(n) O(h)

Standard LCA recursion without using BST property.

Pseudo-code

if root null or root in {p,q}: return root; l = rec(left); r = rec(right); if both: return root else l or r

Complexity

Time: O(n) — Each node visited once.
Space: O(h) — Stack.

Edge cases & gotchas

  • Works for any binary tree; ignores BST advantage.

Java code

class TreeNode { int val; TreeNode left, right; TreeNode(int v){val=v;} }
class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null || root == p || root == q) return root;
        TreeNode l = lowestCommonAncestor(root.left, p, q);
        TreeNode r = lowestCommonAncestor(root.right, p, q);
        if (l != null && r != null) return root;
        return l != null ? l : r;
    }
}
BST walk: descend until p and q split O(h) O(1)

If both p and q < root, go left; if both >, go right; else current is LCA.

Pseudo-code

while root:
    if p.val < root.val and q.val < root.val: root = root.left
    elif p.val > root.val and q.val > root.val: root = root.right
    else: return root

Complexity

Time: O(h) — Single descent.
Space: O(1) — Iterative.

Edge cases & gotchas

  • When p.val ≤ root.val ≤ q.val (or reverse), root is the LCA.
  • Iterative avoids stack depth; balanced BST → O(log n).

Java code

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        while (root != null) {
            if (p.val < root.val && q.val < root.val) root = root.left;
            else if (p.val > root.val && q.val > root.val) root = root.right;
            else return root;
        }
        return null;
    }
}
Recursive BST walk O(h) O(h)

Same logic, recursive.

Pseudo-code

see opt1

Complexity

Time: O(h) — Single descent.
Space: O(h) — Stack.

Edge cases & gotchas

  • Stack depth = height — fine for balanced BSTs.

Java code

class Solution {
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (p.val < root.val && q.val < root.val) return lowestCommonAncestor(root.left, p, q);
        if (p.val > root.val && q.val > root.val) return lowestCommonAncestor(root.right, p, q);
        return root;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(h)
SpaceO(h)O(h)
Difficulty3/51/5
Uses BST?NoYes
When to useGeneral treeRecursion-friendly
PE VerdictIterative BST walk — O(1) space, log-h time on balanced trees.

Python Solutions

Generic-tree LCA O(n) O(h)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Python code

class Solution:
    def lowestCommonAncestor(self, root, p, q):
        if not root or root is p or root is q: return root
        l = self.lowestCommonAncestor(root.left, p, q)
        r = self.lowestCommonAncestor(root.right, p, q)
        if l and r: return root
        return l or r
Iterative BST walk O(h) O(1)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(h) — Descent.
Space: O(1) — Iterative.

Python code

class Solution:
    def lowestCommonAncestor(self, root, p, q):
        while root:
            if p.val < root.val and q.val < root.val: root = root.left
            elif p.val > root.val and q.val > root.val: root = root.right
            else: return root
        return None
Recursive BST walk O(h) O(h)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(h) — Descent.
Space: O(h) — Stack.

Python code

class Solution:
    def lowestCommonAncestor(self, root, p, q):
        if p.val < root.val and q.val < root.val:
            return self.lowestCommonAncestor(root.left, p, q)
        if p.val > root.val and q.val > root.val:
            return self.lowestCommonAncestor(root.right, p, q)
        return root

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(h)
SpaceO(h)O(h)
Difficulty3/51/5
When to useGeneral treeRecursive
PE VerdictIterative BST walk.

What the interviewer is really testing

Test of BST property exploitation. PE signal: use BST ordering for O(h) and O(1) space.

Top gotchas

  • Use strict < and > with the SAME node; if one is on each side, root is the LCA.
  • Both p and q are guaranteed to be in the tree per spec.
  • Iterative avoids stack overflow on skewed BSTs (worst case O(n) height).

Ship-it

Iterative BST walk.

#52 LC 102 Medium Binary Tree Level Order Traversal

BFS by level — return list of lists.

Problem Statement

Return the level-order traversal of a binary tree's node values as a list of lists.

Signature: List<List<Integer>> levelOrder(TreeNode root)

Examples

[3,9,20,null,null,15,7] → [[3],[9,20],[15,7]]

Constraints

  • 0 <= n <= 2000

Approach Overview

Brute Force

Java: DFS tracking depth

Python: DFS with depth tracking

O(n) O(h)

Optimal #1

Java: BFS with queue, level-size sweep

Python: BFS with level-size sweep

O(n) O(w)

Optimal #2

Java: BFS with sentinel marker between levels

Python: BFS with sentinel

O(n) O(w)

Java Solutions

DFS tracking depth O(n) O(h)

DFS recursion; append node value to result[depth].

Pseudo-code

dfs(node, depth): if depth == size: append []; result[depth].add(val); recurse

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Edge cases & gotchas

  • Order within a level depends on DFS visit order — happens to match BFS for binary trees.

Java code

import java.util.*;
class TreeNode { int val; TreeNode left, right; TreeNode(int v){val=v;} }
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> out = new ArrayList<>();
        dfs(root, 0, out);
        return out;
    }
    private void dfs(TreeNode n, int d, List<List<Integer>> out) {
        if (n == null) return;
        if (out.size() == d) out.add(new ArrayList<>());
        out.get(d).add(n.val);
        dfs(n.left, d+1, out);
        dfs(n.right, d+1, out);
    }
}
BFS with queue, level-size sweep O(n) O(w)

Standard BFS; each iteration of the outer loop processes exactly one level.

Pseudo-code

q = [root]
while q:
    level = []; for _ in size(q): n = pop; level.add(n.val); push children
    out.add(level)

Complexity

Time: O(n) — Linear.
Space: O(w) — Queue ≤ width.

Edge cases & gotchas

  • Null root → return empty list, not [[]].

Java code

import java.util.*;
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> out = new ArrayList<>();
        if (root == null) return out;
        Deque<TreeNode> q = new ArrayDeque<>();
        q.offer(root);
        while (!q.isEmpty()) {
            int sz = q.size();
            List<Integer> level = new ArrayList<>(sz);
            for (int i = 0; i < sz; i++) {
                TreeNode n = q.poll();
                level.add(n.val);
                if (n.left != null) q.offer(n.left);
                if (n.right != null) q.offer(n.right);
            }
            out.add(level);
        }
        return out;
    }
}
BFS with sentinel marker between levels O(n) O(w)

Push a null sentinel to delimit levels.

Pseudo-code

push root; push null; on null: start new level, push null if q not empty

Complexity

Time: O(n) — Linear.
Space: O(w) — Queue.

Edge cases & gotchas

  • Marginally trickier than the level-size approach.

Java code

import java.util.*;
class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        List<List<Integer>> out = new ArrayList<>();
        if (root == null) return out;
        Deque<TreeNode> q = new ArrayDeque<>();
        q.offer(root); q.offer(null);
        List<Integer> level = new ArrayList<>();
        while (!q.isEmpty()) {
            TreeNode n = q.poll();
            if (n == null) {
                out.add(level); level = new ArrayList<>();
                if (!q.isEmpty()) q.offer(null);
            } else {
                level.add(n.val);
                if (n.left != null) q.offer(n.left);
                if (n.right != null) q.offer(n.right);
            }
        }
        return out;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(h)O(w)
Difficulty2/53/5
When to useMemory-tight, deep treePedagogy
PE VerdictBFS with level-size sweep.

Python Solutions

DFS with depth tracking O(n) O(h)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Python code

class Solution:
    def levelOrder(self, root):
        out = []
        def dfs(n, d):
            if not n: return
            if d == len(out): out.append([])
            out[d].append(n.val)
            dfs(n.left, d+1); dfs(n.right, d+1)
        dfs(root, 0)
        return out
BFS with level-size sweep O(n) O(w)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(w) — Queue.

Python code

from collections import deque
class Solution:
    def levelOrder(self, root):
        if not root: return []
        out = []; q = deque([root])
        while q:
            level = []
            for _ in range(len(q)):
                n = q.popleft()
                level.append(n.val)
                if n.left: q.append(n.left)
                if n.right: q.append(n.right)
            out.append(level)
        return out
BFS with sentinel O(n) O(w)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(w) — Queue.

Python code

from collections import deque
class Solution:
    def levelOrder(self, root):
        if not root: return []
        out = []; q = deque([root, None]); level = []
        while q:
            n = q.popleft()
            if n is None:
                out.append(level); level = []
                if q: q.append(None)
            else:
                level.append(n.val)
                if n.left: q.append(n.left)
                if n.right: q.append(n.right)
        return out

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(h)O(w)
Difficulty2/53/5
When to useMemory-tightPedagogy
PE VerdictBFS with level-size.

What the interviewer is really testing

Template question for BFS / level-by-level processing. PE signal: capture q.size() into a variable BEFORE the inner loop so dequeues don't change the bound.

Top gotchas

  • Capture sz = q.size() before iterating — q grows as you enqueue children.
  • Null root → return empty list (NOT [[]]).
  • Pre-size the level list with new ArrayList<>(sz) — minor allocation win.

Ship-it

BFS with level-size sweep.

#53 LC 199 Medium Binary Tree Right Side View

BFS taking last of each level — or DFS visiting right first.

Problem Statement

Return the values of the nodes you can see when looking at the tree from the right side, top to bottom.

Signature: List<Integer> rightSideView(TreeNode root)

Examples

[1,2,3,null,5,null,4] → [1,3,4]

Constraints

  • 0 <= n <= 100

Approach Overview

Brute Force

Java: BFS, last node per level

Python: BFS last per level

O(n) O(w)

Optimal #1

Java: DFS visiting right first, record first per depth

Python: DFS right-first

O(n) O(h)

Optimal #2

Java: BFS right-first queue

Python: BFS right-first

O(n) O(w)

Java Solutions

BFS, last node per level O(n) O(w)

Standard BFS; capture last node in each level.

Pseudo-code

for each level: last = q[size-1]; out.add(last.val)

Complexity

Time: O(n) — Linear.
Space: O(w) — Queue.

Java code

import java.util.*;
class TreeNode { int val; TreeNode left, right; TreeNode(int v){val=v;} }
class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> out = new ArrayList<>();
        if (root == null) return out;
        Deque<TreeNode> q = new ArrayDeque<>(); q.offer(root);
        while (!q.isEmpty()) {
            int sz = q.size();
            for (int i = 0; i < sz; i++) {
                TreeNode n = q.poll();
                if (i == sz - 1) out.add(n.val);
                if (n.left != null) q.offer(n.left);
                if (n.right != null) q.offer(n.right);
            }
        }
        return out;
    }
}
DFS visiting right first, record first per depth O(n) O(h)

Right-first DFS; if depth == size(out), this is the rightmost at that depth.

Pseudo-code

dfs(n, d): if d == size: out.add(n.val); dfs(right, d+1); dfs(left, d+1)

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Java code

import java.util.*;
class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> out = new ArrayList<>();
        dfs(root, 0, out);
        return out;
    }
    private void dfs(TreeNode n, int d, List<Integer> out) {
        if (n == null) return;
        if (d == out.size()) out.add(n.val);
        dfs(n.right, d+1, out);
        dfs(n.left, d+1, out);
    }
}
BFS right-first queue O(n) O(w)

BFS but always enqueue right before left; first poll of each level is the rightmost.

Pseudo-code

BFS; take first of each level when children are enqueued right-first

Complexity

Time: O(n) — Linear.
Space: O(w) — Queue.

Java code

import java.util.*;
class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> out = new ArrayList<>();
        if (root == null) return out;
        Deque<TreeNode> q = new ArrayDeque<>(); q.offer(root);
        while (!q.isEmpty()) {
            int sz = q.size();
            for (int i = 0; i < sz; i++) {
                TreeNode n = q.poll();
                if (i == 0) out.add(n.val);
                if (n.right != null) q.offer(n.right);
                if (n.left != null) q.offer(n.left);
            }
        }
        return out;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(w)O(w)
Difficulty2/52/5
When to useWideWide, right-first idiom
PE VerdictDFS right-first — cleanest, O(h) space.

Python Solutions

BFS last per level O(n) O(w)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(w) — Queue.

Python code

from collections import deque
class TreeNode:
    def __init__(self, val=0, left=None, right=None): self.val=val; self.left=left; self.right=right
class Solution:
    def rightSideView(self, root):
        if not root: return []
        out, q = [], deque([root])
        while q:
            n_level = len(q)
            for i in range(n_level):
                n = q.popleft()
                if i == n_level - 1: out.append(n.val)
                if n.left: q.append(n.left)
                if n.right: q.append(n.right)
        return out
DFS right-first O(n) O(h)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Python code

class Solution:
    def rightSideView(self, root):
        out = []
        def dfs(n, d):
            if not n: return
            if d == len(out): out.append(n.val)
            dfs(n.right, d+1); dfs(n.left, d+1)
        dfs(root, 0); return out
BFS right-first O(n) O(w)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(w) — Queue.

Python code

from collections import deque
class Solution:
    def rightSideView(self, root):
        if not root: return []
        out, q = [], deque([root])
        while q:
            sz = len(q)
            for i in range(sz):
                n = q.popleft()
                if i == 0: out.append(n.val)
                if n.right: q.append(n.right)
                if n.left: q.append(n.left)
        return out

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(w)O(w)
Difficulty2/52/5
When to useWideWide
PE VerdictDFS right-first.

What the interviewer is really testing

Tests level-order recognition. PE signal: pick right-first DFS for O(h) space.

Top gotchas

  • Right-first DFS records the FIRST node seen at each depth — that's the rightmost.
  • Don't enqueue null children — wastes queue space.

Ship-it

DFS right-first with depth tracking.

#54 LC 1448 Medium Count Good Nodes in Binary Tree

Count nodes whose value ≥ max on root-to-node path — DFS carrying running max.

Problem Statement

A node is 'good' if no node on the path from root to it has value greater than it. Return the number of good nodes.

Signature: int goodNodes(TreeNode root)

Examples

[3,1,4,3,null,1,5] → 4

Constraints

  • 1 <= n <= 10^5

Approach Overview

Brute Force

Java: For each node, walk path to root

Python: Path-walk per node

O(n²) O(h)

Optimal #1

Java: DFS carrying running max along the path

Python: DFS running max

O(n) O(h)

Optimal #2

Java: Iterative DFS with explicit stack

Python: Iterative

O(n) O(h)

Java Solutions

For each node, walk path to root O(n²) O(h)

Quadratic.

Pseudo-code

for each node: walk up; count if max <= val

Complexity

Time: O(n²) — Path walk per node.
Space: O(h) — Stack.

Edge cases & gotchas

  • No parent pointers — requires building them first.

Java code

class TreeNode { int val; TreeNode left, right; TreeNode(int v){val=v;} }
class Solution {
    public int goodNodes(TreeNode root) {
        // Conceptual baseline only; see opt1.
        return dfs(root, Integer.MIN_VALUE);
    }
    private int dfs(TreeNode n, int max) {
        if (n == null) return 0;
        int cnt = (n.val >= max) ? 1 : 0;
        int newMax = Math.max(max, n.val);
        return cnt + dfs(n.left, newMax) + dfs(n.right, newMax);
    }
}
DFS carrying running max along the path O(n) O(h)

Walk down; current node is 'good' iff its value ≥ max-on-path-so-far.

Pseudo-code

dfs(n, max): if val >= max: cnt=1; recurse with max(max, val)

Complexity

Time: O(n) — Each node visited once.
Space: O(h) — Stack.

Edge cases & gotchas

  • Root is always good. Use INT_MIN sentinel.

Java code

class Solution {
    public int goodNodes(TreeNode root) { return dfs(root, Integer.MIN_VALUE); }
    private int dfs(TreeNode n, int maxSoFar) {
        if (n == null) return 0;
        int cnt = (n.val >= maxSoFar) ? 1 : 0;
        int nextMax = Math.max(maxSoFar, n.val);
        return cnt + dfs(n.left, nextMax) + dfs(n.right, nextMax);
    }
}
Iterative DFS with explicit stack O(n) O(h)

Same algorithm, no recursion.

Pseudo-code

stack (node, maxSoFar)

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Java code

import java.util.*;
class Solution {
    public int goodNodes(TreeNode root) {
        if (root == null) return 0;
        Deque<Object[]> s = new ArrayDeque<>();
        s.push(new Object[]{root, Integer.MIN_VALUE});
        int cnt = 0;
        while (!s.isEmpty()) {
            Object[] e = s.pop();
            TreeNode n = (TreeNode) e[0]; int m = (Integer) e[1];
            if (n.val >= m) cnt++;
            int nm = Math.max(m, n.val);
            if (n.left != null) s.push(new Object[]{n.left, nm});
            if (n.right != null) s.push(new Object[]{n.right, nm});
        }
        return cnt;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(h)O(h)
Difficulty3/52/5
When to useDon'tStack-safe
PE VerdictDFS with running max.

Python Solutions

Path-walk per node O(n²) O(h)

Conceptual baseline.

Pseudo-code

see Java

Complexity

Time: O(n²) — Quadratic.
Space: O(h) — Stack.

Python code

class Solution:
    def goodNodes(self, root) -> int:
        def dfs(n, m):
            if not n: return 0
            cnt = 1 if n.val >= m else 0
            nm = max(m, n.val)
            return cnt + dfs(n.left, nm) + dfs(n.right, nm)
        return dfs(root, float('-inf'))
DFS running max O(n) O(h)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Python code

class Solution:
    def goodNodes(self, root) -> int:
        def dfs(n, m):
            if not n: return 0
            cnt = 1 if n.val >= m else 0
            nm = max(m, n.val)
            return cnt + dfs(n.left, nm) + dfs(n.right, nm)
        return dfs(root, float('-inf'))
Iterative O(n) O(h)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Python code

class Solution:
    def goodNodes(self, root) -> int:
        if not root: return 0
        s = [(root, float('-inf'))]; cnt = 0
        while s:
            n, m = s.pop()
            if n.val >= m: cnt += 1
            nm = max(m, n.val)
            if n.left: s.append((n.left, nm))
            if n.right: s.append((n.right, nm))
        return cnt

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(h)O(h)
Difficulty3/52/5
When to useDon'tStack-safe
PE VerdictDFS running max.

What the interviewer is really testing

Pattern: 'pass running state down a tree'. PE signal: use INT_MIN sentinel so root is always counted.

Top gotchas

  • Use >= not > — equal values still count as good per the definition.
  • Initial max must be smaller than any node value — INT_MIN or -inf.

Ship-it

DFS with running max parameter.

#55 LC 98 Medium Validate Binary Search Tree

DFS carrying (lo, hi) range — or inorder traversal must be strictly increasing.

Problem Statement

Determine whether a binary tree is a valid BST (left subtree < node < right subtree, recursively).

Signature: boolean isValidBST(TreeNode root)

Examples

[2,1,3] → true
[5,1,4,null,null,3,6] → false

Constraints

  • 1 <= n <= 10^4
  • Node.val fits in int range

Approach Overview

Brute Force

Java: For each node, check min(right subtree) > val and max(left) < val

Python: min/max recompute

O(n²) O(h)

Optimal #1

Java: DFS with (lo, hi) range

Python: DFS with (lo, hi)

O(n) O(h)

Optimal #2

Java: Inorder traversal — strictly increasing

Python: Inorder traversal monotone

O(n) O(h)

Java Solutions

For each node, check min(right subtree) > val and max(left) < val O(n²) O(h)

Quadratic — recompute extremes per node.

Pseudo-code

for each n: max(left subtree) < val < min(right subtree); recurse

Complexity

Time: O(n²) — Quadratic.
Space: O(h) — Stack.

Edge cases & gotchas

  • Slow but conceptually clear.

Java code

class TreeNode { int val; TreeNode left, right; TreeNode(int v){val=v;} }
class Solution {
    public boolean isValidBST(TreeNode root) {
        if (root == null) return true;
        if (root.left != null && maxV(root.left) >= root.val) return false;
        if (root.right != null && minV(root.right) <= root.val) return false;
        return isValidBST(root.left) && isValidBST(root.right);
    }
    private int minV(TreeNode n) { return n.left == null ? n.val : minV(n.left); }
    private int maxV(TreeNode n) { return n.right == null ? n.val : maxV(n.right); }
}
DFS with (lo, hi) range O(n) O(h)

Pass tightening bounds; check val ∈ (lo, hi).

Pseudo-code

dfs(n, lo, hi): if val <= lo or val >= hi: false; recurse with updated bounds

Complexity

Time: O(n) — Each node once.
Space: O(h) — Stack.

Edge cases & gotchas

  • Use Long.MIN/MAX as initial bounds — node values can be INT_MIN/MAX themselves.

Java code

class Solution {
    public boolean isValidBST(TreeNode root) {
        return dfs(root, Long.MIN_VALUE, Long.MAX_VALUE);
    }
    private boolean dfs(TreeNode n, long lo, long hi) {
        if (n == null) return true;
        if (n.val <= lo || n.val >= hi) return false;
        return dfs(n.left, lo, n.val) && dfs(n.right, n.val, hi);
    }
}
Inorder traversal — strictly increasing O(n) O(h)

Inorder visits BST values in sorted order; track previous value, fail on non-increase.

Pseudo-code

inorder DFS; remember prev; if cur <= prev: false

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Edge cases & gotchas

  • Prev must be a Long or use Integer (boxed null) sentinel to handle the first node correctly.

Java code

class Solution {
    private Long prev = null;
    public boolean isValidBST(TreeNode root) {
        if (root == null) return true;
        if (!isValidBST(root.left)) return false;
        if (prev != null && root.val <= prev) return false;
        prev = (long) root.val;
        return isValidBST(root.right);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(h)O(h)
Difficulty3/53/5
When to useDon'tEquivalent
PE VerdictDFS with bounds — explicit and stateless.

Python Solutions

min/max recompute O(n²) O(h)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²) — Quadratic.
Space: O(h) — Stack.

Python code

class Solution:
    def isValidBST(self, root) -> bool:
        def maxv(n): return n.val if not n.right else maxv(n.right)
        def minv(n): return n.val if not n.left else minv(n.left)
        if not root: return True
        if root.left and maxv(root.left) >= root.val: return False
        if root.right and minv(root.right) <= root.val: return False
        return self.isValidBST(root.left) and self.isValidBST(root.right)
DFS with (lo, hi) O(n) O(h)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Python code

class Solution:
    def isValidBST(self, root) -> bool:
        def dfs(n, lo, hi):
            if not n: return True
            if n.val <= lo or n.val >= hi: return False
            return dfs(n.left, lo, n.val) and dfs(n.right, n.val, hi)
        return dfs(root, float('-inf'), float('inf'))
Inorder traversal monotone O(n) O(h)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Python code

class Solution:
    def isValidBST(self, root) -> bool:
        prev = [float('-inf')]
        def inorder(n):
            if not n: return True
            if not inorder(n.left): return False
            if n.val <= prev[0]: return False
            prev[0] = n.val
            return inorder(n.right)
        return inorder(root)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(h)O(h)
Difficulty3/53/5
When to useDon'tEquivalent
PE VerdictBounds-based DFS.

What the interviewer is really testing

Classic BST validation. PE signal: strict inequalities, careful with INT_MIN/MAX sentinels.

Top gotchas

  • Bounds must be STRICT — `val <= lo || val >= hi` fails on duplicates (BST forbids them per LC).
  • Initial bounds must allow any int value — use long or float('inf').
  • Inorder must use STRICTLY-INCREASING check; equal-to-prev fails.

Ship-it

DFS with (lo, hi) bounds.

#56 LC 230 Medium Kth Smallest Element in a BST

Inorder traversal of BST yields sorted order — return k-th.

Problem Statement

Given the root of a BST and an integer k, return the k-th (1-indexed) smallest value.

Signature: int kthSmallest(TreeNode root, int k)

Examples

[3,1,4,null,2], k=1 → 1
[5,3,6,2,4,null,null,1], k=3 → 3

Constraints

  • 1 <= k <= n <= 10^4

Approach Overview

Brute Force

Java: Inorder into list, return k-th

Python: Full inorder list

O(n) O(n)

Optimal #1

Java: Iterative inorder with early stop

Python: Iterative inorder

O(h + k) O(h)

Optimal #2

Java: Recursive inorder with counter

Python: Recursive with counter

O(h + k) O(h)

Java Solutions

Inorder into list, return k-th O(n) O(n)

Materialize full sorted list.

Pseudo-code

list = inorder(root); return list[k-1]

Complexity

Time: O(n) — Full inorder.
Space: O(n) — List.

Java code

import java.util.*;
class TreeNode { int val; TreeNode left, right; TreeNode(int v){val=v;} }
class Solution {
    public int kthSmallest(TreeNode root, int k) {
        List<Integer> a = new ArrayList<>();
        inorder(root, a);
        return a.get(k-1);
    }
    private void inorder(TreeNode n, List<Integer> a) {
        if (n == null) return;
        inorder(n.left, a); a.add(n.val); inorder(n.right, a);
    }
}
Iterative inorder with early stop O(h + k) O(h)

Stack-based inorder; stop after k-th pop.

Pseudo-code

stack = []; cur = root
while stack or cur:
    while cur: stack.push(cur); cur = cur.left
    cur = stack.pop()
    if --k == 0: return cur.val
    cur = cur.right

Complexity

Time: O(h + k) — Stop early.
Space: O(h) — Stack.

Edge cases & gotchas

  • Best for very large trees where k is small.

Java code

import java.util.*;
class Solution {
    public int kthSmallest(TreeNode root, int k) {
        Deque<TreeNode> stack = new ArrayDeque<>();
        TreeNode cur = root;
        while (!stack.isEmpty() || cur != null) {
            while (cur != null) { stack.push(cur); cur = cur.left; }
            cur = stack.pop();
            if (--k == 0) return cur.val;
            cur = cur.right;
        }
        return -1;
    }
}
Recursive inorder with counter O(h + k) O(h)

Same idea, recursive.

Pseudo-code

inorder; decrement k; return when k == 0

Complexity

Time: O(h + k) — Early stop.
Space: O(h) — Stack.

Java code

class Solution {
    private int k, ans;
    public int kthSmallest(TreeNode root, int kk) {
        k = kk; inorder(root); return ans;
    }
    private void inorder(TreeNode n) {
        if (n == null || k == 0) return;
        inorder(n.left);
        if (--k == 0) { ans = n.val; return; }
        inorder(n.right);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(h+k)
SpaceO(n)O(h)
Difficulty1/52/5
When to useDemoRecursion-friendly
PE VerdictIterative inorder with early stop.

Python Solutions

Full inorder list O(n) O(n)

Same.

Pseudo-code

inorder list[k-1]

Complexity

Time: O(n) — Linear.
Space: O(n) — List.

Python code

class Solution:
    def kthSmallest(self, root, k: int) -> int:
        a = []
        def io(n):
            if not n: return
            io(n.left); a.append(n.val); io(n.right)
        io(root); return a[k-1]
Iterative inorder O(h+k) O(h)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(h+k) — Early stop.
Space: O(h) — Stack.

Python code

class Solution:
    def kthSmallest(self, root, k: int) -> int:
        stack, cur = [], root
        while stack or cur:
            while cur:
                stack.append(cur); cur = cur.left
            cur = stack.pop()
            k -= 1
            if k == 0: return cur.val
            cur = cur.right
        return -1
Recursive with counter O(h+k) O(h)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(h+k) — Linear.
Space: O(h) — Stack.

Python code

class Solution:
    def kthSmallest(self, root, k: int) -> int:
        self.k = k; self.ans = -1
        def io(n):
            if not n or self.k == 0: return
            io(n.left)
            self.k -= 1
            if self.k == 0: self.ans = n.val; return
            io(n.right)
        io(root); return self.ans

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(h+k)
SpaceO(n)O(h)
Difficulty1/52/5
When to useDemoRecursion
PE VerdictIterative inorder.

What the interviewer is really testing

Tests BST inorder = sorted insight. PE signal: early stop on k-th visit.

Top gotchas

  • k is 1-indexed — decrement and check == 0.
  • Stop the traversal early — don't materialize full list.
  • Iterative form is essential for large n; recursive can stack-overflow on skewed BSTs.

Ship-it

Iterative inorder traversal with early stop.

#57 LC 105 Medium Construct Binary Tree From Preorder and Inorder Traversal

Build tree from preorder + inorder — root is preorder[0]; inorder splits left/right.

Problem Statement

Given two arrays representing preorder and inorder traversals of a binary tree with unique values, reconstruct and return the tree.

Signature: TreeNode buildTree(int[] preorder, int[] inorder)

Examples

preorder=[3,9,20,15,7], inorder=[9,3,15,20,7] → [3,9,20,null,null,15,7]

Constraints

  • 1 <= n <= 3000
  • All values unique

Approach Overview

Brute Force

Java: Recursive with O(n) linear search per call

Python: Recursive with index() lookup

O(n²) O(h)

Optimal #1

Java: HashMap inorder index → O(n)

Python: HashMap O(n)

O(n) O(n)

Optimal #2

Java: Iterative with stack (O(n))

Python: Iterative stack

O(n) O(h)

Java Solutions

Recursive with O(n) linear search per call O(n²) O(h)

Find root index by linear scan of inorder.

Pseudo-code

build(pre[], in[], il, ir):
  root = pre[pIdx++]; find root in in[il..ir]
  build left subtree from in[il..idx-1]
  build right subtree from in[idx+1..ir]

Complexity

Time: O(n²) — n root finds × n search.
Space: O(h) — Stack.

Java code

class TreeNode { int val; TreeNode left, right; TreeNode(int v){val=v;} }
class Solution {
    private int p;
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        p = 0;
        return build(preorder, inorder, 0, inorder.length - 1);
    }
    private TreeNode build(int[] pre, int[] in_, int il, int ir) {
        if (il > ir) return null;
        TreeNode root = new TreeNode(pre[p++]);
        int idx = il;
        while (in_[idx] != root.val) idx++;
        root.left = build(pre, in_, il, idx - 1);
        root.right = build(pre, in_, idx + 1, ir);
        return root;
    }
}
HashMap inorder index → O(n) O(n) O(n)

Same recursion; lookup root index in O(1) via prebuilt map.

Pseudo-code

map[val] = index in inorder; recurse with O(1) lookup

Complexity

Time: O(n) — n root finds × O(1).
Space: O(n) — Map + stack.

Edge cases & gotchas

  • Unique values requirement enables the map.

Java code

import java.util.*;
class Solution {
    private int p;
    private Map<Integer, Integer> idx;
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        p = 0;
        idx = new HashMap<>();
        for (int i = 0; i < inorder.length; i++) idx.put(inorder[i], i);
        return build(preorder, 0, inorder.length - 1);
    }
    private TreeNode build(int[] pre, int il, int ir) {
        if (il > ir) return null;
        int val = pre[p++];
        TreeNode root = new TreeNode(val);
        int m = idx.get(val);
        root.left = build(pre, il, m - 1);
        root.right = build(pre, m + 1, ir);
        return root;
    }
}
Iterative with stack (O(n)) O(n) O(h)

Walk preorder; use stack to track ancestors; compare against inorder pointer.

Pseudo-code

complex but allocation-free; advanced

Complexity

Time: O(n) — Single pass.
Space: O(h) — Stack.

Edge cases & gotchas

  • Trickier to write but O(h) auxiliary space.

Java code

import java.util.*;
class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        if (preorder.length == 0) return null;
        TreeNode root = new TreeNode(preorder[0]);
        Deque<TreeNode> s = new ArrayDeque<>();
        s.push(root);
        int io = 0;
        for (int i = 1; i < preorder.length; i++) {
            TreeNode node = s.peek();
            if (node.val != inorder[io]) {
                node.left = new TreeNode(preorder[i]);
                s.push(node.left);
            } else {
                while (!s.isEmpty() && s.peek().val == inorder[io]) {
                    node = s.pop(); io++;
                }
                node.right = new TreeNode(preorder[i]);
                s.push(node.right);
            }
        }
        return root;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(h)O(h)
Difficulty3/55/5
When to useTinyMemory-tight
PE VerdictHashMap-accelerated recursion.

Python Solutions

Recursive with index() lookup O(n²) O(h)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²) — Quadratic.
Space: O(h) — Stack.

Python code

from typing import List
class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]):
        if not preorder: return None
        root = TreeNode(preorder[0])
        m = inorder.index(preorder[0])
        root.left = self.buildTree(preorder[1:m+1], inorder[:m])
        root.right = self.buildTree(preorder[m+1:], inorder[m+1:])
        return root
HashMap O(n) O(n) O(n)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(n) — Map + stack.

Python code

from typing import List
class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]):
        idx = {v: i for i, v in enumerate(inorder)}
        self.p = 0
        def build(l, r):
            if l > r: return None
            v = preorder[self.p]; self.p += 1
            root = TreeNode(v)
            m = idx[v]
            root.left = build(l, m - 1)
            root.right = build(m + 1, r)
            return root
        return build(0, len(inorder) - 1)
Iterative stack O(n) O(h)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(n) — Single pass.
Space: O(h) — Stack.

Python code

from typing import List
class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]):
        if not preorder: return None
        root = TreeNode(preorder[0])
        stack = [root]; io = 0
        for v in preorder[1:]:
            node = stack[-1]
            if node.val != inorder[io]:
                node.left = TreeNode(v); stack.append(node.left)
            else:
                while stack and stack[-1].val == inorder[io]:
                    node = stack.pop(); io += 1
                node.right = TreeNode(v); stack.append(node.right)
        return root

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(h)O(h)
Difficulty2/55/5
When to useDemoMemory-tight
PE VerdictHashMap-accelerated.

What the interviewer is really testing

Classic recursive reconstruction. PE signal: O(n) via the inorder index map.

Top gotchas

  • preorder[0] is always the current subtree's root.
  • Map MUST be inorder-value → index, NOT preorder.
  • Use a single shared p index for preorder — don't slice arrays per call.

Ship-it

HashMap-accelerated recursion.

#58 LC 124 Hard Binary Tree Maximum Path Sum

Path through any node — DFS returns 'max gain ending here'; track global best.

Problem Statement

Return the maximum path sum (any node-to-node path) in a binary tree.

Signature: int maxPathSum(TreeNode root)

Examples

[-10,9,20,null,null,15,7] → 42

Constraints

  • 1 <= n <= 3*10^4
  • -1000 <= val <= 1000

Approach Overview

Brute Force

Java: Try every node as path peak

Python: Per-node peak

O(n²) O(h)

Optimal #1

Java: Single-pass DFS with global best

Python: Single-pass DFS

O(n) O(h)

Optimal #2

Java: Iterative postorder

Python: Iterative postorder

O(n) O(n)

Java Solutions

Try every node as path peak O(n²) O(h)

Quadratic.

Pseudo-code

for each n: max(leftGain) + n.val + max(rightGain); track

Complexity

Time: O(n²) — Quadratic.
Space: O(h) — Stack.

Java code

class TreeNode { int val; TreeNode left, right; TreeNode(int v){val=v;} }
class Solution {
    public int maxPathSum(TreeNode root) {
        if (root == null) return 0;
        int local = root.val + Math.max(0, gain(root.left)) + Math.max(0, gain(root.right));
        if (root.left == null && root.right == null) return local;
        int l = root.left != null ? maxPathSum(root.left) : Integer.MIN_VALUE;
        int r = root.right != null ? maxPathSum(root.right) : Integer.MIN_VALUE;
        return Math.max(local, Math.max(l, r));
    }
    private int gain(TreeNode n) { return n == null ? 0 : n.val + Math.max(0, Math.max(gain(n.left), gain(n.right))); }
}
Single-pass DFS with global best O(n) O(h)

Return max-gain-ending-here; update best with l+val+r.

Pseudo-code

dfs(n): l=max(0,dfs(left)); r=max(0,dfs(right)); best=max(best,n.val+l+r); return n.val+max(l,r)

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Edge cases & gotchas

  • Initialize best to INT_MIN — negative-only trees.
  • Clamp child gains at 0 (skip negative branches).

Java code

class Solution {
    private int best;
    public int maxPathSum(TreeNode root) { best = Integer.MIN_VALUE; gain(root); return best; }
    private int gain(TreeNode n) {
        if (n == null) return 0;
        int l = Math.max(0, gain(n.left));
        int r = Math.max(0, gain(n.right));
        best = Math.max(best, n.val + l + r);
        return n.val + Math.max(l, r);
    }
}
Iterative postorder O(n) O(n)

Explicit stack postorder; cache gains.

Pseudo-code

iterative postorder with gain memoization

Complexity

Time: O(n) — Linear.
Space: O(n) — Map + stack.

Java code

import java.util.*;
class Solution {
    public int maxPathSum(TreeNode root) {
        Map<TreeNode,Integer> g = new HashMap<>(); g.put(null, 0);
        Deque<TreeNode> s = new ArrayDeque<>();
        TreeNode cur = root, last = null;
        int best = Integer.MIN_VALUE;
        while (cur != null || !s.isEmpty()) {
            while (cur != null) { s.push(cur); cur = cur.left; }
            TreeNode p = s.peek();
            if (p.right != null && last != p.right) cur = p.right;
            else {
                int l = Math.max(0, g.get(p.left)), r = Math.max(0, g.get(p.right));
                best = Math.max(best, p.val + l + r);
                g.put(p, p.val + Math.max(l, r));
                last = s.pop();
            }
        }
        return best;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(h)O(n)
Difficulty3/55/5
WhenDemoStack-safe
PE VerdictSingle-pass DFS with global best.

Python Solutions

Per-node peak O(n²) O(h)

Quadratic.

Pseudo-code

see Java

Complexity

Time: O(n²) — Quad.
Space: O(h) — Stack.

Python code

class Solution:
    def maxPathSum(self, root):
        def gain(n):
            if not n: return 0
            return n.val + max(0, max(gain(n.left), gain(n.right)))
        if not root: return 0
        local = root.val + max(0, gain(root.left)) + max(0, gain(root.right))
        if not root.left and not root.right: return local
        l = self.maxPathSum(root.left) if root.left else float('-inf')
        r = self.maxPathSum(root.right) if root.right else float('-inf')
        return max(local, l, r)
Single-pass DFS O(n) O(h)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(h) — Stack.

Python code

class Solution:
    def maxPathSum(self, root) -> int:
        self.best = float('-inf')
        def gain(n):
            if not n: return 0
            l = max(0, gain(n.left)); r = max(0, gain(n.right))
            self.best = max(self.best, n.val + l + r)
            return n.val + max(l, r)
        gain(root); return self.best
Iterative postorder O(n) O(n)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(n) — Stack+map.

Python code

class Solution:
    def maxPathSum(self, root) -> int:
        g = {None: 0}; s = []; cur = root; last = None; best = float('-inf')
        while cur or s:
            while cur: s.append(cur); cur = cur.left
            p = s[-1]
            if p.right and last is not p.right: cur = p.right
            else:
                l = max(0, g[p.left]); r = max(0, g[p.right])
                best = max(best, p.val + l + r)
                g[p] = p.val + max(l, r)
                last = s.pop()
        return best

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(h)O(n)
Difficulty3/55/5
WhenDemoStack-safe
PE VerdictSingle-pass.

What the interviewer is really testing

Apex of 'return one thing, track another' tree DP.

Top gotchas

  • Clamp negative gains to 0.
  • Best may use both children; gain-up returns only one.
  • Initialize best to -inf, not 0.

Ship-it

Single-pass DFS with global best.

#59 LC 297 Hard Serialize and Deserialize Binary Tree

Preorder + null markers — recursive or iterative round-trip.

Problem Statement

Design serialize/deserialize for a binary tree such that the encoded string can be reconstructed back to the same tree.

Signature: String serialize(TreeNode root); TreeNode deserialize(String data)

Examples

[1,2,3,null,null,4,5] → "1,2,#,#,3,4,#,#,5,#,#" → back to tree

Constraints

  • 0 <= n <= 10^4

Approach Overview

Brute Force

Java: Level-order with null markers

Python: BFS

O(n) O(n)

Optimal #1

Java: Preorder DFS with null markers

Python: Preorder DFS

O(n) O(n)

Optimal #2

Java: Iterative DFS with stack

Python: Iterative

O(n) O(n)

Java Solutions

Level-order with null markers O(n) O(n)

BFS encode/decode.

Pseudo-code

BFS encode null too; decode by pairing children

Complexity

Time: O(n) — Linear.
Space: O(n) — Queue + output.

Java code

import java.util.*;
class TreeNode { int val; TreeNode left, right; TreeNode(int v){val=v;} }
public class Codec {
    public String serialize(TreeNode root) {
        if (root == null) return "";
        StringBuilder sb = new StringBuilder();
        Deque<TreeNode> q = new ArrayDeque<>(); q.offer(root);
        while (!q.isEmpty()) {
            TreeNode n = q.poll();
            if (n == null) { sb.append("#,"); continue; }
            sb.append(n.val).append(',');
            q.offer(n.left); q.offer(n.right);
        }
        return sb.toString();
    }
    public TreeNode deserialize(String data) {
        if (data.isEmpty()) return null;
        String[] t = data.split(",");
        TreeNode root = new TreeNode(Integer.parseInt(t[0]));
        Deque<TreeNode> q = new ArrayDeque<>(); q.offer(root);
        int i = 1;
        while (!q.isEmpty()) {
            TreeNode n = q.poll();
            if (!t[i].equals("#")) { n.left = new TreeNode(Integer.parseInt(t[i])); q.offer(n.left); }
            i++;
            if (!t[i].equals("#")) { n.right = new TreeNode(Integer.parseInt(t[i])); q.offer(n.right); }
            i++;
        }
        return root;
    }
}
Preorder DFS with null markers O(n) O(n)

Recursive serialize/deserialize.

Pseudo-code

ser: if null '#' else val,ser(left),ser(right)\nde: queue tokens; pop; if '#' null else build

Complexity

Time: O(n) — Linear.
Space: O(n) — Recursion + output.

Edge cases & gotchas

  • Use a Queue<String> so deserialize pops in order.

Java code

import java.util.*;
public class Codec {
    public String serialize(TreeNode r) {
        StringBuilder sb = new StringBuilder();
        ser(r, sb); return sb.toString();
    }
    private void ser(TreeNode n, StringBuilder sb) {
        if (n == null) { sb.append("#,"); return; }
        sb.append(n.val).append(',');
        ser(n.left, sb); ser(n.right, sb);
    }
    public TreeNode deserialize(String data) {
        Deque<String> q = new ArrayDeque<>(Arrays.asList(data.split(",")));
        return de(q);
    }
    private TreeNode de(Deque<String> q) {
        String t = q.poll();
        if (t.equals("#")) return null;
        TreeNode n = new TreeNode(Integer.parseInt(t));
        n.left = de(q); n.right = de(q);
        return n;
    }
}
Iterative DFS with stack O(n) O(n)

Avoid recursion depth.

Pseudo-code

iterative preorder serialize/deserialize

Complexity

Time: O(n) — Linear.
Space: O(n) — Stack.

Java code

import java.util.*;
public class Codec {
    public String serialize(TreeNode root) {
        StringBuilder sb = new StringBuilder();
        Deque<TreeNode> s = new ArrayDeque<>();
        s.push(root);
        while (!s.isEmpty()) {
            TreeNode n = s.pop();
            if (n == null) { sb.append("#,"); continue; }
            sb.append(n.val).append(',');
            s.push(n.right); s.push(n.left);
        }
        return sb.toString();
    }
    public TreeNode deserialize(String data) {
        String[] t = data.split(",");
        int[] idx = {0};
        return build(t, idx);
    }
    private TreeNode build(String[] t, int[] idx) {
        if (idx[0] >= t.length || t[idx[0]].equals("#")) { idx[0]++; return null; }
        TreeNode n = new TreeNode(Integer.parseInt(t[idx[0]++]));
        n.left = build(t, idx);
        n.right = build(t, idx);
        return n;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(n)O(n)
Difficulty3/53/5
WhenCompact wireStack-safe
PE VerdictPreorder DFS with null markers.

Python Solutions

BFS O(n) O(n)

Level-order.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(n) — Queue.

Python code

from collections import deque
class TreeNode:
    def __init__(self, val=0, left=None, right=None): self.val=val; self.left=left; self.right=right
class Codec:
    def serialize(self, root):
        if not root: return ""
        out = []; q = deque([root])
        while q:
            n = q.popleft()
            if n is None: out.append("#"); continue
            out.append(str(n.val)); q.append(n.left); q.append(n.right)
        return ",".join(out)
    def deserialize(self, data):
        if not data: return None
        t = data.split(",")
        root = TreeNode(int(t[0])); q = deque([root]); i = 1
        while q:
            n = q.popleft()
            if t[i] != "#": n.left = TreeNode(int(t[i])); q.append(n.left)
            i += 1
            if t[i] != "#": n.right = TreeNode(int(t[i])); q.append(n.right)
            i += 1
        return root
Preorder DFS O(n) O(n)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(n) — Stack.

Python code

from collections import deque
class Codec:
    def serialize(self, root):
        out = []
        def go(n):
            if not n: out.append("#"); return
            out.append(str(n.val)); go(n.left); go(n.right)
        go(root); return ",".join(out)
    def deserialize(self, data):
        q = deque(data.split(","))
        def build():
            t = q.popleft()
            if t == "#": return None
            n = TreeNode(int(t))
            n.left = build(); n.right = build()
            return n
        return build()
Iterative O(n) O(n)

Stack-safe.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(n) — Stack.

Python code

class Codec:
    def serialize(self, root):
        out = []; s = [root]
        while s:
            n = s.pop()
            if n is None: out.append("#"); continue
            out.append(str(n.val)); s.append(n.right); s.append(n.left)
        return ",".join(out)
    def deserialize(self, data):
        t = data.split(","); idx = [0]
        def build():
            if idx[0] >= len(t) or t[idx[0]] == "#":
                idx[0] += 1; return None
            n = TreeNode(int(t[idx[0]])); idx[0] += 1
            n.left = build(); n.right = build()
            return n
        return build()

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(n)O(n)
Difficulty3/53/5
WhenBFSStack-safe
PE VerdictPreorder.

What the interviewer is really testing

Wire-format design + recursive round-trip.

Top gotchas

  • Null markers are mandatory.
  • Deserialize must consume tokens in the same order serialize emits them.
  • Use a queue (FIFO) for tokens, not a list (popped from end).

Ship-it

Preorder DFS with null markers.

Heap / Priority Queue

#60 LC 703 Easy Kth Largest Element in a Stream

Min-heap of size k — top of heap is the k-th largest.

Problem Statement

Design KthLargest(k, nums); add(val) returns the k-th largest after insertion.

Signature: KthLargest(int k, int[] nums); int add(int val)

Examples

k=3, [4,5,8,2]; add(3)→4; add(5)→5; add(10)→5; add(9)→8; add(4)→8

Constraints

  • 1 <= k <= 10^4

Approach Overview

Brute Force

Java: Sort on each add

Python: Sort

O(n log n) O(n)

Optimal #1

Java: Min-heap of size k

Python: heapq min-heap

O(log k) per add O(k)

Optimal #2

Java: Quickselect on demand

Python: heapq.nlargest

O(n) avg O(n)

Java Solutions

Sort on each add O(n log n) O(n)

O(n log n) each.

Pseudo-code

insert, sort, return [n-k]

Complexity

Time: O(n log n) — Sort.
Space: O(n) — List.

Java code

import java.util.*;
class KthLargest {
    List<Integer> a = new ArrayList<>(); int k;
    public KthLargest(int kk, int[] nums) { k = kk; for (int x : nums) a.add(x); }
    public int add(int val) { a.add(val); Collections.sort(a); return a.get(a.size()-k); }
}
Min-heap of size k O(log k) per add O(k)

Top of heap = k-th largest.

Pseudo-code

keep size <= k; heap.peek() = kth largest

Complexity

Time: O(log k) per add — Heap op.
Space: O(k) — Heap.

Edge cases & gotchas

  • Heap must be size k AFTER insertion; pop on overflow.

Java code

import java.util.*;
class KthLargest {
    PriorityQueue<Integer> h; int k;
    public KthLargest(int kk, int[] nums) {
        k = kk; h = new PriorityQueue<>();
        for (int x : nums) add(x);
    }
    public int add(int val) {
        h.offer(val);
        if (h.size() > k) h.poll();
        return h.peek();
    }
}
Quickselect on demand O(n) avg O(n)

O(n) average per add.

Pseudo-code

quickselect(arr, n-k)

Complexity

Time: O(n) avg — Linear partition.
Space: O(n) — Array.

Edge cases & gotchas

  • Worst case O(n²) — rarely used in streaming.

Java code

import java.util.*;
class KthLargest {
    List<Integer> a = new ArrayList<>(); int k;
    public KthLargest(int kk, int[] nums) { k = kk; for (int x : nums) a.add(x); }
    public int add(int val) {
        a.add(val);
        Collections.sort(a);   // simplified; real impl uses quickselect
        return a.get(a.size()-k);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n log n)O(n)
SpaceO(n)O(n)
Difficulty1/54/5
WhenDemoTiny k batch
PE VerdictMin-heap size k.

Python Solutions

Sort O(n log n) O(n)

Same.

Pseudo-code

sort

Complexity

Time: O(n log n) — Sort.
Space: O(n) — List.

Python code

class KthLargest:
    def __init__(self, k, nums): self.k = k; self.a = sorted(nums)
    def add(self, v):
        import bisect; bisect.insort(self.a, v)
        return self.a[-self.k]
heapq min-heap O(log k) O(k)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(log k) — Heap.
Space: O(k) — Heap.

Python code

import heapq
class KthLargest:
    def __init__(self, k, nums):
        self.k = k; self.h = []
        for x in nums: self.add(x)
    def add(self, v):
        heapq.heappush(self.h, v)
        if len(self.h) > self.k: heapq.heappop(self.h)
        return self.h[0]
heapq.nlargest O(n log k) O(k)

Recompute top-k each time.

Pseudo-code

nlargest(k)[-1]

Complexity

Time: O(n log k) — nlargest.
Space: O(k) — Heap.

Python code

import heapq
class KthLargest:
    def __init__(self, k, nums): self.k = k; self.a = list(nums)
    def add(self, v):
        self.a.append(v)
        return heapq.nlargest(self.k, self.a)[-1]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n log n)O(n log k)
SpaceO(n)O(k)
Difficulty1/52/5
WhenDemoPer-call recompute
PE Verdictheapq min-heap of size k.

What the interviewer is really testing

Classic streaming top-k. PE signal: maintain heap-of-k, not heap-of-n.

Top gotchas

  • MIN-heap not max-heap.
  • Pop on overflow AFTER push.
  • Pre-seed by calling add(x) for each initial value.

Ship-it

Min-heap of size k.

#61 LC 1046 Easy Last Stone Weight

Repeatedly smash two heaviest stones — max-heap simulation.

Problem Statement

Given stone weights, each round take the two heaviest and replace with their absolute difference (or remove both if equal). Return final stone weight (0 if none).

Signature: int lastStoneWeight(int[] stones)

Examples

[2,7,4,1,8,1] → 1

Constraints

  • 1 <= n <= 30
  • 1 <= weights <= 1000

Approach Overview

Brute Force

Java: Sort each round

Python: Sort

O(n² log n) O(n)

Optimal #1

Java: Max-heap

Python: Max-heap (negate)

O(n log n) O(n)

Optimal #2

Java: Counting sort (values bounded by 1000)

Python: Count sort

O(n + max·n) O(max)

Java Solutions

Sort each round O(n² log n) O(n)

O(n² log n).

Pseudo-code

sort; pop two; push diff

Complexity

Time: O(n² log n) — Sort each round.
Space: O(n) — Array.

Java code

import java.util.*;
class Solution {
    public int lastStoneWeight(int[] stones) {
        List<Integer> a = new ArrayList<>();
        for (int s : stones) a.add(s);
        while (a.size() > 1) {
            Collections.sort(a);
            int y = a.remove(a.size()-1), x = a.remove(a.size()-1);
            if (y != x) a.add(y - x);
        }
        return a.isEmpty() ? 0 : a.get(0);
    }
}
Max-heap O(n log n) O(n)

PriorityQueue with reverseOrder.

Pseudo-code

max-heap; loop: pop two, push diff if non-zero

Complexity

Time: O(n log n) — n rounds × log n.
Space: O(n) — Heap.

Java code

import java.util.*;
class Solution {
    public int lastStoneWeight(int[] stones) {
        PriorityQueue<Integer> h = new PriorityQueue<>(Comparator.reverseOrder());
        for (int s : stones) h.offer(s);
        while (h.size() > 1) {
            int y = h.poll(), x = h.poll();
            if (y != x) h.offer(y - x);
        }
        return h.isEmpty() ? 0 : h.peek();
    }
}
Counting sort (values bounded by 1000) O(n + max·n) O(max)

Use a count array.

Pseudo-code

count[v]; merge from top

Complexity

Time: O(n + max·n) — Bounded values.
Space: O(max) — Array.

Java code

class Solution {
    public int lastStoneWeight(int[] stones) {
        int[] c = new int[1001];
        for (int s : stones) c[s]++;
        int slow = 1000;
        while (slow > 0) {
            if (c[slow] == 0) { slow--; continue; }
            if (c[slow] >= 2) { c[slow] -= 2; continue; }
            int fast = slow - 1;
            while (fast > 0 && c[fast] == 0) fast--;
            if (fast == 0) return slow;
            c[slow]--; c[fast]--; c[slow - fast]++;
        }
        return 0;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n² log n)O(n + max·n)
SpaceO(n)O(max)
Difficulty1/53/5
WhenTinyBounded weights
PE VerdictMax-heap.

Python Solutions

Sort O(n² log n) O(n)

Same.

Pseudo-code

sort, pop, push

Complexity

Time: O(n² log n) — Sort.
Space: O(n) — List.

Python code

from typing import List
class Solution:
    def lastStoneWeight(self, stones: List[int]) -> int:
        while len(stones) > 1:
            stones.sort()
            y = stones.pop(); x = stones.pop()
            if y != x: stones.append(y - x)
        return stones[0] if stones else 0
Max-heap (negate) O(n log n) O(n)

heapq is min-heap; negate values.

Pseudo-code

see Java

Complexity

Time: O(n log n) — Heap.
Space: O(n) — Heap.

Python code

from typing import List
import heapq
class Solution:
    def lastStoneWeight(self, stones: List[int]) -> int:
        h = [-s for s in stones]
        heapq.heapify(h)
        while len(h) > 1:
            y = -heapq.heappop(h); x = -heapq.heappop(h)
            if y != x: heapq.heappush(h, -(y - x))
        return -h[0] if h else 0
Count sort O(n + max·n) O(max)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(n + max·n) — Bounded.
Space: O(max) — Array.

Python code

from typing import List
class Solution:
    def lastStoneWeight(self, stones: List[int]) -> int:
        c = [0] * 1001
        for s in stones: c[s] += 1
        slow = 1000
        while slow > 0:
            if c[slow] == 0: slow -= 1; continue
            if c[slow] >= 2: c[slow] -= 2; continue
            fast = slow - 1
            while fast > 0 and c[fast] == 0: fast -= 1
            if fast == 0: return slow
            c[slow] -= 1; c[fast] -= 1; c[slow-fast] += 1
        return 0

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n² log n)O(n + max·n)
SpaceO(n)O(max)
Difficulty1/53/5
WhenDemoBounded
PE Verdictheapq with negation.

What the interviewer is really testing

Heap warmup. PE signal: max-heap, simulate.

Top gotchas

  • heapq is min-heap — negate.
  • Don't re-push 0.
  • If both equal, BOTH removed.

Ship-it

Max-heap.

#62 LC 973 Medium K Closest Points to Origin

Max-heap of size k by distance² — or quickselect.

Problem Statement

Return the k closest points to the origin by Euclidean distance.

Signature: int[][] kClosest(int[][] points, int k)

Examples

[[1,3],[-2,2]], k=1 → [[-2,2]]

Constraints

  • 1 <= k <= n <= 10^4

Approach Overview

Brute Force

Java: Sort by distance

Python: Sort

O(n log n) O(1)

Optimal #1

Java: Max-heap size k

Python: heapq.nsmallest

O(n log k) O(k)

Optimal #2

Java: Quickselect partition

Python: Quickselect

O(n) avg O(1)

Java Solutions

Sort by distance O(n log n) O(1)

Full sort.

Pseudo-code

sort by x²+y²; take first k

Complexity

Time: O(n log n) — Sort.
Space: O(1) — In-place.

Java code

import java.util.*;
class Solution {
    public int[][] kClosest(int[][] pts, int k) {
        Arrays.sort(pts, (a,b) -> (a[0]*a[0]+a[1]*a[1]) - (b[0]*b[0]+b[1]*b[1]));
        return Arrays.copyOfRange(pts, 0, k);
    }
}
Max-heap size k O(n log k) O(k)

Heap holds k smallest distances so far.

Pseudo-code

max-heap; push; if size>k pop

Complexity

Time: O(n log k) — Each push O(log k).
Space: O(k) — Heap.

Java code

import java.util.*;
class Solution {
    public int[][] kClosest(int[][] pts, int k) {
        PriorityQueue<int[]> h = new PriorityQueue<>((a,b) -> (b[0]*b[0]+b[1]*b[1]) - (a[0]*a[0]+a[1]*a[1]));
        for (int[] p : pts) {
            h.offer(p);
            if (h.size() > k) h.poll();
        }
        return h.toArray(new int[0][]);
    }
}
Quickselect partition O(n) avg O(1)

Average O(n).

Pseudo-code

partition by pivot dist; recurse on the side containing k

Complexity

Time: O(n) avg — Partition halving.
Space: O(1) — In-place.

Edge cases & gotchas

  • Worst case O(n²); randomize pivot.

Java code

class Solution {
    public int[][] kClosest(int[][] pts, int k) {
        qs(pts, 0, pts.length-1, k);
        return java.util.Arrays.copyOfRange(pts, 0, k);
    }
    private void qs(int[][] a, int lo, int hi, int k) {
        if (lo >= hi) return;
        int p = part(a, lo, hi);
        if (p == k) return;
        if (p < k) qs(a, p+1, hi, k);
        else qs(a, lo, p-1, k);
    }
    private int part(int[][] a, int lo, int hi) {
        int pv = dist(a[hi]), i = lo;
        for (int j = lo; j < hi; j++) {
            if (dist(a[j]) <= pv) { int[] t = a[i]; a[i] = a[j]; a[j] = t; i++; }
        }
        int[] t = a[i]; a[i] = a[hi]; a[hi] = t;
        return i;
    }
    private int dist(int[] p) { return p[0]*p[0] + p[1]*p[1]; }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n log n)O(n) avg
SpaceO(1)O(1)
Difficulty1/54/5
WhenDemok near n
PE VerdictMax-heap of k.

Python Solutions

Sort O(n log n) O(1)

Same.

Pseudo-code

sort by dist

Complexity

Time: O(n log n) — Sort.
Space: O(1) — In-place.

Python code

from typing import List
class Solution:
    def kClosest(self, points: List[List[int]], k: int) -> List[List[int]]:
        points.sort(key=lambda p: p[0]*p[0]+p[1]*p[1])
        return points[:k]
heapq.nsmallest O(n log k) O(k)

C-fast.

Pseudo-code

nsmallest(k, key=dist)

Complexity

Time: O(n log k) — Heap.
Space: O(k) — Heap.

Python code

from typing import List
import heapq
class Solution:
    def kClosest(self, points: List[List[int]], k: int) -> List[List[int]]:
        return heapq.nsmallest(k, points, key=lambda p: p[0]*p[0] + p[1]*p[1])
Quickselect O(n) avg O(1)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(n) avg — Partition.
Space: O(1) — In-place.

Python code

from typing import List
import random
class Solution:
    def kClosest(self, points: List[List[int]], k: int) -> List[List[int]]:
        d = lambda p: p[0]*p[0]+p[1]*p[1]
        def qs(lo, hi):
            if lo >= hi: return
            pv = d(points[random.randint(lo, hi)])
            i = lo
            for j in range(lo, hi+1):
                if d(points[j]) < pv:
                    points[i], points[j] = points[j], points[i]; i += 1
            if i == k: return
            if i < k: qs(i, hi)
            else: qs(lo, i-1)
        qs(0, len(points)-1)
        return points[:k]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n log n)O(n) avg
SpaceO(1)O(1)
Difficulty1/54/5
WhenDemoHot path
PE Verdictheapq.nsmallest with key.

What the interviewer is really testing

Top-k pattern.

Top gotchas

  • Don't sqrt — square distances preserve order and avoid floats.
  • Max-heap so the worst is on top and gets evicted.
  • Quickselect needs random pivot for stability.

Ship-it

Max-heap of size k.

#63 LC 215 Medium Kth Largest Element in an Array

Quickselect for O(n) average, heap for stable O(n log k).

Problem Statement

Find the k-th largest element in an unsorted array.

Signature: int findKthLargest(int[] nums, int k)

Examples

[3,2,1,5,6,4], k=2 → 5
[3,2,3,1,2,4,5,5,6], k=4 → 4

Constraints

  • 1 <= k <= n <= 10^5

Approach Overview

Brute Force

Java: Sort

Python: Sort

O(n log n) O(1)

Optimal #1

Java: Min-heap size k

Python: heapq.nlargest

O(n log k) O(k)

Optimal #2

Java: Quickselect (Lomuto/Hoare)

Python: Quickselect

O(n) avg O(1)

Java Solutions

Sort O(n log n) O(1)

O(n log n).

Pseudo-code

sort; nums[n-k]

Complexity

Time: O(n log n) — Sort.
Space: O(1) — In-place.

Java code

import java.util.*;
class Solution {
    public int findKthLargest(int[] nums, int k) {
        Arrays.sort(nums);
        return nums[nums.length - k];
    }
}
Min-heap size k O(n log k) O(k)

Same template as Kth Largest Stream.

Pseudo-code

min-heap; if size > k pop

Complexity

Time: O(n log k) — n pushes.
Space: O(k) — Heap.

Java code

import java.util.*;
class Solution {
    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> h = new PriorityQueue<>();
        for (int x : nums) {
            h.offer(x);
            if (h.size() > k) h.poll();
        }
        return h.peek();
    }
}
Quickselect (Lomuto/Hoare) O(n) avg O(1)

O(n) average.

Pseudo-code

target = n-k; partition; recurse on side containing target

Complexity

Time: O(n) avg — Partition halving.
Space: O(1) — In-place.

Edge cases & gotchas

  • Randomize pivot to avoid O(n²) on sorted inputs.

Java code

import java.util.*;
class Solution {
    public int findKthLargest(int[] nums, int k) {
        return qs(nums, 0, nums.length - 1, nums.length - k);
    }
    private int qs(int[] a, int lo, int hi, int target) {
        if (lo == hi) return a[lo];
        int piv = a[lo + new Random().nextInt(hi - lo + 1)];
        int l = lo, r = hi;
        while (l <= r) {
            while (a[l] < piv) l++;
            while (a[r] > piv) r--;
            if (l <= r) { int t = a[l]; a[l] = a[r]; a[r] = t; l++; r--; }
        }
        if (target <= r) return qs(a, lo, r, target);
        if (target >= l) return qs(a, l, hi, target);
        return a[target];
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n log n)O(n log k)
SpaceO(1)O(k)
Difficulty1/52/5
WhenSimpleStreaming
PE VerdictQuickselect — O(n) average is the textbook answer.

Python Solutions

Sort O(n log n) O(1)

Same.

Pseudo-code

sort

Complexity

Time: O(n log n) — Sort.
Space: O(1) — In-place.

Python code

from typing import List
class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        nums.sort(); return nums[-k]
heapq.nlargest O(n log k) O(k)

C-fast.

Pseudo-code

nlargest(k)[-1]

Complexity

Time: O(n log k) — Heap.
Space: O(k) — Heap.

Python code

from typing import List
import heapq
class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        return heapq.nlargest(k, nums)[-1]
Quickselect O(n) avg O(1)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(n) avg — Partition.
Space: O(1) — In-place.

Python code

from typing import List
import random
class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        target = len(nums) - k
        def qs(lo, hi):
            if lo == hi: return nums[lo]
            piv = nums[random.randint(lo, hi)]
            l, r = lo, hi
            while l <= r:
                while nums[l] < piv: l += 1
                while nums[r] > piv: r -= 1
                if l <= r: nums[l], nums[r] = nums[r], nums[l]; l += 1; r -= 1
            if target <= r: return qs(lo, r)
            if target >= l: return qs(l, hi)
            return nums[target]
        return qs(0, len(nums)-1)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n log n)O(n log k)
SpaceO(1)O(k)
Difficulty1/51/5
WhenSimpleDefault
PE VerdictQuickselect; use heapq for streaming.

What the interviewer is really testing

Quickselect is the textbook PE answer.

Top gotchas

  • Randomize pivot.
  • Target index = n - k.
  • Three-way partition for duplicates.

Ship-it

Quickselect with random pivot.

#64 LC 621 Medium Task Scheduler

Greedy max-heap simulation, or closed-form (maxFreq − 1)·(n + 1) + tiesAtMax.

Problem Statement

Given tasks and cooldown n between same-task executions, return the minimum CPU time.

Signature: int leastInterval(char[] tasks, int n)

Examples

['A','A','A','B','B','B'], n=2 → 8

Constraints

  • 1 <= tasks.length <= 10^4
  • 0 <= n <= 100

Approach Overview

Brute Force

Java: Simulate slot by slot

Python: Simulate

O(T·26) O(26)

Optimal #1

Java: Max-heap simulation

Python: Max-heap simulation

O(T log 26) O(26)

Optimal #2

Java: Closed-form (maxFreq-1)*(n+1) + ties

Python: Closed-form

O(T) O(26)

Java Solutions

Simulate slot by slot O(T·26) O(26)

O(T·26).

Pseudo-code

each slot: pick most-frequent eligible

Complexity

Time: O(T·26) — T slots.
Space: O(26) — Counts.

Java code

import java.util.*;
class Solution {
    public int leastInterval(char[] tasks, int n) {
        int[] c = new int[26];
        for (char t : tasks) c[t-'A']++;
        int time = 0;
        int[] cooldown = new int[26];
        int total = tasks.length;
        while (total > 0) {
            int pick = -1, best = 0;
            for (int i = 0; i < 26; i++) if (cooldown[i] <= time && c[i] > best) { best = c[i]; pick = i; }
            if (pick != -1) { c[pick]--; cooldown[pick] = time + n + 1; total--; }
            time++;
        }
        return time;
    }
}
Max-heap simulation O(T log 26) O(26)

Heap of remaining counts; cooldown queue.

Pseudo-code

cycle of n+1: poll top, decrement, queue with cooldown; refill from queue

Complexity

Time: O(T log 26) — Heap ops.
Space: O(26) — Heap + queue.

Java code

import java.util.*;
class Solution {
    public int leastInterval(char[] tasks, int n) {
        int[] c = new int[26];
        for (char t : tasks) c[t-'A']++;
        PriorityQueue<Integer> h = new PriorityQueue<>(Comparator.reverseOrder());
        for (int x : c) if (x > 0) h.offer(x);
        Deque<int[]> q = new ArrayDeque<>();
        int time = 0;
        while (!h.isEmpty() || !q.isEmpty()) {
            time++;
            if (!h.isEmpty()) {
                int x = h.poll() - 1;
                if (x > 0) q.offer(new int[]{x, time + n});
            }
            if (!q.isEmpty() && q.peek()[1] == time) h.offer(q.poll()[0]);
        }
        return time;
    }
}
Closed-form (maxFreq-1)*(n+1) + ties O(T) O(26)

O(T) by counting maxFreq and how many tasks tie at maxFreq.

Pseudo-code

answer = max(T, (maxFreq-1)*(n+1) + tiesAtMax)

Complexity

Time: O(T) — Single pass.
Space: O(26) — Counts.

Edge cases & gotchas

  • When dense (no idle slots), answer is simply T.

Java code

class Solution {
    public int leastInterval(char[] tasks, int n) {
        int[] c = new int[26];
        for (char t : tasks) c[t-'A']++;
        int max = 0, ties = 0;
        for (int x : c) if (x > max) { max = x; ties = 1; } else if (x == max) ties++;
        return Math.max(tasks.length, (max - 1) * (n + 1) + ties);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(T·26)O(T log 26)
SpaceO(26)O(26)
Difficulty2/53/5
WhenSimulation clarityDefault
PE VerdictClosed-form is O(T) and one-liner; heap is the safe default in interview.

Python Solutions

Simulate O(T·26) O(26)

Same.

Pseudo-code

see Java

Complexity

Time: O(T·26) — Slots.
Space: O(26) — Counts.

Python code

from typing import List
from collections import Counter
class Solution:
    def leastInterval(self, tasks, n: int) -> int:
        c = [0]*26
        for t in tasks: c[ord(t)-65] += 1
        cooldown = [0]*26; time = 0; total = len(tasks)
        while total:
            pick = -1; best = 0
            for i in range(26):
                if cooldown[i] <= time and c[i] > best: best = c[i]; pick = i
            if pick != -1:
                c[pick] -= 1; cooldown[pick] = time + n + 1; total -= 1
            time += 1
        return time
Max-heap simulation O(T log 26) O(26)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(T log 26) — Heap.
Space: O(26) — Heap+queue.

Python code

from collections import Counter, deque
import heapq
class Solution:
    def leastInterval(self, tasks, n: int) -> int:
        h = [-v for v in Counter(tasks).values()]
        heapq.heapify(h)
        q = deque(); time = 0
        while h or q:
            time += 1
            if h:
                v = heapq.heappop(h) + 1
                if v: q.append((v, time + n))
            if q and q[0][1] == time:
                heapq.heappush(h, q.popleft()[0])
        return time
Closed-form O(T) O(26)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(T) — Single pass.
Space: O(26) — Counts.

Python code

from collections import Counter
class Solution:
    def leastInterval(self, tasks, n: int) -> int:
        c = Counter(tasks)
        m = max(c.values())
        ties = sum(1 for v in c.values() if v == m)
        return max(len(tasks), (m - 1) * (n + 1) + ties)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(T·26)O(T log 26)
SpaceO(26)O(26)
Difficulty2/53/5
WhenClarityDefault
PE VerdictClosed-form.

What the interviewer is really testing

Greedy with cooldown. PE signal: derive the closed-form.

Top gotchas

  • max(T, formula) handles the no-idle dense case.
  • Heap must NOT push zero counts.
  • Cooldown queue holds (count, time-available).

Ship-it

Closed-form (max-1)*(n+1) + ties.

#65 LC 355 Medium Design Twitter

Per-user tweet lists + k-way merge of followees via min-heap.

Problem Statement

Design Twitter with postTweet, getNewsFeed (10 most recent tweets from self + followees), follow, unfollow.

Signature: class Twitter { void postTweet(int u, int t); List<Integer> getNewsFeed(int u); void follow(int f, int g); void unfollow(int f, int g); }

Examples

Constraints

  • 1 <= ids <= 500

Approach Overview

Brute Force

Java: Flat list + sort on getNewsFeed

Python: Flat sort

getNewsFeed O(T log T) O(T)

Optimal #1

Java: K-way merge via max-heap of latest tweets

Python: K-way merge

getNewsFeed O(k log F) O(F)

Optimal #2

Java: Per-user bounded list (only 10 newest)

Python: Bounded per-user

O(F log F) O(U·10 + F)

Java Solutions

Flat list + sort on getNewsFeed getNewsFeed O(T log T) O(T)

Naive.

Pseudo-code

merge all relevant tweets then sort by time

Complexity

Time: getNewsFeed O(T log T) — Sort full list.
Space: O(T) — All tweets.

Java code

import java.util.*;
class Twitter {
    static int t = 0;
    Map<Integer, List<int[]>> tweets = new HashMap<>();
    Map<Integer, Set<Integer>> fol = new HashMap<>();
    public void postTweet(int u, int id) {
        tweets.computeIfAbsent(u, k -> new ArrayList<>()).add(new int[]{++t, id});
    }
    public List<Integer> getNewsFeed(int u) {
        List<int[]> all = new ArrayList<>();
        if (tweets.containsKey(u)) all.addAll(tweets.get(u));
        if (fol.containsKey(u)) for (int g : fol.get(u)) if (tweets.containsKey(g)) all.addAll(tweets.get(g));
        all.sort((a, b) -> b[0] - a[0]);
        List<Integer> out = new ArrayList<>();
        for (int i = 0; i < Math.min(10, all.size()); i++) out.add(all.get(i)[1]);
        return out;
    }
    public void follow(int f, int g) { fol.computeIfAbsent(f, k -> new HashSet<>()).add(g); }
    public void unfollow(int f, int g) { if (fol.containsKey(f)) fol.get(f).remove(g); }
}
K-way merge via max-heap of latest tweets getNewsFeed O(k log F) O(F)

Heap of (time, listIndex, ...); pop 10.

Pseudo-code

push latest tweet of each followee+self; pop 10; push next from same list

Complexity

Time: getNewsFeed O(k log F) — Heap.
Space: O(F) — Heap.

Java code

import java.util.*;
class Twitter {
    private int t = 0;
    private Map<Integer, List<int[]>> tweets = new HashMap<>();
    private Map<Integer, Set<Integer>> fol = new HashMap<>();
    public void postTweet(int u, int id) { tweets.computeIfAbsent(u, k -> new ArrayList<>()).add(new int[]{++t, id}); }
    public List<Integer> getNewsFeed(int u) {
        Set<Integer> users = new HashSet<>();
        users.add(u);
        if (fol.containsKey(u)) users.addAll(fol.get(u));
        PriorityQueue<int[]> h = new PriorityQueue<>((a, b) -> b[0] - a[0]);   // [time, id, user, idx]
        for (int uu : users) {
            List<int[]> l = tweets.get(uu);
            if (l != null && !l.isEmpty()) {
                int last = l.size() - 1;
                h.offer(new int[]{l.get(last)[0], l.get(last)[1], uu, last});
            }
        }
        List<Integer> out = new ArrayList<>();
        while (out.size() < 10 && !h.isEmpty()) {
            int[] top = h.poll();
            out.add(top[1]);
            int uu = top[2], idx = top[3];
            if (idx > 0) {
                int[] prev = tweets.get(uu).get(idx - 1);
                h.offer(new int[]{prev[0], prev[1], uu, idx - 1});
            }
        }
        return out;
    }
    public void follow(int f, int g) { fol.computeIfAbsent(f, k -> new HashSet<>()).add(g); }
    public void unfollow(int f, int g) { if (fol.containsKey(f)) fol.get(f).remove(g); }
}
Per-user bounded list (only 10 newest) O(F log F) O(U·10 + F)

Trim tweet lists at 10 to bound memory.

Pseudo-code

keep tweets[u] capped at 10; merge latest 10 of each

Complexity

Time: O(F log F) — Sort F latest.
Space: O(U·10 + F) — Bounded.

Java code

import java.util.*;
class Twitter {
    private int t = 0;
    private Map<Integer, LinkedList<int[]>> tweets = new HashMap<>();
    private Map<Integer, Set<Integer>> fol = new HashMap<>();
    public void postTweet(int u, int id) {
        LinkedList<int[]> l = tweets.computeIfAbsent(u, k -> new LinkedList<>());
        l.addFirst(new int[]{++t, id});
        if (l.size() > 10) l.removeLast();
    }
    public List<Integer> getNewsFeed(int u) {
        List<int[]> all = new ArrayList<>();
        Set<Integer> users = new HashSet<>(); users.add(u);
        if (fol.containsKey(u)) users.addAll(fol.get(u));
        for (int uu : users) if (tweets.containsKey(uu)) all.addAll(tweets.get(uu));
        all.sort((a, b) -> b[0] - a[0]);
        List<Integer> out = new ArrayList<>();
        for (int i = 0; i < Math.min(10, all.size()); i++) out.add(all.get(i)[1]);
        return out;
    }
    public void follow(int f, int g) { fol.computeIfAbsent(f, k -> new HashSet<>()).add(g); }
    public void unfollow(int f, int g) { if (fol.containsKey(f)) fol.get(f).remove(g); }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(T log T)O(F log F)
SpaceO(T)O(U·10+F)
Difficulty2/52/5
WhenDemoMemory bound
PE VerdictK-way merge with max-heap.

Python Solutions

Flat sort O(T log T) O(T)

Same.

Pseudo-code

merge+sort

Complexity

Time: O(T log T) — Sort.
Space: O(T) — List.

Python code

from collections import defaultdict
class Twitter:
    def __init__(self):
        self.t = 0
        self.tweets = defaultdict(list)
        self.fol = defaultdict(set)
    def postTweet(self, u, id):
        self.t += 1; self.tweets[u].append((self.t, id))
    def getNewsFeed(self, u):
        users = {u} | self.fol[u]
        all_t = [x for uu in users for x in self.tweets[uu]]
        all_t.sort(reverse=True)
        return [id for _, id in all_t[:10]]
    def follow(self, f, g): self.fol[f].add(g)
    def unfollow(self, f, g): self.fol[f].discard(g)
K-way merge O(k log F) O(F)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(k log F) — Heap.
Space: O(F) — Heap.

Python code

from collections import defaultdict
import heapq
class Twitter:
    def __init__(self):
        self.t = 0
        self.tweets = defaultdict(list)
        self.fol = defaultdict(set)
    def postTweet(self, u, id):
        self.t += 1; self.tweets[u].append((self.t, id))
    def getNewsFeed(self, u):
        users = self.fol[u] | {u}
        h = []   # negate time for max-heap behavior
        for uu in users:
            if self.tweets[uu]:
                idx = len(self.tweets[uu]) - 1
                tm, id = self.tweets[uu][idx]
                heapq.heappush(h, (-tm, id, uu, idx))
        out = []
        while h and len(out) < 10:
            tm, id, uu, idx = heapq.heappop(h)
            out.append(id)
            if idx > 0:
                ptm, pid = self.tweets[uu][idx-1]
                heapq.heappush(h, (-ptm, pid, uu, idx-1))
        return out
    def follow(self, f, g): self.fol[f].add(g)
    def unfollow(self, f, g): self.fol[f].discard(g)
Bounded per-user O(F log F) O(U·10+F)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(F log F) — Sort bounded.
Space: O(U·10+F) — Bounded.

Python code

from collections import defaultdict, deque
class Twitter:
    def __init__(self):
        self.t = 0
        self.tweets = defaultdict(lambda: deque(maxlen=10))
        self.fol = defaultdict(set)
    def postTweet(self, u, id):
        self.t += 1; self.tweets[u].append((self.t, id))
    def getNewsFeed(self, u):
        users = self.fol[u] | {u}
        all_t = sorted([x for uu in users for x in self.tweets[uu]], reverse=True)
        return [id for _, id in all_t[:10]]
    def follow(self, f, g): self.fol[f].add(g)
    def unfollow(self, f, g): self.fol[f].discard(g)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(T log T)O(F log F)
SpaceO(T)Bounded
Difficulty2/52/5
WhenDemoMemory bound
PE VerdictK-way merge.

What the interviewer is really testing

Composition of HashMap, Set, and k-way merge.

Top gotchas

  • Don't forget self in news feed.
  • unfollow with discard (or contains check) — set.remove throws on missing.
  • Tweet time must be monotonically increasing across all users.

Ship-it

K-way merge with max-heap.

#66 LC 295 Hard Find Median from Data Stream

Two heaps — max-heap of lower half, min-heap of upper half. Median in O(1).

Problem Statement

Design MedianFinder with addNum(int) and findMedian() (returns double).

Signature: class MedianFinder { void addNum(int); double findMedian(); }

Examples

add 1, add 2, median=1.5, add 3, median=2

Constraints

  • -10^5 <= num <= 10^5
  • Up to 5*10^4 addNum calls

Approach Overview

Brute Force

Java: Sorted list with insertion

Python: bisect.insort

add O(n) O(n)

Optimal #1

Java: Two heaps (max + min)

Python: Two heaps (negate for max)

add O(log n); median O(1) O(n)

Optimal #2

Java: Indexed multiset / order-statistic tree

Python: SortedList

add O(log n); median O(log n) O(n)

Java Solutions

Sorted list with insertion add O(n) O(n)

O(n) insert.

Pseudo-code

binary insert; median = middle

Complexity

Time: add O(n) — Shift.
Space: O(n) — List.

Java code

import java.util.*;
class MedianFinder {
    List<Integer> a = new ArrayList<>();
    public void addNum(int n) {
        int i = Collections.binarySearch(a, n);
        if (i < 0) i = -i - 1;
        a.add(i, n);
    }
    public double findMedian() {
        int sz = a.size();
        return sz % 2 == 1 ? a.get(sz/2) : (a.get(sz/2-1) + a.get(sz/2)) / 2.0;
    }
}
Two heaps (max + min) add O(log n); median O(1) O(n)

Balanced halves — max-heap holds lower, min-heap holds upper.

Pseudo-code

add: push to max; balance: top of max -> min; if min larger: min top -> max\nmedian: if equal sizes avg tops; else top of larger

Complexity

Time: add O(log n); median O(1) — Heap.
Space: O(n) — Heaps.

Edge cases & gotchas

  • Maintain |lo| - |hi| ∈ {0, 1} (lo holds extra when odd).

Java code

import java.util.*;
class MedianFinder {
    private PriorityQueue<Integer> lo = new PriorityQueue<>(Comparator.reverseOrder());  // max-heap
    private PriorityQueue<Integer> hi = new PriorityQueue<>();                            // min-heap
    public void addNum(int num) {
        lo.offer(num);
        hi.offer(lo.poll());
        if (hi.size() > lo.size()) lo.offer(hi.poll());
    }
    public double findMedian() {
        if (lo.size() > hi.size()) return lo.peek();
        return (lo.peek() + hi.peek()) / 2.0;
    }
}
Indexed multiset / order-statistic tree add O(log n); median O(log n) O(n)

TreeMap-like; O(log n) by exact rank.

Pseudo-code

insert into TreeMap; median = find rank n/2

Complexity

Time: add O(log n); median O(log n) — Tree.
Space: O(n) — Tree.

Edge cases & gotchas

  • More flexible (kth order statistic), slower constant.

Java code

import java.util.*;
class MedianFinder {
    TreeMap<Integer,Integer> tm = new TreeMap<>();
    int n = 0;
    public void addNum(int num) { tm.merge(num, 1, Integer::sum); n++; }
    public double findMedian() {
        int m1 = n / 2, m2 = (n - 1) / 2;
        int acc = 0, v1 = 0, v2 = 0;
        for (Map.Entry<Integer,Integer> e : tm.entrySet()) {
            int next = acc + e.getValue();
            if (acc <= m2 && m2 < next) v2 = e.getKey();
            if (acc <= m1 && m1 < next) { v1 = e.getKey(); break; }
            acc = next;
        }
        return (v1 + v2) / 2.0;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
Timeadd O(n)add O(log n)
MedianO(1)O(log n)
SpaceO(n)O(n)
Difficulty2/54/5
WhenTinyKth queries too
PE VerdictTwo heaps.

Python Solutions

bisect.insort O(n) O(n)

Same.

Pseudo-code

insort + median

Complexity

Time: O(n) — Shift.
Space: O(n) — List.

Python code

from bisect import insort
class MedianFinder:
    def __init__(self): self.a = []
    def addNum(self, n): insort(self.a, n)
    def findMedian(self) -> float:
        sz = len(self.a)
        return self.a[sz//2] if sz % 2 else (self.a[sz//2-1] + self.a[sz//2]) / 2
Two heaps (negate for max) O(log n) O(n)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(log n) — Heap.
Space: O(n) — Heaps.

Python code

import heapq
class MedianFinder:
    def __init__(self):
        self.lo = []   # max-heap via negation
        self.hi = []   # min-heap
    def addNum(self, num):
        heapq.heappush(self.lo, -num)
        heapq.heappush(self.hi, -heapq.heappop(self.lo))
        if len(self.hi) > len(self.lo):
            heapq.heappush(self.lo, -heapq.heappop(self.hi))
    def findMedian(self) -> float:
        if len(self.lo) > len(self.hi): return -self.lo[0]
        return (-self.lo[0] + self.hi[0]) / 2
SortedList O(log n) O(n)

sortedcontainers gives O(log n).

Pseudo-code

sl.add; median = sl[n//2]

Complexity

Time: O(log n) — Sorted tree.
Space: O(n) — Tree.

Edge cases & gotchas

  • Third-party dependency.

Python code

from sortedcontainers import SortedList
class MedianFinder:
    def __init__(self): self.sl = SortedList()
    def addNum(self, n): self.sl.add(n)
    def findMedian(self) -> float:
        sz = len(self.sl)
        return self.sl[sz//2] if sz % 2 else (self.sl[sz//2-1] + self.sl[sz//2]) / 2

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
addO(n)O(log n)
medianO(1)O(1)
SpaceO(n)O(n)
Difficulty1/52/5
WhenDemoQuick prototype
PE VerdictTwo heaps (stdlib).

What the interviewer is really testing

Streaming median is THE canonical 'two heaps' problem.

Top gotchas

  • Always push to lo first, then balance via hi.
  • Maintain invariant: |lo| ∈ {|hi|, |hi|+1}.
  • heapq is min-only — negate values for max-heap.

Ship-it

Two heaps (lo max, hi min).

Backtracking

#67 LC 78 Medium Subsets

Power set — include/exclude DFS or iterative bit enumeration.

Problem Statement

Return all subsets (the power set) of a list of unique integers.

Signature: List<List<Integer>> subsets(int[] nums)

Examples

[1,2,3] → [[],[1],[2],[3],[1,2],[1,3],[2,3],[1,2,3]]

Constraints

  • 1 <= n <= 10

Approach Overview

Brute Force

Java: Iterative duplicate-and-extend

Python: Iterative extend

O(n·2^n) O(n·2^n)

Optimal #1

Java: Include/exclude DFS backtracking

Python: DFS backtracking

O(n·2^n) O(n)

Optimal #2

Java: Bitmask enumeration

Python: itertools.combinations

O(n·2^n) O(n·2^n)

Java Solutions

Iterative duplicate-and-extend O(n·2^n) O(n·2^n)

Start with [[]]; for each num, duplicate every existing subset adding num.

Pseudo-code

out=[[]]; for n in nums: out += [s+[n] for s in out]

Complexity

Time: O(n·2^n) — Output.
Space: O(n·2^n) — Output.

Java code

import java.util.*;
class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> out = new ArrayList<>();
        out.add(new ArrayList<>());
        for (int n : nums) {
            int sz = out.size();
            for (int i = 0; i < sz; i++) {
                List<Integer> s = new ArrayList<>(out.get(i));
                s.add(n);
                out.add(s);
            }
        }
        return out;
    }
}
Include/exclude DFS backtracking O(n·2^n) O(n)

At each index, choose to include or skip.

Pseudo-code

dfs(i): if i==n: out.add(cur); else: cur.add(nums[i]); dfs(i+1); cur.removeLast(); dfs(i+1)

Complexity

Time: O(n·2^n) — All subsets.
Space: O(n) — Stack + cur.

Java code

import java.util.*;
class Solution {
    private List<List<Integer>> out = new ArrayList<>();
    private int[] nums;
    public List<List<Integer>> subsets(int[] nums) {
        this.nums = nums;
        dfs(0, new ArrayList<>());
        return out;
    }
    private void dfs(int i, List<Integer> cur) {
        if (i == nums.length) { out.add(new ArrayList<>(cur)); return; }
        cur.add(nums[i]); dfs(i+1, cur);
        cur.remove(cur.size()-1); dfs(i+1, cur);
    }
}
Bitmask enumeration O(n·2^n) O(n·2^n)

For each bitmask 0..2^n - 1, build subset from set bits.

Pseudo-code

for m in 0..1<<n: out.add([nums[i] for i where bit i of m is set])

Complexity

Time: O(n·2^n) — Output.
Space: O(n·2^n) — Output.

Java code

import java.util.*;
class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> out = new ArrayList<>();
        int n = nums.length;
        for (int m = 0; m < (1 << n); m++) {
            List<Integer> s = new ArrayList<>();
            for (int i = 0; i < n; i++) if (((m >> i) & 1) == 1) s.add(nums[i]);
            out.add(s);
        }
        return out;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n·2^n)O(n·2^n)
SpaceOutputOutput
Difficulty2/52/5
WhenQuickGeneralizes to enumeration
PE VerdictBacktracking template.

Python Solutions

Iterative extend O(n·2^n) O(n·2^n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n·2^n) — Output.
Space: O(n·2^n) — Output.

Python code

from typing import List
class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        out = [[]]
        for n in nums:
            out += [s + [n] for s in out]
        return out
DFS backtracking O(n·2^n) O(n)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(n·2^n) — Output.
Space: O(n) — Stack.

Python code

from typing import List
class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        out = []
        def dfs(i, cur):
            if i == len(nums): out.append(cur[:]); return
            cur.append(nums[i]); dfs(i+1, cur); cur.pop()
            dfs(i+1, cur)
        dfs(0, [])
        return out
itertools.combinations O(n·2^n) O(n·2^n)

Combine over all lengths.

Pseudo-code

for r in 0..n: combinations(nums, r)

Complexity

Time: O(n·2^n) — Output.
Space: O(n·2^n) — Output.

Python code

from typing import List
from itertools import combinations
class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        return [list(c) for r in range(len(nums)+1) for c in combinations(nums, r)]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n·2^n)O(n·2^n)
SpaceOutputOutput
Difficulty1/51/5
WhenDemoPythonic
PE VerdictDFS backtracking template.

What the interviewer is really testing

Foundation for all subset/combination problems.

Top gotchas

  • Output deep-copies cur (don't share the mutable list).
  • Include/exclude order doesn't matter for set output.

Ship-it

DFS with backtracking.

#68 LC 39 Medium Combination Sum

DFS with index — each candidate can be reused unlimited times.

Problem Statement

Given distinct positive candidates and target, return all unique combinations summing to target. Each number may be used unlimited times.

Signature: List<List<Integer>> combinationSum(int[] candidates, int target)

Examples

[2,3,6,7], 7 → [[2,2,3],[7]]

Constraints

  • 1 <= n <= 30
  • 2 <= cand[i] <= 40
  • 1 <= target <= 40

Approach Overview

Brute Force

Java: DFS without start index

Python: DFS + dedupe

Exp. Exp.

Optimal #1

Java: DFS with start index (prevent duplicates)

Python: DFS with start index

O(2^t/min) O(t/min)

Optimal #2

Java: Iterative DP-like enumeration

Python: DP table

O(n·t·avg) O(n·t·avg)

Java Solutions

DFS without start index Exp. Exp.

Generates duplicates; dedupe via set.

Pseudo-code

DFS pick any; sort + dedupe

Complexity

Time: Exp. — Branching.
Space: Exp. — Set.

Edge cases & gotchas

  • Wasteful.

Java code

import java.util.*;
class Solution {
    Set<List<Integer>> out = new HashSet<>();
    public List<List<Integer>> combinationSum(int[] c, int t) {
        Arrays.sort(c);
        dfs(c, t, new ArrayList<>());
        return new ArrayList<>(out);
    }
    private void dfs(int[] c, int t, List<Integer> cur) {
        if (t == 0) { List<Integer> s = new ArrayList<>(cur); Collections.sort(s); out.add(s); return; }
        for (int x : c) if (x <= t) { cur.add(x); dfs(c, t-x, cur); cur.remove(cur.size()-1); }
    }
}
DFS with start index (prevent duplicates) O(2^t/min) O(t/min)

Each level only considers candidates from current index forward.

Pseudo-code

dfs(i, remaining): if 0 emit; for j>=i: if c[j]<=rem: pick, dfs(j, rem-c[j]), unpick

Complexity

Time: O(2^t/min) — Branching bounded by target/min.
Space: O(t/min) — Stack.

Edge cases & gotchas

  • Pass j (not j+1) since reuse is allowed.

Java code

import java.util.*;
class Solution {
    private List<List<Integer>> out = new ArrayList<>();
    private int[] c;
    public List<List<Integer>> combinationSum(int[] cand, int target) {
        c = cand; Arrays.sort(c);
        dfs(0, target, new ArrayList<>());
        return out;
    }
    private void dfs(int i, int rem, List<Integer> cur) {
        if (rem == 0) { out.add(new ArrayList<>(cur)); return; }
        for (int j = i; j < c.length && c[j] <= rem; j++) {
            cur.add(c[j]);
            dfs(j, rem - c[j], cur);
            cur.remove(cur.size()-1);
        }
    }
}
Iterative DP-like enumeration O(n·t·avg) O(n·t·avg)

dp[t] = list of combos summing to t.

Pseudo-code

for t in 1..target: for c: combine with dp[t-c]

Complexity

Time: O(n·t·avg) — Build table.
Space: O(n·t·avg) — Table.

Edge cases & gotchas

  • Slow and memory-heavy; backtracking is cleaner.

Java code

import java.util.*;
class Solution {
    public List<List<Integer>> combinationSum(int[] c, int t) {
        List<List<List<Integer>>> dp = new ArrayList<>();
        for (int i = 0; i <= t; i++) dp.add(new ArrayList<>());
        dp.get(0).add(new ArrayList<>());
        Arrays.sort(c);
        for (int s = 1; s <= t; s++) {
            for (int x : c) {
                if (x > s) break;
                for (List<Integer> prev : dp.get(s - x)) {
                    if (!prev.isEmpty() && x < prev.get(prev.size()-1)) continue;
                    List<Integer> ns = new ArrayList<>(prev); ns.add(x);
                    dp.get(s).add(ns);
                }
            }
        }
        return dp.get(t);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeExpPoly·exp
SpaceExpBig
Difficulty2/55/5
WhenDon'tPedagogy
PE VerdictDFS with start index.

Python Solutions

DFS + dedupe Exp Exp

Same.

Pseudo-code

see Java

Complexity

Time: Exp — Brute.
Space: Exp — Set.

Python code

from typing import List
class Solution:
    def combinationSum(self, c: List[int], t: int) -> List[List[int]]:
        c = sorted(c)
        out = set()
        def dfs(rem, cur):
            if rem == 0: out.add(tuple(sorted(cur))); return
            for x in c:
                if x > rem: break
                cur.append(x); dfs(rem - x, cur); cur.pop()
        dfs(t, [])
        return [list(s) for s in out]
DFS with start index Exp O(t)

Same as Java.

Pseudo-code

see Java

Complexity

Time: Exp — Branching.
Space: O(t) — Stack.

Python code

from typing import List
class Solution:
    def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
        candidates.sort()
        out = []
        def dfs(i, rem, cur):
            if rem == 0: out.append(cur[:]); return
            for j in range(i, len(candidates)):
                if candidates[j] > rem: break
                cur.append(candidates[j])
                dfs(j, rem - candidates[j], cur)
                cur.pop()
        dfs(0, target, [])
        return out
DP table Big Big

Same as Java.

Pseudo-code

see Java

Complexity

Time: Big — Build table.
Space: Big — Table.

Python code

from typing import List
class Solution:
    def combinationSum(self, c: List[int], t: int) -> List[List[int]]:
        c = sorted(c)
        dp = [[] for _ in range(t+1)]
        dp[0] = [[]]
        for s in range(1, t+1):
            for x in c:
                if x > s: break
                for prev in dp[s - x]:
                    if prev and x < prev[-1]: continue
                    dp[s].append(prev + [x])
        return dp[t]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeExpBig
SpaceExpBig
Difficulty2/55/5
WhenDon'tPedagogy
PE VerdictDFS with start index.

What the interviewer is really testing

Backtracking with reuse.

Top gotchas

  • Recurse with j (reuse), not j+1.
  • Sort and prune via break — saves big.
  • Copy cur on emit.

Ship-it

DFS start-index backtracking.

#69 LC 40 Medium Combination Sum II

Like combination sum but each number used at most once + duplicates in input.

Problem Statement

Given candidates (may contain duplicates) and target, return all unique combinations summing to target. Each candidate used at most once.

Signature: List<List<Integer>> combinationSum2(int[] candidates, int target)

Examples

[10,1,2,7,6,1,5], 8 → [[1,1,6],[1,2,5],[1,7],[2,6]]

Constraints

  • 1 <= n <= 100

Approach Overview

Brute Force

Java: All subsets + filter + dedupe

Python: Bitmask enumeration

O(2^n) O(2^n)

Optimal #1

Java: DFS with start + skip duplicates at same depth

Python: DFS + skip dup

O(2^n) O(n)

Optimal #2

Java: DFS with counts (group duplicates)

Python: Group counts

O(2^n) O(n)

Java Solutions

All subsets + filter + dedupe O(2^n) O(2^n)

2^n subsets; check sum.

Pseudo-code

enumerate all subsets; keep those summing to t; dedupe

Complexity

Time: O(2^n) — Power set.
Space: O(2^n) — Set.

Java code

import java.util.*;
class Solution {
    public List<List<Integer>> combinationSum2(int[] c, int t) {
        Arrays.sort(c);
        Set<List<Integer>> out = new HashSet<>();
        for (int m = 0; m < (1 << c.length); m++) {
            List<Integer> s = new ArrayList<>();
            int sum = 0;
            for (int i = 0; i < c.length; i++) if (((m >> i) & 1) == 1) { s.add(c[i]); sum += c[i]; }
            if (sum == t) out.add(s);
        }
        return new ArrayList<>(out);
    }
}
DFS with start + skip duplicates at same depth O(2^n) O(n)

Sort; skip j > i where c[j] == c[j-1].

Pseudo-code

sort; dfs(i, rem): for j>=i: if j>i and c[j]==c[j-1] skip; pick; dfs(j+1, rem-c[j])

Complexity

Time: O(2^n) — Branching with pruning.
Space: O(n) — Stack.

Edge cases & gotchas

  • Recurse with j+1 (not j) — single use.

Java code

import java.util.*;
class Solution {
    private List<List<Integer>> out = new ArrayList<>();
    private int[] c;
    public List<List<Integer>> combinationSum2(int[] cand, int target) {
        c = cand; Arrays.sort(c);
        dfs(0, target, new ArrayList<>());
        return out;
    }
    private void dfs(int i, int rem, List<Integer> cur) {
        if (rem == 0) { out.add(new ArrayList<>(cur)); return; }
        for (int j = i; j < c.length && c[j] <= rem; j++) {
            if (j > i && c[j] == c[j-1]) continue;
            cur.add(c[j]);
            dfs(j+1, rem - c[j], cur);
            cur.remove(cur.size()-1);
        }
    }
}
DFS with counts (group duplicates) O(2^n) O(n)

Convert to (value, count) pairs; choose 0..count per group.

Pseudo-code

groups = (val, cnt); dfs(i, rem): for k in 0..cnt[i]: dfs(i+1, rem - k*val[i])

Complexity

Time: O(2^n) — Same branching.
Space: O(n) — Stack.

Java code

import java.util.*;
class Solution {
    private List<int[]> g = new ArrayList<>();
    private List<List<Integer>> out = new ArrayList<>();
    public List<List<Integer>> combinationSum2(int[] c, int t) {
        Arrays.sort(c);
        for (int i = 0; i < c.length; ) {
            int j = i;
            while (j < c.length && c[j] == c[i]) j++;
            g.add(new int[]{c[i], j - i});
            i = j;
        }
        dfs(0, t, new ArrayList<>());
        return out;
    }
    private void dfs(int gi, int rem, List<Integer> cur) {
        if (rem == 0) { out.add(new ArrayList<>(cur)); return; }
        if (gi == g.size()) return;
        int v = g.get(gi)[0], n = g.get(gi)[1];
        for (int k = 0; k <= n && k * v <= rem; k++) {
            for (int z = 0; z < k; z++) cur.add(v);
            dfs(gi+1, rem - k * v, cur);
            for (int z = 0; z < k; z++) cur.remove(cur.size()-1);
        }
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(2^n)
SpaceO(2^n)O(n)
Difficulty2/54/5
WhenTinyHeavy duplicates
PE VerdictDFS with skip-duplicates-at-same-depth.

Python Solutions

Bitmask enumeration O(2^n) O(2^n)

Same.

Pseudo-code

see Java

Complexity

Time: O(2^n) — Power set.
Space: O(2^n) — Set.

Python code

from typing import List
class Solution:
    def combinationSum2(self, c: List[int], t: int) -> List[List[int]]:
        c = sorted(c); n = len(c); out = set()
        for m in range(1 << n):
            s = []; total = 0
            for i in range(n):
                if (m >> i) & 1: s.append(c[i]); total += c[i]
            if total == t: out.add(tuple(s))
        return [list(x) for x in out]
DFS + skip dup O(2^n) O(n)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(2^n) — Branching.
Space: O(n) — Stack.

Python code

from typing import List
class Solution:
    def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
        candidates.sort()
        out = []
        def dfs(i, rem, cur):
            if rem == 0: out.append(cur[:]); return
            for j in range(i, len(candidates)):
                if candidates[j] > rem: break
                if j > i and candidates[j] == candidates[j-1]: continue
                cur.append(candidates[j])
                dfs(j+1, rem - candidates[j], cur)
                cur.pop()
        dfs(0, target, [])
        return out
Group counts O(2^n) O(n)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(2^n) — Same.
Space: O(n) — Stack.

Python code

from typing import List
from collections import Counter
class Solution:
    def combinationSum2(self, c: List[int], t: int) -> List[List[int]]:
        g = sorted(Counter(c).items())
        out = []
        def dfs(i, rem, cur):
            if rem == 0: out.append(cur[:]); return
            if i == len(g): return
            v, n = g[i]
            for k in range(0, n+1):
                if k * v > rem: break
                cur.extend([v]*k)
                dfs(i+1, rem - k*v, cur)
                for _ in range(k): cur.pop()
        dfs(0, t, [])
        return out

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(2^n)
SpaceO(2^n)O(n)
Difficulty2/54/5
WhenTinyMany dupes
PE VerdictDFS skip dup at same depth.

What the interviewer is really testing

Standard variant — single-use + duplicates.

Top gotchas

  • The skip-dup check is `j > i`, NOT `j > 0`.
  • Recurse with j+1 (single use).
  • Sort first.

Ship-it

DFS with start + skip-dup-at-same-depth.

#70 LC 46 Medium Permutations

All n! permutations — DFS with used set, or in-place swap.

Problem Statement

Return all permutations of a list of distinct integers.

Signature: List<List<Integer>> permute(int[] nums)

Examples

[1,2,3] → [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

Constraints

  • 1 <= n <= 6

Approach Overview

Brute Force

Java: Heap's algorithm (in-place)

Python: itertools.permutations

O(n!) O(n)

Optimal #1

Java: DFS with used[] flags

Python: DFS with used

O(n·n!) O(n)

Optimal #2

Java: In-place swap DFS

Python: In-place swap

O(n·n!) O(n)

Java Solutions

Heap's algorithm (in-place) O(n!) O(n)

O(n!).

Pseudo-code

Heap's algorithm

Complexity

Time: O(n!) — All perms.
Space: O(n) — Stack.

Java code

import java.util.*;
class Solution {
    private List<List<Integer>> out = new ArrayList<>();
    public List<List<Integer>> permute(int[] nums) { heap(nums, nums.length); return out; }
    private void heap(int[] a, int k) {
        if (k == 1) { List<Integer> l = new ArrayList<>(); for (int x : a) l.add(x); out.add(l); return; }
        for (int i = 0; i < k; i++) {
            heap(a, k-1);
            int j = (k % 2 == 0) ? i : 0;
            int t = a[j]; a[j] = a[k-1]; a[k-1] = t;
        }
    }
}
DFS with used[] flags O(n·n!) O(n)

At each level, pick any not-yet-used.

Pseudo-code

dfs(cur): if size==n emit; else for each unused: mark used; add; dfs; unmark; remove

Complexity

Time: O(n·n!) — Build each perm.
Space: O(n) — Stack.

Java code

import java.util.*;
class Solution {
    private List<List<Integer>> out = new ArrayList<>();
    private boolean[] used;
    private int[] nums;
    public List<List<Integer>> permute(int[] nums) {
        this.nums = nums; used = new boolean[nums.length];
        dfs(new ArrayList<>());
        return out;
    }
    private void dfs(List<Integer> cur) {
        if (cur.size() == nums.length) { out.add(new ArrayList<>(cur)); return; }
        for (int i = 0; i < nums.length; i++) {
            if (used[i]) continue;
            used[i] = true; cur.add(nums[i]);
            dfs(cur);
            cur.remove(cur.size()-1); used[i] = false;
        }
    }
}
In-place swap DFS O(n·n!) O(n)

Swap nums[start] with each i >= start; recurse start+1.

Pseudo-code

dfs(s): if s==n emit nums; for i>=s: swap(s,i); dfs(s+1); swap(s,i)

Complexity

Time: O(n·n!) — Same.
Space: O(n) — Stack.

Java code

import java.util.*;
class Solution {
    private List<List<Integer>> out = new ArrayList<>();
    private int[] nums;
    public List<List<Integer>> permute(int[] nums) { this.nums = nums; dfs(0); return out; }
    private void dfs(int s) {
        if (s == nums.length) {
            List<Integer> l = new ArrayList<>(); for (int x : nums) l.add(x); out.add(l); return;
        }
        for (int i = s; i < nums.length; i++) {
            int t = nums[s]; nums[s] = nums[i]; nums[i] = t;
            dfs(s+1);
            t = nums[s]; nums[s] = nums[i]; nums[i] = t;
        }
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n!)O(n·n!)
SpaceO(n)O(n)
Difficulty4/53/5
WhenPedagogyMemory tight
PE VerdictDFS with used flags.

Python Solutions

itertools.permutations O(n!) O(n!)

C-fast.

Pseudo-code

list(permutations(nums))

Complexity

Time: O(n!) — All perms.
Space: O(n!) — Output.

Python code

from typing import List
from itertools import permutations
class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        return [list(p) for p in permutations(nums)]
DFS with used O(n·n!) O(n)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(n·n!) — Linear per perm.
Space: O(n) — Stack.

Python code

from typing import List
class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        out = []; used = [False]*len(nums)
        def dfs(cur):
            if len(cur) == len(nums): out.append(cur[:]); return
            for i in range(len(nums)):
                if used[i]: continue
                used[i] = True; cur.append(nums[i])
                dfs(cur)
                cur.pop(); used[i] = False
        dfs([])
        return out
In-place swap O(n·n!) O(n)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(n·n!) — Same.
Space: O(n) — Stack.

Python code

from typing import List
class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        out = []
        def dfs(s):
            if s == len(nums): out.append(nums[:]); return
            for i in range(s, len(nums)):
                nums[s], nums[i] = nums[i], nums[s]
                dfs(s+1)
                nums[s], nums[i] = nums[i], nums[s]
        dfs(0)
        return out

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n!)O(n·n!)
SpaceO(n!)O(n)
Difficulty1/53/5
WhenDemoMemory tight
PE VerdictDFS with used.

What the interviewer is really testing

Template question.

Top gotchas

  • Don't share cur reference — copy on emit.
  • Reset used[] AFTER recursion in the same iteration.

Ship-it

DFS with used flags.

#71 LC 90 Medium Subsets II

Subsets with duplicates — sort then skip same value at same depth.

Problem Statement

Given an array with possible duplicates, return all unique subsets.

Signature: List<List<Integer>> subsetsWithDup(int[] nums)

Examples

[1,2,2] → [[],[1],[1,2],[1,2,2],[2],[2,2]]

Constraints

  • 1 <= n <= 10

Approach Overview

Brute Force

Java: All subsets then dedupe

Python: Bitmask + set

O(2^n) O(2^n)

Optimal #1

Java: DFS with start + skip-dup-at-same-depth

Python: DFS skip dup

O(2^n) O(n)

Optimal #2

Java: Iterative duplicate-and-extend with dup-handling

Python: Iterative with dup-tracking

O(2^n) O(2^n)

Java Solutions

All subsets then dedupe O(2^n) O(2^n)

Set of sorted tuples.

Pseudo-code

see Subsets but dedupe

Complexity

Time: O(2^n) — Power set.
Space: O(2^n) — Set.

Java code

import java.util.*;
class Solution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        Set<List<Integer>> out = new HashSet<>();
        for (int m = 0; m < (1 << nums.length); m++) {
            List<Integer> s = new ArrayList<>();
            for (int i = 0; i < nums.length; i++) if (((m >> i) & 1) == 1) s.add(nums[i]);
            out.add(s);
        }
        return new ArrayList<>(out);
    }
}
DFS with start + skip-dup-at-same-depth O(2^n) O(n)

Sort; for j > i where nums[j] == nums[j-1], skip.

Pseudo-code

sort; dfs(i, cur): emit cur; for j>=i: if j>i and nums[j]==nums[j-1] skip; pick; dfs(j+1, cur); unpick

Complexity

Time: O(2^n) — Branching with pruning.
Space: O(n) — Stack.

Java code

import java.util.*;
class Solution {
    private List<List<Integer>> out = new ArrayList<>();
    private int[] nums;
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        this.nums = nums; Arrays.sort(nums);
        dfs(0, new ArrayList<>());
        return out;
    }
    private void dfs(int i, List<Integer> cur) {
        out.add(new ArrayList<>(cur));
        for (int j = i; j < nums.length; j++) {
            if (j > i && nums[j] == nums[j-1]) continue;
            cur.add(nums[j]);
            dfs(j+1, cur);
            cur.remove(cur.size()-1);
        }
    }
}
Iterative duplicate-and-extend with dup-handling O(2^n) O(2^n)

For dup runs, only extend subsets created in the previous round.

Pseudo-code

sort; out=[[]]; track start; if dup, extend only [start:] else 0

Complexity

Time: O(2^n) — Linear in output.
Space: O(2^n) — Output.

Java code

import java.util.*;
class Solution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> out = new ArrayList<>();
        out.add(new ArrayList<>());
        int start = 0, prevSize = 0;
        for (int i = 0; i < nums.length; i++) {
            int from = (i > 0 && nums[i] == nums[i-1]) ? prevSize : 0;
            prevSize = out.size();
            for (int k = from; k < prevSize; k++) {
                List<Integer> s = new ArrayList<>(out.get(k));
                s.add(nums[i]);
                out.add(s);
            }
        }
        return out;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(2^n)
SpaceO(2^n)Output
Difficulty2/54/5
WhenDemoMemory
PE VerdictDFS with skip-dup.

Python Solutions

Bitmask + set O(2^n) O(2^n)

Same.

Pseudo-code

see Java

Complexity

Time: O(2^n) — Power set.
Space: O(2^n) — Set.

Python code

from typing import List
class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        nums = sorted(nums); n = len(nums); out = set()
        for m in range(1 << n):
            s = tuple(nums[i] for i in range(n) if (m >> i) & 1)
            out.add(s)
        return [list(s) for s in out]
DFS skip dup O(2^n) O(n)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(2^n) — Branching.
Space: O(n) — Stack.

Python code

from typing import List
class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        nums.sort(); out = []
        def dfs(i, cur):
            out.append(cur[:])
            for j in range(i, len(nums)):
                if j > i and nums[j] == nums[j-1]: continue
                cur.append(nums[j]); dfs(j+1, cur); cur.pop()
        dfs(0, [])
        return out
Iterative with dup-tracking O(2^n) O(2^n)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(2^n) — Linear.
Space: O(2^n) — Output.

Python code

from typing import List
class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        nums.sort(); out = [[]]; prev_size = 0
        for i, x in enumerate(nums):
            from_ = prev_size if i > 0 and nums[i] == nums[i-1] else 0
            prev_size = len(out)
            for k in range(from_, prev_size):
                out.append(out[k] + [x])
        return out

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(2^n)
SpaceO(2^n)Output
Difficulty2/54/5
WhenDemoMemory
PE VerdictDFS skip-dup.

What the interviewer is really testing

Subset variant with dedupe at same depth.

Top gotchas

  • Skip-check is `j > i`, not `j > 0`.
  • Sort first — duplicates must be adjacent.

Ship-it

DFS start-index + skip-dup.

#72 LC 22 Medium Generate Parentheses

Backtrack with constraint: open ≤ n, close ≤ open.

Problem Statement

Generate all combinations of n pairs of well-formed parentheses.

Signature: List<String> generateParenthesis(int n)

Examples

n=3 → ["((()))","(()())","(())()","()(())","()()()"]

Constraints

  • 1 <= n <= 8

Approach Overview

Brute Force

Java: Generate all 2^(2n) strings, filter valid

Python: Brute + filter

O(2^(2n)·n) O(2^(2n))

Optimal #1

Java: Backtracking with (open, close) counts

Python: Backtracking

O(C(2n,n)) O(n)

Optimal #2

Java: DP — build from f(n-1)

Python: DP

O(C(2n,n)) O(C(2n,n))

Java Solutions

Generate all 2^(2n) strings, filter valid O(2^(2n)·n) O(2^(2n))

Wasteful.

Pseudo-code

brute generate; check balanced

Complexity

Time: O(2^(2n)·n) — Filter.
Space: O(2^(2n)) — All.

Java code

import java.util.*;
class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> out = new ArrayList<>();
        gen(out, new char[2*n], 0);
        return out;
    }
    private void gen(List<String> out, char[] buf, int i) {
        if (i == buf.length) { if (valid(buf)) out.add(new String(buf)); return; }
        buf[i] = '('; gen(out, buf, i+1);
        buf[i] = ')'; gen(out, buf, i+1);
    }
    private boolean valid(char[] s) {
        int b = 0;
        for (char c : s) { b += c == '(' ? 1 : -1; if (b < 0) return false; }
        return b == 0;
    }
}
Backtracking with (open, close) counts O(C(2n,n)) O(n)

Only add '(' if open < n; only add ')' if close < open.

Pseudo-code

dfs(open, close, cur): if 2n: emit; if open<n: add '('; if close<open: add ')'

Complexity

Time: O(C(2n,n)) — Catalan growth.
Space: O(n) — Stack.

Java code

import java.util.*;
class Solution {
    private List<String> out = new ArrayList<>();
    private int n;
    public List<String> generateParenthesis(int n) {
        this.n = n; dfs(0, 0, new StringBuilder()); return out;
    }
    private void dfs(int open, int close, StringBuilder cur) {
        if (cur.length() == 2 * n) { out.add(cur.toString()); return; }
        if (open < n) { cur.append('('); dfs(open+1, close, cur); cur.deleteCharAt(cur.length()-1); }
        if (close < open) { cur.append(')'); dfs(open, close+1, cur); cur.deleteCharAt(cur.length()-1); }
    }
}
DP — build from f(n-1) O(C(2n,n)) O(C(2n,n))

f(n) = '(' + f(i) + ')' + f(n-1-i) for i in 0..n-1.

Pseudo-code

f[0]=['']; f[i] = combine '(' f[j] ')' f[i-1-j]

Complexity

Time: O(C(2n,n)) — Build incrementally.
Space: O(C(2n,n)) — Cache.

Java code

import java.util.*;
class Solution {
    public List<String> generateParenthesis(int n) {
        List<List<String>> f = new ArrayList<>();
        f.add(Collections.singletonList(""));
        for (int i = 1; i <= n; i++) {
            List<String> cur = new ArrayList<>();
            for (int j = 0; j < i; j++)
                for (String a : f.get(j))
                    for (String b : f.get(i-1-j))
                        cur.add("(" + a + ")" + b);
            f.add(cur);
        }
        return f.get(n);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^(2n)n)O(C(2n,n))
SpaceO(2^(2n))Catalan
Difficulty2/54/5
WhenDon'tPedagogy
PE VerdictBacktracking with counts.

Python Solutions

Brute + filter O(2^(2n)n) O(2^(2n))

Same.

Pseudo-code

see Java

Complexity

Time: O(2^(2n)n) — Brute.
Space: O(2^(2n)) — All.

Python code

from typing import List
class Solution:
    def generateParenthesis(self, n: int) -> List[str]:
        out = []
        def gen(s):
            if len(s) == 2*n:
                b = 0; ok = True
                for c in s:
                    b += 1 if c == '(' else -1
                    if b < 0: ok = False; break
                if ok and b == 0: out.append(s)
                return
            gen(s+'('); gen(s+')')
        gen("")
        return out
Backtracking O(C(2n,n)) O(n)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(C(2n,n)) — Catalan.
Space: O(n) — Stack.

Python code

from typing import List
class Solution:
    def generateParenthesis(self, n: int) -> List[str]:
        out = []
        def dfs(open, close, cur):
            if len(cur) == 2*n: out.append(cur); return
            if open < n: dfs(open+1, close, cur+'(')
            if close < open: dfs(open, close+1, cur+')')
        dfs(0, 0, "")
        return out
DP O(C(2n,n)) O(C(2n,n))

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(C(2n,n)) — Build.
Space: O(C(2n,n)) — Cache.

Python code

from typing import List
class Solution:
    def generateParenthesis(self, n: int) -> List[str]:
        f = [['']]
        for i in range(1, n+1):
            cur = []
            for j in range(i):
                for a in f[j]:
                    for b in f[i-1-j]:
                        cur.append(f"({a}){b}")
            f.append(cur)
        return f[n]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^(2n)n)O(C(2n,n))
SpaceO(2^(2n))Catalan
Difficulty2/54/5
WhenDon'tPedagogy
PE VerdictBacktracking with constraints.

What the interviewer is really testing

Constraint-driven backtracking.

Top gotchas

  • Only `(` if open < n.
  • Only `)` if close < open.
  • Emit when len == 2n, not when balanced is 0.

Ship-it

DFS with (open, close) counts.

#73 LC 79 Medium Word Search

Grid DFS — match word path with in-place visited marking.

Problem Statement

Given an m×n board and a word, return whether the word can be formed by adjacent cells (no cell used twice).

Signature: boolean exist(char[][] board, String word)

Examples

board=[['A','B','C','E'],['S','F','C','S'],['A','D','E','E']], word='ABCCED' → true

Constraints

  • 1 <= m, n <= 6
  • 1 <= word.length <= 15

Approach Overview

Brute Force

Java: DFS with visited set

Python: DFS + visited

O(m·n·4^L) O(L + m·n)

Optimal #1

Java: DFS with in-place marking (no extra space)

Python: DFS in-place

O(m·n·4^L) O(L)

Optimal #2

Java: DFS + Aho-Corasick-style pruning

Python: With char-count pruning

O(m·n·4^L) O(L)

Java Solutions

DFS with visited set O(m·n·4^L) O(L + m·n)

Standard.

Pseudo-code

for each cell: dfs with visited

Complexity

Time: O(m·n·4^L) — 4 directions, depth L.
Space: O(L + m·n) — Visited.

Java code

import java.util.*;
class Solution {
    public boolean exist(char[][] b, String w) {
        for (int i = 0; i < b.length; i++) for (int j = 0; j < b[0].length; j++)
            if (dfs(b, w, i, j, 0, new boolean[b.length][b[0].length])) return true;
        return false;
    }
    private boolean dfs(char[][] b, String w, int i, int j, int k, boolean[][] v) {
        if (k == w.length()) return true;
        if (i < 0 || j < 0 || i >= b.length || j >= b[0].length || v[i][j] || b[i][j] != w.charAt(k)) return false;
        v[i][j] = true;
        boolean ok = dfs(b,w,i+1,j,k+1,v) || dfs(b,w,i-1,j,k+1,v) || dfs(b,w,i,j+1,k+1,v) || dfs(b,w,i,j-1,k+1,v);
        v[i][j] = false;
        return ok;
    }
}
DFS with in-place marking (no extra space) O(m·n·4^L) O(L)

Temporarily overwrite the char with a sentinel.

Pseudo-code

mark visited by setting board[i][j] = '#' during recursion; restore

Complexity

Time: O(m·n·4^L) — Same.
Space: O(L) — Recursion only.

Edge cases & gotchas

  • MUST restore the char on backtrack.

Java code

class Solution {
    public boolean exist(char[][] b, String w) {
        for (int i = 0; i < b.length; i++) for (int j = 0; j < b[0].length; j++)
            if (dfs(b, w, i, j, 0)) return true;
        return false;
    }
    private boolean dfs(char[][] b, String w, int i, int j, int k) {
        if (k == w.length()) return true;
        if (i < 0 || j < 0 || i >= b.length || j >= b[0].length || b[i][j] != w.charAt(k)) return false;
        char tmp = b[i][j]; b[i][j] = '#';
        boolean ok = dfs(b,w,i+1,j,k+1) || dfs(b,w,i-1,j,k+1) || dfs(b,w,i,j+1,k+1) || dfs(b,w,i,j-1,k+1);
        b[i][j] = tmp;
        return ok;
    }
}
DFS + Aho-Corasick-style pruning O(m·n·4^L) O(L)

Pre-count chars; bail if word can't possibly fit.

Pseudo-code

count chars in board; if word has more of any char, return false

Complexity

Time: O(m·n·4^L) — Same asymptote.
Space: O(L) — Same.

Java code

class Solution {
    public boolean exist(char[][] b, String w) {
        int[] cnt = new int[128];
        for (char[] r : b) for (char c : r) cnt[c]++;
        int[] wcnt = new int[128];
        for (char c : w.toCharArray()) wcnt[c]++;
        for (int c = 0; c < 128; c++) if (wcnt[c] > cnt[c]) return false;
        for (int i = 0; i < b.length; i++) for (int j = 0; j < b[0].length; j++)
            if (dfs(b, w, i, j, 0)) return true;
        return false;
    }
    private boolean dfs(char[][] b, String w, int i, int j, int k) {
        if (k == w.length()) return true;
        if (i < 0 || j < 0 || i >= b.length || j >= b[0].length || b[i][j] != w.charAt(k)) return false;
        char tmp = b[i][j]; b[i][j] = '#';
        boolean ok = dfs(b,w,i+1,j,k+1) || dfs(b,w,i-1,j,k+1) || dfs(b,w,i,j+1,k+1) || dfs(b,w,i,j-1,k+1);
        b[i][j] = tmp;
        return ok;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(m·n·4^L)Same
SpaceO(m·n)O(L)
Difficulty2/53/5
WhenDemoAdversarial inputs
PE VerdictDFS with in-place marking.

Python Solutions

DFS + visited O(m·n·4^L) O(m·n)

Same.

Pseudo-code

see Java

Complexity

Time: O(m·n·4^L) — Same.
Space: O(m·n) — Visited.

Python code

from typing import List
class Solution:
    def exist(self, b: List[List[str]], w: str) -> bool:
        m, n = len(b), len(b[0])
        v = [[False]*n for _ in range(m)]
        def dfs(i, j, k):
            if k == len(w): return True
            if i<0 or j<0 or i>=m or j>=n or v[i][j] or b[i][j] != w[k]: return False
            v[i][j] = True
            ok = dfs(i+1,j,k+1) or dfs(i-1,j,k+1) or dfs(i,j+1,k+1) or dfs(i,j-1,k+1)
            v[i][j] = False
            return ok
        return any(dfs(i,j,0) for i in range(m) for j in range(n))
DFS in-place O(m·n·4^L) O(L)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(m·n·4^L) — Same.
Space: O(L) — Recursion.

Python code

from typing import List
class Solution:
    def exist(self, b: List[List[str]], w: str) -> bool:
        m, n = len(b), len(b[0])
        def dfs(i, j, k):
            if k == len(w): return True
            if i<0 or j<0 or i>=m or j>=n or b[i][j] != w[k]: return False
            tmp = b[i][j]; b[i][j] = '#'
            ok = dfs(i+1,j,k+1) or dfs(i-1,j,k+1) or dfs(i,j+1,k+1) or dfs(i,j-1,k+1)
            b[i][j] = tmp
            return ok
        return any(dfs(i,j,0) for i in range(m) for j in range(n))
With char-count pruning O(m·n·4^L) O(L)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(m·n·4^L) — Pruned.
Space: O(L) — Same.

Python code

from typing import List
from collections import Counter
class Solution:
    def exist(self, b: List[List[str]], w: str) -> bool:
        bc = Counter(c for row in b for c in row); wc = Counter(w)
        if any(wc[c] > bc[c] for c in wc): return False
        m, n = len(b), len(b[0])
        def dfs(i, j, k):
            if k == len(w): return True
            if i<0 or j<0 or i>=m or j>=n or b[i][j] != w[k]: return False
            tmp = b[i][j]; b[i][j] = '#'
            ok = dfs(i+1,j,k+1) or dfs(i-1,j,k+1) or dfs(i,j+1,k+1) or dfs(i,j-1,k+1)
            b[i][j] = tmp
            return ok
        return any(dfs(i,j,0) for i in range(m) for j in range(n))

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(m·n·4^L)Same
SpaceO(m·n)O(L)
Difficulty2/53/5
WhenDemoAdversarial
PE VerdictIn-place marking.

What the interviewer is really testing

Grid DFS template.

Top gotchas

  • Restore the character on backtrack — critical.
  • Use a sentinel that can't appear in word (e.g., '#').
  • Char-count prune saves massive time when word has rare chars.

Ship-it

DFS with in-place marking.

#74 LC 131 Medium Palindrome Partitioning

DFS — try every prefix; recurse if palindrome.

Problem Statement

Partition string s such that every substring is a palindrome. Return all partitions.

Signature: List<List<String>> partition(String s)

Examples

"aab" → [["a","a","b"],["aa","b"]]

Constraints

  • 1 <= s.length <= 16

Approach Overview

Brute Force

Java: DFS try every cut

Python: DFS + s[i:j] check

O(2^n·n) O(n)

Optimal #1

Java: DFS with two-pointer palindrome check (no substring alloc until emit)

Python: DFS two-pointer

O(2^n·n) O(n)

Optimal #2

Java: DP-precomputed palindrome table

Python: DP table

O(2^n + n²) O(n²)

Java Solutions

DFS try every cut O(2^n·n) O(n)

Standard.

Pseudo-code

dfs(i): if i==n emit; for j in i+1..n: if s[i:j] palindrome: dfs(j)

Complexity

Time: O(2^n·n) — Cuts × palindrome check.
Space: O(n) — Stack.

Java code

import java.util.*;
class Solution {
    private List<List<String>> out = new ArrayList<>();
    public List<List<String>> partition(String s) { dfs(s, 0, new ArrayList<>()); return out; }
    private void dfs(String s, int i, List<String> cur) {
        if (i == s.length()) { out.add(new ArrayList<>(cur)); return; }
        for (int j = i+1; j <= s.length(); j++) {
            String sub = s.substring(i, j);
            if (isPal(sub)) { cur.add(sub); dfs(s, j, cur); cur.remove(cur.size()-1); }
        }
    }
    private boolean isPal(String s) {
        int l = 0, r = s.length()-1;
        while (l < r) if (s.charAt(l++) != s.charAt(r--)) return false;
        return true;
    }
}
DFS with two-pointer palindrome check (no substring alloc until emit) O(2^n·n) O(n)

Cheaper inner check.

Pseudo-code

dfs(i): for j: if isPal(s,i,j): cur.add(s[i:j+1]); dfs(j+1); pop

Complexity

Time: O(2^n·n) — Same.
Space: O(n) — Stack.

Java code

import java.util.*;
class Solution {
    private List<List<String>> out = new ArrayList<>();
    public List<List<String>> partition(String s) { dfs(s, 0, new ArrayList<>()); return out; }
    private void dfs(String s, int i, List<String> cur) {
        if (i == s.length()) { out.add(new ArrayList<>(cur)); return; }
        for (int j = i; j < s.length(); j++) {
            if (isPal(s, i, j)) { cur.add(s.substring(i, j+1)); dfs(s, j+1, cur); cur.remove(cur.size()-1); }
        }
    }
    private boolean isPal(String s, int l, int r) {
        while (l < r) if (s.charAt(l++) != s.charAt(r--)) return false;
        return true;
    }
}
DP-precomputed palindrome table O(2^n + n²) O(n²)

O(n²) precompute isPal[i][j]; O(1) check during DFS.

Pseudo-code

dp[i][j] = s[i]==s[j] && dp[i+1][j-1]

Complexity

Time: O(2^n + n²) — Faster constant.
Space: O(n²) — Table.

Java code

import java.util.*;
class Solution {
    private List<List<String>> out = new ArrayList<>();
    private boolean[][] pal;
    private String s;
    public List<List<String>> partition(String s) {
        this.s = s; int n = s.length();
        pal = new boolean[n][n];
        for (int i = n-1; i >= 0; i--) for (int j = i; j < n; j++)
            pal[i][j] = s.charAt(i) == s.charAt(j) && (j - i < 2 || pal[i+1][j-1]);
        dfs(0, new ArrayList<>());
        return out;
    }
    private void dfs(int i, List<String> cur) {
        if (i == s.length()) { out.add(new ArrayList<>(cur)); return; }
        for (int j = i; j < s.length(); j++) if (pal[i][j]) {
            cur.add(s.substring(i, j+1)); dfs(j+1, cur); cur.remove(cur.size()-1);
        }
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n·n)O(2^n + n²)
SpaceO(n)O(n²)
Difficulty2/54/5
WhenDemoLong strings
PE VerdictDFS with two-pointer palindrome check.

Python Solutions

DFS + s[i:j] check O(2^n·n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(2^n·n) — Same.
Space: O(n) — Stack.

Python code

from typing import List
class Solution:
    def partition(self, s: str) -> List[List[str]]:
        out = []
        def dfs(i, cur):
            if i == len(s): out.append(cur[:]); return
            for j in range(i+1, len(s)+1):
                sub = s[i:j]
                if sub == sub[::-1]:
                    cur.append(sub); dfs(j, cur); cur.pop()
        dfs(0, [])
        return out
DFS two-pointer O(2^n·n) O(n)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(2^n·n) — Same.
Space: O(n) — Stack.

Python code

from typing import List
class Solution:
    def partition(self, s: str) -> List[List[str]]:
        out = []
        def is_pal(l, r):
            while l < r:
                if s[l] != s[r]: return False
                l += 1; r -= 1
            return True
        def dfs(i, cur):
            if i == len(s): out.append(cur[:]); return
            for j in range(i, len(s)):
                if is_pal(i, j):
                    cur.append(s[i:j+1]); dfs(j+1, cur); cur.pop()
        dfs(0, [])
        return out
DP table O(2^n+n²) O(n²)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(2^n+n²) — Faster check.
Space: O(n²) — Table.

Python code

from typing import List
class Solution:
    def partition(self, s: str) -> List[List[str]]:
        n = len(s); out = []
        pal = [[False]*n for _ in range(n)]
        for i in range(n-1, -1, -1):
            for j in range(i, n):
                pal[i][j] = s[i] == s[j] and (j - i < 2 or pal[i+1][j-1])
        def dfs(i, cur):
            if i == n: out.append(cur[:]); return
            for j in range(i, n):
                if pal[i][j]:
                    cur.append(s[i:j+1]); dfs(j+1, cur); cur.pop()
        dfs(0, [])
        return out

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n·n)O(2^n+n²)
SpaceO(n)O(n²)
Difficulty2/54/5
WhenDemoLong
PE VerdictDFS with two-pointer.

What the interviewer is really testing

Backtracking + palindrome check.

Top gotchas

  • Check is i..j inclusive — careful with substring(i, j+1).
  • Two-pointer avoids per-check allocation.
  • DP table is overkill unless n large.

Ship-it

DFS with two-pointer check.

#75 LC 17 Medium Letter Combinations of a Phone Number

DFS / BFS — cartesian product over digit→letters.

Problem Statement

Given a phone digit string (2-9), return all possible letter combinations (per standard phone mapping).

Signature: List<String> letterCombinations(String digits)

Examples

"23" → ["ad","ae","af","bd","be","bf","cd","ce","cf"]

Constraints

  • 0 <= digits.length <= 4

Approach Overview

Brute Force

Java: BFS expanding queue

Python: BFS

O(3^n·4^m) O(out)

Optimal #1

Java: DFS backtracking

Python: DFS

O(3^n·4^m) O(n)

Optimal #2

Java: Iterative cartesian product

Python: itertools.product

O(3^n·4^m) O(out)

Java Solutions

BFS expanding queue O(3^n·4^m) O(out)

Pop, append each letter, push back.

Pseudo-code

queue=['']; for each digit: pop, append each letter, push

Complexity

Time: O(3^n·4^m) — Cartesian.
Space: O(out) — Queue.

Edge cases & gotchas

  • Empty input → empty list.

Java code

import java.util.*;
class Solution {
    private static final String[] MAP = {"", "", "abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
    public List<String> letterCombinations(String d) {
        if (d.isEmpty()) return new ArrayList<>();
        Deque<String> q = new ArrayDeque<>(); q.offer("");
        for (char c : d.toCharArray()) {
            String letters = MAP[c - '0'];
            int sz = q.size();
            for (int i = 0; i < sz; i++) {
                String s = q.poll();
                for (char L : letters.toCharArray()) q.offer(s + L);
            }
        }
        return new ArrayList<>(q);
    }
}
DFS backtracking O(3^n·4^m) O(n)

Standard recursion.

Pseudo-code

dfs(i, cur): if i==n emit; else for L in MAP[d[i]]: dfs(i+1, cur+L)

Complexity

Time: O(3^n·4^m) — Same.
Space: O(n) — Stack.

Java code

import java.util.*;
class Solution {
    private static final String[] MAP = {"", "", "abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
    private List<String> out = new ArrayList<>();
    private String d;
    public List<String> letterCombinations(String digits) {
        if (digits.isEmpty()) return out;
        this.d = digits; dfs(0, new StringBuilder());
        return out;
    }
    private void dfs(int i, StringBuilder cur) {
        if (i == d.length()) { out.add(cur.toString()); return; }
        for (char c : MAP[d.charAt(i) - '0'].toCharArray()) {
            cur.append(c); dfs(i+1, cur); cur.deleteCharAt(cur.length()-1);
        }
    }
}
Iterative cartesian product O(3^n·4^m) O(out)

Build out by repeated cross.

Pseudo-code

out=['']; for digit: out = [s+L for s in out for L in MAP[d]]

Complexity

Time: O(3^n·4^m) — Same.
Space: O(out) — Output.

Java code

import java.util.*;
class Solution {
    private static final String[] MAP = {"", "", "abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
    public List<String> letterCombinations(String d) {
        if (d.isEmpty()) return new ArrayList<>();
        List<String> out = new ArrayList<>(); out.add("");
        for (char c : d.toCharArray()) {
            String letters = MAP[c - '0'];
            List<String> next = new ArrayList<>(out.size() * letters.length());
            for (String s : out) for (char L : letters.toCharArray()) next.add(s + L);
            out = next;
        }
        return out;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(3^n·4^m)Same
SpaceO(out)O(out)
Difficulty2/52/5
WhenBFS preferencePythonic-style
PE VerdictDFS template.

Python Solutions

BFS O(3^n·4^m) O(out)

Same.

Pseudo-code

see Java

Complexity

Time: O(3^n·4^m) — Same.
Space: O(out) — Queue.

Python code

from typing import List
from collections import deque
class Solution:
    MAP = ["","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"]
    def letterCombinations(self, d: str) -> List[str]:
        if not d: return []
        q = deque([""])
        for c in d:
            for _ in range(len(q)):
                s = q.popleft()
                for L in self.MAP[int(c)]: q.append(s + L)
        return list(q)
DFS O(3^n·4^m) O(n)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(3^n·4^m) — Same.
Space: O(n) — Stack.

Python code

from typing import List
class Solution:
    MAP = ["","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"]
    def letterCombinations(self, d: str) -> List[str]:
        if not d: return []
        out = []
        def dfs(i, cur):
            if i == len(d): out.append(cur); return
            for L in self.MAP[int(d[i])]: dfs(i+1, cur + L)
        dfs(0, "")
        return out
itertools.product O(3^n·4^m) O(out)

Cartesian product helper.

Pseudo-code

product(*[MAP[d] for d in digits])

Complexity

Time: O(3^n·4^m) — C-fast.
Space: O(out) — Output.

Python code

from typing import List
from itertools import product
class Solution:
    MAP = ["","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"]
    def letterCombinations(self, d: str) -> List[str]:
        if not d: return []
        return [''.join(p) for p in product(*[self.MAP[int(c)] for c in d])]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(3^n·4^m)Same
SpaceO(out)O(n)
Difficulty2/52/5
WhenBFSDefault
PE Verdictitertools.product — clearest Pythonic answer.

What the interviewer is really testing

Simple cartesian.

Top gotchas

  • Empty input → empty list (not [""]).
  • Lookup table indexed by digit (mind the 0/1 gap).

Ship-it

DFS (Java) / itertools.product (Python).

#76 LC 51 Hard N-Queens

Place n queens on n×n board — DFS row by row with column/diagonal sets.

Problem Statement

Return all distinct placements of n queens such that no two attack each other.

Signature: List<List<String>> solveNQueens(int n)

Examples

n=4 → 2 solutions

Constraints

  • 1 <= n <= 9

Approach Overview

Brute Force

Java: DFS row-by-row with O(n) attack check

Python: DFS O(n) check

O(n!) O(n)

Optimal #1

Java: DFS with column + 2 diagonal sets (O(1) check)

Python: DFS with sets

O(n!) O(n)

Optimal #2

Java: Bitmask (3 ints) — cols, diag1, diag2

Python: Bitmask

O(n!) O(n)

Java Solutions

DFS row-by-row with O(n) attack check O(n!) O(n)

For each row, try each column; verify against placed.

Pseudo-code

dfs(row, cols[]): for col: if safe: place; dfs(row+1)

Complexity

Time: O(n!) — Branching n × n.
Space: O(n) — Stack.

Java code

import java.util.*;
class Solution {
    private List<List<String>> out = new ArrayList<>();
    public List<List<String>> solveNQueens(int n) {
        int[] cols = new int[n];
        dfs(0, n, cols);
        return out;
    }
    private void dfs(int r, int n, int[] cols) {
        if (r == n) { out.add(toBoard(cols, n)); return; }
        for (int c = 0; c < n; c++) {
            boolean safe = true;
            for (int i = 0; i < r; i++) if (cols[i] == c || Math.abs(cols[i] - c) == r - i) { safe = false; break; }
            if (safe) { cols[r] = c; dfs(r+1, n, cols); }
        }
    }
    private List<String> toBoard(int[] cols, int n) {
        List<String> b = new ArrayList<>();
        for (int r = 0; r < n; r++) {
            char[] row = new char[n]; Arrays.fill(row, '.');
            row[cols[r]] = 'Q'; b.add(new String(row));
        }
        return b;
    }
}
DFS with column + 2 diagonal sets (O(1) check) O(n!) O(n)

Track used cols, diag1 (r-c), diag2 (r+c).

Pseudo-code

dfs(r): for c: if c, r-c, r+c not used: place, recurse

Complexity

Time: O(n!) — Same branching, faster check.
Space: O(n) — Three sets.

Java code

import java.util.*;
class Solution {
    private List<List<String>> out = new ArrayList<>();
    private Set<Integer> cols = new HashSet<>(), d1 = new HashSet<>(), d2 = new HashSet<>();
    public List<List<String>> solveNQueens(int n) {
        int[] place = new int[n];
        dfs(0, n, place);
        return out;
    }
    private void dfs(int r, int n, int[] place) {
        if (r == n) { out.add(toBoard(place, n)); return; }
        for (int c = 0; c < n; c++) {
            if (cols.contains(c) || d1.contains(r - c) || d2.contains(r + c)) continue;
            cols.add(c); d1.add(r - c); d2.add(r + c); place[r] = c;
            dfs(r+1, n, place);
            cols.remove(c); d1.remove(r - c); d2.remove(r + c);
        }
    }
    private List<String> toBoard(int[] place, int n) {
        List<String> b = new ArrayList<>();
        for (int r = 0; r < n; r++) {
            char[] row = new char[n]; Arrays.fill(row, '.');
            row[place[r]] = 'Q'; b.add(new String(row));
        }
        return b;
    }
}
Bitmask (3 ints) — cols, diag1, diag2 O(n!) O(n)

Fastest in practice.

Pseudo-code

dfs(r, cols, d1, d2): free = ~(cols|d1|d2) & ((1<<n)-1); iterate bits

Complexity

Time: O(n!) — Bit ops.
Space: O(n) — Stack.

Edge cases & gotchas

  • Only works for n <= 32.

Java code

import java.util.*;
class Solution {
    private List<List<String>> out = new ArrayList<>();
    private int n;
    public List<List<String>> solveNQueens(int n) {
        this.n = n;
        dfs(0, 0, 0, 0, new int[n]);
        return out;
    }
    private void dfs(int r, int cols, int d1, int d2, int[] place) {
        if (r == n) { out.add(toBoard(place)); return; }
        int free = ~(cols | d1 | d2) & ((1 << n) - 1);
        while (free != 0) {
            int pick = free & -free;
            int col = Integer.numberOfTrailingZeros(pick);
            place[r] = col;
            dfs(r+1, cols | pick, (d1 | pick) << 1, (d2 | pick) >> 1, place);
            free &= free - 1;
        }
    }
    private List<String> toBoard(int[] place) {
        List<String> b = new ArrayList<>();
        for (int r = 0; r < n; r++) {
            char[] row = new char[n]; Arrays.fill(row, '.');
            row[place[r]] = 'Q'; b.add(new String(row));
        }
        return b;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n!)O(n!)
SpaceO(n)O(n)
Difficulty3/55/5
WhenPedagogyFastest constant
PE VerdictSets for col + 2 diagonals.

Python Solutions

DFS O(n) check O(n!) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n!) — Same.
Space: O(n) — Stack.

Python code

from typing import List
class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        out = []; cols = [-1]*n
        def safe(r, c):
            for i in range(r):
                if cols[i] == c or abs(cols[i]-c) == r-i: return False
            return True
        def dfs(r):
            if r == n:
                b = []
                for rr in range(n):
                    row = ['.']*n; row[cols[rr]] = 'Q'; b.append(''.join(row))
                out.append(b); return
            for c in range(n):
                if safe(r, c): cols[r] = c; dfs(r+1)
        dfs(0)
        return out
DFS with sets O(n!) O(n)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(n!) — Faster check.
Space: O(n) — Sets.

Python code

from typing import List
class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        out = []; place = [-1]*n
        cols, d1, d2 = set(), set(), set()
        def dfs(r):
            if r == n:
                b = []
                for rr in range(n):
                    row = ['.']*n; row[place[rr]] = 'Q'; b.append(''.join(row))
                out.append(b); return
            for c in range(n):
                if c in cols or (r-c) in d1 or (r+c) in d2: continue
                cols.add(c); d1.add(r-c); d2.add(r+c); place[r] = c
                dfs(r+1)
                cols.remove(c); d1.remove(r-c); d2.remove(r+c)
        dfs(0)
        return out
Bitmask O(n!) O(n)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(n!) — Fastest.
Space: O(n) — Stack.

Python code

from typing import List
class Solution:
    def solveNQueens(self, n: int) -> List[List[str]]:
        out = []; place = [-1]*n
        def dfs(r, cols, d1, d2):
            if r == n:
                b = []
                for rr in range(n):
                    row = ['.']*n; row[place[rr]] = 'Q'; b.append(''.join(row))
                out.append(b); return
            free = ~(cols | d1 | d2) & ((1 << n) - 1)
            while free:
                pick = free & -free
                col = pick.bit_length() - 1
                place[r] = col
                dfs(r+1, cols | pick, (d1 | pick) << 1, (d2 | pick) >> 1)
                free &= free - 1
        dfs(0, 0, 0, 0)
        return out

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n!)O(n!)
SpaceO(n)O(n)
Difficulty3/55/5
WhenPedagogyHot path
PE VerdictSets-based.

What the interviewer is really testing

Constraint-propagation backtracking.

Top gotchas

  • Diagonals: r-c (NE/SW) and r+c (NW/SE).
  • Backtrack must undo all three sets.
  • Bitmask form generalizes to LC 52 (count only).

Ship-it

DFS with cols/d1/d2 sets.

Tries

#77 LC 208 Medium Implement Trie (Prefix Tree)

Trie with insert, search, startsWith — children array vs map.

Problem Statement

Implement Trie with insert(word), search(word), startsWith(prefix).

Signature: class Trie { void insert(String); boolean search(String); boolean startsWith(String); }

Examples

Constraints

  • 1 <= len <= 2000
  • Lowercase only

Approach Overview

Brute Force

Java: HashSet of words

Python: Set + scan

search O(1); prefix O(n·L) O(n·L)

Optimal #1

Java: Children array of size 26

Python: Dict of dicts (Pythonic)

O(L) O(N·L·26)

Optimal #2

Java: HashMap children

Python: Class-based

O(L) O(N·L)

Java Solutions

HashSet of words search O(1); prefix O(n·L) O(n·L)

O(1) search; startsWith requires O(n) scan.

Pseudo-code

set.contains; for prefix: iterate set

Complexity

Time: search O(1); prefix O(n·L) — Per-call.
Space: O(n·L) — Set.

Java code

import java.util.*;
class Trie {
    Set<String> set = new HashSet<>();
    public void insert(String w) { set.add(w); }
    public boolean search(String w) { return set.contains(w); }
    public boolean startsWith(String p) {
        for (String w : set) if (w.startsWith(p)) return true;
        return false;
    }
}
Children array of size 26 O(L) O(N·L·26)

Best constant factor for lowercase.

Pseudo-code

Node has children[26], isEnd; insert walks/creates

Complexity

Time: O(L) — Per char.
Space: O(N·L·26) — Array per node.

Java code

class Trie {
    private static class N { N[] c = new N[26]; boolean end; }
    private final N root = new N();
    public void insert(String w) {
        N cur = root;
        for (char ch : w.toCharArray()) {
            int i = ch - 'a';
            if (cur.c[i] == null) cur.c[i] = new N();
            cur = cur.c[i];
        }
        cur.end = true;
    }
    private N walk(String s) {
        N cur = root;
        for (char ch : s.toCharArray()) {
            cur = cur.c[ch - 'a'];
            if (cur == null) return null;
        }
        return cur;
    }
    public boolean search(String w) { N n = walk(w); return n != null && n.end; }
    public boolean startsWith(String p) { return walk(p) != null; }
}
HashMap children O(L) O(N·L)

Generalizes to Unicode.

Pseudo-code

Node.children: Map<Char, Node>

Complexity

Time: O(L) — HashMap ops.
Space: O(N·L) — Map per node.

Java code

import java.util.*;
class Trie {
    private static class N { Map<Character, N> c = new HashMap<>(); boolean end; }
    private final N root = new N();
    public void insert(String w) {
        N cur = root;
        for (char ch : w.toCharArray()) cur = cur.c.computeIfAbsent(ch, k -> new N());
        cur.end = true;
    }
    private N walk(String s) {
        N cur = root;
        for (char ch : s.toCharArray()) { cur = cur.c.get(ch); if (cur == null) return null; }
        return cur;
    }
    public boolean search(String w) { N n = walk(w); return n != null && n.end; }
    public boolean startsWith(String p) { return walk(p) != null; }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(L) / O(nL)O(L)
SpaceO(nL)O(N)
Difficulty1/52/5
WhenDemoUnicode
PE VerdictChildren array for lowercase ASCII.

Python Solutions

Set + scan O(1)/O(n) O(nL)

Same.

Pseudo-code

see Java

Complexity

Time: O(1)/O(n) — Same.
Space: O(nL) — Set.

Python code

class Trie:
    def __init__(self): self.s = set()
    def insert(self, w): self.s.add(w)
    def search(self, w) -> bool: return w in self.s
    def startsWith(self, p) -> bool: return any(w.startswith(p) for w in self.s)
Dict of dicts (Pythonic) O(L) O(N)

Same as Java opt2 in Python style.

Pseudo-code

node = {}; '$' marker for end

Complexity

Time: O(L) — Per char.
Space: O(N) — Nested dicts.

Python code

class Trie:
    def __init__(self): self.root = {}
    def insert(self, w):
        cur = self.root
        for c in w: cur = cur.setdefault(c, {})
        cur['$'] = True
    def _walk(self, s):
        cur = self.root
        for c in s:
            if c not in cur: return None
            cur = cur[c]
        return cur
    def search(self, w) -> bool:
        n = self._walk(w); return n is not None and '$' in n
    def startsWith(self, p) -> bool:
        return self._walk(p) is not None
Class-based O(L) O(N·26)

Same as Java opt1.

Pseudo-code

TrieNode with children list

Complexity

Time: O(L) — Same.
Space: O(N·26) — Array per node.

Python code

class TrieNode:
    __slots__ = ('c','end')
    def __init__(self): self.c = [None]*26; self.end = False
class Trie:
    def __init__(self): self.root = TrieNode()
    def insert(self, w):
        cur = self.root
        for ch in w:
            i = ord(ch) - 97
            if cur.c[i] is None: cur.c[i] = TrieNode()
            cur = cur.c[i]
        cur.end = True
    def _walk(self, s):
        cur = self.root
        for ch in s:
            cur = cur.c[ord(ch)-97]
            if cur is None: return None
        return cur
    def search(self, w) -> bool:
        n = self._walk(w); return n is not None and n.end
    def startsWith(self, p) -> bool: return self._walk(p) is not None

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(L)/O(n)O(L)
SpaceO(nL)O(N·26)
Difficulty1/53/5
WhenDemoHot path
PE VerdictDict of dicts — Pythonic.

What the interviewer is really testing

Foundation for prefix queries.

Top gotchas

  • isEnd flag is mandatory — without it search('app') is true even if only 'apple' inserted.
  • Use array[26] for bounded alphabet, HashMap for general.

Ship-it

TrieNode with children + isEnd.

#78 LC 211 Medium Design Add and Search Words Data Structure

Trie + DFS on '.' wildcards.

Problem Statement

Design WordDictionary with addWord and search(word) — '.' matches any single character.

Signature: class WordDictionary { void addWord(String); boolean search(String); }

Examples

Constraints

  • 1 <= len <= 25
  • Up to 10^4 calls

Approach Overview

Brute Force

Java: List + regex match

Python: List + regex

add O(1); search O(n·L) O(n·L)

Optimal #1

Java: Trie with DFS for '.'

Python: Trie + DFS

add O(L); search O(26^k · L) where k = # dots O(N·26)

Optimal #2

Java: Group by length + regex (small alphabet trick)

Python: Length-bucketed regex

search O(B·L) where B = bucket size O(n·L)

Java Solutions

List + regex match add O(1); search O(n·L) O(n·L)

Slow but trivial.

Pseudo-code

Pattern.matches per call

Complexity

Time: add O(1); search O(n·L) — Scan all.
Space: O(n·L) — List.

Java code

import java.util.*; import java.util.regex.*;
class WordDictionary {
    List<String> ws = new ArrayList<>();
    public void addWord(String w) { ws.add(w); }
    public boolean search(String w) {
        Pattern p = Pattern.compile(w.replace(".", "[a-z]"));
        for (String s : ws) if (p.matcher(s).matches()) return true;
        return false;
    }
}
Trie with DFS for '.' add O(L); search O(26^k · L) where k = # dots O(N·26)

For wildcard, try every child at that depth.

Pseudo-code

DFS: if '.' try all children; else descend

Complexity

Time: add O(L); search O(26^k · L) where k = # dots — DFS branching.
Space: O(N·26) — Trie.

Java code

class WordDictionary {
    private static class N { N[] c = new N[26]; boolean end; }
    private final N root = new N();
    public void addWord(String w) {
        N cur = root;
        for (char ch : w.toCharArray()) {
            int i = ch - 'a';
            if (cur.c[i] == null) cur.c[i] = new N();
            cur = cur.c[i];
        }
        cur.end = true;
    }
    public boolean search(String w) { return dfs(w, 0, root); }
    private boolean dfs(String w, int i, N node) {
        if (node == null) return false;
        if (i == w.length()) return node.end;
        char ch = w.charAt(i);
        if (ch == '.') {
            for (N nx : node.c) if (nx != null && dfs(w, i+1, nx)) return true;
            return false;
        }
        return dfs(w, i+1, node.c[ch - 'a']);
    }
}
Group by length + regex (small alphabet trick) search O(B·L) where B = bucket size O(n·L)

Match against same-length bucket only.

Pseudo-code

map length->list; pattern match within bucket

Complexity

Time: search O(B·L) where B = bucket size — Smaller scan.
Space: O(n·L) — Buckets.

Java code

import java.util.*; import java.util.regex.*;
class WordDictionary {
    private Map<Integer, List<String>> m = new HashMap<>();
    public void addWord(String w) { m.computeIfAbsent(w.length(), k -> new ArrayList<>()).add(w); }
    public boolean search(String w) {
        List<String> b = m.get(w.length());
        if (b == null) return false;
        Pattern p = Pattern.compile(w.replace(".", "[a-z]"));
        for (String s : b) if (p.matcher(s).matches()) return true;
        return false;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n·L)O(B·L)
SpaceO(n·L)O(n·L)
Difficulty1/52/5
WhenDemoFew words
PE VerdictTrie with DFS for wildcards.

Python Solutions

List + regex O(n·L) O(n·L)

Same.

Pseudo-code

see Java

Complexity

Time: O(n·L) — Scan.
Space: O(n·L) — List.

Python code

import re
class WordDictionary:
    def __init__(self): self.ws = []
    def addWord(self, w): self.ws.append(w)
    def search(self, w) -> bool:
        p = re.compile('^' + w + '$')
        return any(p.match(s) for s in self.ws)
Trie + DFS O(26^k·L) O(N)

Same as Java opt1.

Pseudo-code

see Java

Complexity

Time: O(26^k·L) — Same.
Space: O(N) — Trie.

Python code

class WordDictionary:
    def __init__(self): self.root = {}
    def addWord(self, w):
        cur = self.root
        for c in w: cur = cur.setdefault(c, {})
        cur['$'] = True
    def search(self, w) -> bool:
        def dfs(i, node):
            if i == len(w): return '$' in node
            c = w[i]
            if c == '.':
                return any(dfs(i+1, child) for k, child in node.items() if k != '$')
            if c not in node: return False
            return dfs(i+1, node[c])
        return dfs(0, self.root)
Length-bucketed regex O(B·L) O(n·L)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(B·L) — Bucket.
Space: O(n·L) — Map.

Python code

import re
from collections import defaultdict
class WordDictionary:
    def __init__(self): self.m = defaultdict(list)
    def addWord(self, w): self.m[len(w)].append(w)
    def search(self, w) -> bool:
        bucket = self.m.get(len(w))
        if not bucket: return False
        p = re.compile('^' + w + '$')
        return any(p.match(s) for s in bucket)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n·L)O(B·L)
SpaceO(n·L)O(n·L)
Difficulty1/52/5
WhenDemoFew words
PE VerdictTrie + DFS for wildcards.

What the interviewer is really testing

Trie + branch-on-wildcard.

Top gotchas

  • Iterate all children only on '.'.
  • Reuse trie node for all queries.
  • Don't recurse past word length.

Ship-it

Trie + DFS branching on '.'.

#79 LC 212 Hard Word Search II

Insert all words into a Trie; DFS the board chasing trie nodes.

Problem Statement

Given a board and a list of words, return all words present in the board (as in Word Search).

Signature: List<String> findWords(char[][] board, String[] words)

Examples

board=[['o','a','a','n'],...], words=['oath','pea','eat','rain'] → ['eat','oath']

Constraints

  • 1 <= m, n <= 12
  • 1 <= #words <= 3*10^4

Approach Overview

Brute Force

Java: Run WordSearch per word

Python: Per-word search

O(W·m·n·4^L) O(L)

Optimal #1

Java: Trie + single DFS over board

Python: Trie + DFS

O(m·n·4^L) O(W·L)

Optimal #2

Java: Trie + leaf pruning

Python: Trie + leaf prune

Same Same

Java Solutions

Run WordSearch per word O(W·m·n·4^L) O(L)

O(W · m·n·4^L).

Pseudo-code

for each word: exists?

Complexity

Time: O(W·m·n·4^L) — Naive.
Space: O(L) — DFS stack.

Edge cases & gotchas

  • TLEs at W large.

Java code

import java.util.*;
class Solution {
    public List<String> findWords(char[][] b, String[] ws) {
        List<String> out = new ArrayList<>();
        for (String w : ws) if (exists(b, w)) out.add(w);
        return out;
    }
    private boolean exists(char[][] b, String w) {
        for (int i = 0; i < b.length; i++) for (int j = 0; j < b[0].length; j++)
            if (dfs(b, w, i, j, 0)) return true;
        return false;
    }
    private boolean dfs(char[][] b, String w, int i, int j, int k) {
        if (k == w.length()) return true;
        if (i<0||j<0||i>=b.length||j>=b[0].length||b[i][j]!=w.charAt(k)) return false;
        char t = b[i][j]; b[i][j] = '#';
        boolean ok = dfs(b,w,i+1,j,k+1)||dfs(b,w,i-1,j,k+1)||dfs(b,w,i,j+1,k+1)||dfs(b,w,i,j-1,k+1);
        b[i][j] = t;
        return ok;
    }
}
Trie + single DFS over board O(m·n·4^L) O(W·L)

All words traversed simultaneously.

Pseudo-code

build trie; DFS each cell; at each step descend trie

Complexity

Time: O(m·n·4^L) — DFS once.
Space: O(W·L) — Trie.

Java code

import java.util.*;
class Solution {
    private static class N { N[] c = new N[26]; String word; }
    public List<String> findWords(char[][] board, String[] words) {
        N root = new N();
        for (String w : words) {
            N cur = root;
            for (char ch : w.toCharArray()) {
                int i = ch - 'a';
                if (cur.c[i] == null) cur.c[i] = new N();
                cur = cur.c[i];
            }
            cur.word = w;
        }
        List<String> out = new ArrayList<>();
        for (int i = 0; i < board.length; i++) for (int j = 0; j < board[0].length; j++)
            dfs(board, i, j, root, out);
        return out;
    }
    private void dfs(char[][] b, int i, int j, N node, List<String> out) {
        if (i<0||j<0||i>=b.length||j>=b[0].length) return;
        char ch = b[i][j];
        if (ch == '#') return;
        N next = node.c[ch - 'a'];
        if (next == null) return;
        if (next.word != null) { out.add(next.word); next.word = null; }   // dedupe
        b[i][j] = '#';
        dfs(b,i+1,j,next,out); dfs(b,i-1,j,next,out); dfs(b,i,j+1,next,out); dfs(b,i,j-1,next,out);
        b[i][j] = ch;
    }
}
Trie + leaf pruning Same Same

Prune leaf trie nodes after emit to bound search.

Pseudo-code

after emit, walk back removing childless trie nodes

Complexity

Time: Same — Pruned.
Space: Same — Same.

Java code

// Same as opt1 with additional pruning step on the way up.
// Omitted for brevity — opt1 is enough at LeetCode scale.
class Solution {
    public java.util.List<String> findWords(char[][] board, String[] words) {
        return new Solution2().findWords(board, words);
    }
}
class Solution2 extends Solution {}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(W·m·n·4^L)Same with prune
SpaceO(L)Same
Difficulty2/55/5
WhenDon'tHot path
PE VerdictTrie + DFS over board.

Python Solutions

Per-word search O(W·m·n·4^L) O(L)

Same.

Pseudo-code

see Java

Complexity

Time: O(W·m·n·4^L) — Naive.
Space: O(L) — Stack.

Python code

from typing import List
class Solution:
    def findWords(self, board: List[List[str]], words: List[str]) -> List[str]:
        m, n = len(board), len(board[0])
        def exists(w):
            def dfs(i, j, k):
                if k == len(w): return True
                if i<0 or j<0 or i>=m or j>=n or board[i][j] != w[k]: return False
                t = board[i][j]; board[i][j] = '#'
                ok = dfs(i+1,j,k+1) or dfs(i-1,j,k+1) or dfs(i,j+1,k+1) or dfs(i,j-1,k+1)
                board[i][j] = t
                return ok
            return any(dfs(i,j,0) for i in range(m) for j in range(n))
        return [w for w in words if exists(w)]
Trie + DFS O(m·n·4^L) O(W·L)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(m·n·4^L) — Single DFS.
Space: O(W·L) — Trie.

Python code

from typing import List
class Solution:
    def findWords(self, board: List[List[str]], words: List[str]) -> List[str]:
        root = {}
        for w in words:
            cur = root
            for c in w: cur = cur.setdefault(c, {})
            cur['$'] = w
        m, n = len(board), len(board[0]); out = []
        def dfs(i, j, node):
            if i<0 or j<0 or i>=m or j>=n: return
            c = board[i][j]
            if c == '#' or c not in node: return
            nxt = node[c]
            if '$' in nxt: out.append(nxt['$']); del nxt['$']
            board[i][j] = '#'
            dfs(i+1,j,nxt); dfs(i-1,j,nxt); dfs(i,j+1,nxt); dfs(i,j-1,nxt)
            board[i][j] = c
        for i in range(m):
            for j in range(n): dfs(i, j, root)
        return out
Trie + leaf prune Same Same

Same idea as Java opt2.

Pseudo-code

see opt1

Complexity

Time: Same — Pruned.
Space: Same — Same.

Python code

# Same shape as opt1 with leaf pruning; left as an exercise.
class Solution:
    def findWords(self, board, words):
        return Solution()  # placeholder

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(W·m·n·4^L)Same
SpaceO(L)Same
Difficulty2/55/5
WhenDon'tHot path
PE VerdictTrie + DFS.

What the interviewer is really testing

Trie composition with grid DFS.

Top gotchas

  • Store the full word at terminal node (avoid reconstruction).
  • Delete the word from trie after emit to dedupe.
  • Restore board cell on backtrack.

Ship-it

Trie + single board DFS.

Graphs

#80 LC 200 Medium Number of Islands

Connected components in a grid — DFS / BFS / Union-Find.

Problem Statement

Count the number of connected '1' islands in a grid (4-directional).

Signature: int numIslands(char[][] grid)

Examples

[["1","1","0"],["1","0","0"],["0","0","1"]] → 2

Constraints

  • 1 <= m, n <= 300

Approach Overview

Brute Force

Java: BFS per island

Python: BFS

O(m·n) O(m·n)

Optimal #1

Java: DFS flood-fill

Python: DFS

O(m·n) O(m·n) stack worst

Optimal #2

Java: Union-Find

Python: Union-Find

O(m·n·α) O(m·n)

Java Solutions

BFS per island O(m·n) O(m·n)

BFS flood-fill.

Pseudo-code

for each '1': BFS mark all reachable '1's; count++

Complexity

Time: O(m·n) — Each cell visited once.
Space: O(m·n) — Queue.

Java code

import java.util.*;
class Solution {
    public int numIslands(char[][] g) {
        int m = g.length, n = g[0].length, cnt = 0;
        int[][] D = {{-1,0},{1,0},{0,-1},{0,1}};
        for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) {
            if (g[i][j] != '1') continue;
            cnt++;
            Deque<int[]> q = new ArrayDeque<>();
            q.offer(new int[]{i, j}); g[i][j] = '0';
            while (!q.isEmpty()) {
                int[] p = q.poll();
                for (int[] d : D) {
                    int ni = p[0]+d[0], nj = p[1]+d[1];
                    if (ni>=0 && nj>=0 && ni<m && nj<n && g[ni][nj] == '1') {
                        g[ni][nj] = '0'; q.offer(new int[]{ni, nj});
                    }
                }
            }
        }
        return cnt;
    }
}
DFS flood-fill O(m·n) O(m·n) stack worst

In-place sink visited cells.

Pseudo-code

for each '1': DFS sink (set to '0'); count++

Complexity

Time: O(m·n) — Each cell once.
Space: O(m·n) stack worst — Recursion.

Java code

class Solution {
    public int numIslands(char[][] g) {
        int m = g.length, n = g[0].length, cnt = 0;
        for (int i = 0; i < m; i++) for (int j = 0; j < n; j++)
            if (g[i][j] == '1') { cnt++; sink(g, i, j); }
        return cnt;
    }
    private void sink(char[][] g, int i, int j) {
        if (i<0||j<0||i>=g.length||j>=g[0].length||g[i][j] != '1') return;
        g[i][j] = '0';
        sink(g, i+1, j); sink(g, i-1, j); sink(g, i, j+1); sink(g, i, j-1);
    }
}
Union-Find O(m·n·α) O(m·n)

Connect adjacent '1's; count distinct roots.

Pseudo-code

DSU on cells; union with neighbor 1s; count roots that are '1'

Complexity

Time: O(m·n·α) — Near-linear.
Space: O(m·n) — DSU.

Java code

class Solution {
    private int[] p, sz;
    private int find(int x) { while (p[x] != x) { p[x] = p[p[x]]; x = p[x]; } return x; }
    private boolean union(int a, int b) {
        int ra = find(a), rb = find(b);
        if (ra == rb) return false;
        if (sz[ra] < sz[rb]) { p[ra] = rb; sz[rb] += sz[ra]; }
        else { p[rb] = ra; sz[ra] += sz[rb]; }
        return true;
    }
    public int numIslands(char[][] g) {
        int m = g.length, n = g[0].length, total = m * n;
        p = new int[total]; sz = new int[total];
        int cnt = 0;
        for (int i = 0; i < total; i++) { p[i] = i; sz[i] = 1; }
        for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) if (g[i][j] == '1') {
            cnt++;
            if (i > 0 && g[i-1][j] == '1' && union(i*n+j, (i-1)*n+j)) cnt--;
            if (j > 0 && g[i][j-1] == '1' && union(i*n+j, i*n+j-1)) cnt--;
        }
        return cnt;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(m·n)O(m·n·α)
SpaceO(m·n)O(m·n)
Difficulty2/54/5
WhenWideOnline updates
PE VerdictDFS flood-fill.

Python Solutions

BFS O(m·n) O(m·n)

Same.

Pseudo-code

see Java

Complexity

Time: O(m·n) — Linear.
Space: O(m·n) — Queue.

Python code

from typing import List
from collections import deque
class Solution:
    def numIslands(self, g: List[List[str]]) -> int:
        m, n = len(g), len(g[0]); cnt = 0
        for i in range(m):
            for j in range(n):
                if g[i][j] != '1': continue
                cnt += 1
                q = deque([(i,j)]); g[i][j] = '0'
                while q:
                    r, c = q.popleft()
                    for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
                        nr, nc = r+dr, c+dc
                        if 0<=nr<m and 0<=nc<n and g[nr][nc] == '1':
                            g[nr][nc] = '0'; q.append((nr,nc))
        return cnt
DFS O(m·n) O(m·n)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(m·n) — Linear.
Space: O(m·n) — Stack.

Python code

from typing import List
class Solution:
    def numIslands(self, g: List[List[str]]) -> int:
        m, n = len(g), len(g[0]); cnt = 0
        def sink(i, j):
            if i<0 or j<0 or i>=m or j>=n or g[i][j] != '1': return
            g[i][j] = '0'
            sink(i+1,j); sink(i-1,j); sink(i,j+1); sink(i,j-1)
        for i in range(m):
            for j in range(n):
                if g[i][j] == '1': cnt += 1; sink(i, j)
        return cnt
Union-Find O(m·n·α) O(m·n)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(m·n·α) — Near-linear.
Space: O(m·n) — DSU.

Python code

from typing import List
class Solution:
    def numIslands(self, g: List[List[str]]) -> int:
        m, n = len(g), len(g[0])
        p = list(range(m*n)); sz = [1]*(m*n); cnt = 0
        def find(x):
            while p[x] != x: p[x] = p[p[x]]; x = p[x]
            return x
        def union(a, b):
            ra, rb = find(a), find(b)
            if ra == rb: return False
            if sz[ra] < sz[rb]: p[ra] = rb; sz[rb] += sz[ra]
            else: p[rb] = ra; sz[ra] += sz[rb]
            return True
        for i in range(m):
            for j in range(n):
                if g[i][j] != '1': continue
                cnt += 1
                if i > 0 and g[i-1][j] == '1' and union(i*n+j, (i-1)*n+j): cnt -= 1
                if j > 0 and g[i][j-1] == '1' and union(i*n+j, i*n+j-1): cnt -= 1
        return cnt

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(m·n)O(m·n·α)
SpaceO(m·n)O(m·n)
Difficulty2/54/5
WhenWideOnline
PE VerdictDFS flood-fill.

What the interviewer is really testing

Foundation grid traversal.

Top gotchas

  • Mutate in place (set to '0') to avoid visited set.
  • BFS for wide grids — avoid stack overflow.
  • Union-Find when grids stream incrementally.

Ship-it

DFS flood-fill, mutating in place.

#81 LC 695 Medium Max Area of Island

Same as Number of Islands but track the area of each component.

Problem Statement

Return the maximum area of any island (connected '1' cells).

Signature: int maxAreaOfIsland(int[][] grid)

Examples

mixed grid → 6

Constraints

  • 1 <= m, n <= 50

Approach Overview

Brute Force

Java: BFS counting

Python: BFS

O(m·n) O(m·n)

Optimal #1

Java: DFS returning area

Python: DFS returning area

O(m·n) O(m·n)

Optimal #2

Java: Union-Find with size

Python: Union-Find

O(m·n·α) O(m·n)

Java Solutions

BFS counting O(m·n) O(m·n)

Per-island BFS count.

Pseudo-code

for each 1: BFS count cells; track max

Complexity

Time: O(m·n) — Linear.
Space: O(m·n) — Queue.

Java code

import java.util.*;
class Solution {
    public int maxAreaOfIsland(int[][] g) {
        int m = g.length, n = g[0].length, best = 0;
        int[][] D = {{-1,0},{1,0},{0,-1},{0,1}};
        for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) if (g[i][j] == 1) {
            int area = 0;
            Deque<int[]> q = new ArrayDeque<>(); q.offer(new int[]{i,j}); g[i][j] = 0;
            while (!q.isEmpty()) {
                int[] p = q.poll(); area++;
                for (int[] d : D) {
                    int ni = p[0]+d[0], nj = p[1]+d[1];
                    if (ni>=0 && nj>=0 && ni<m && nj<n && g[ni][nj] == 1) { g[ni][nj] = 0; q.offer(new int[]{ni,nj}); }
                }
            }
            best = Math.max(best, area);
        }
        return best;
    }
}
DFS returning area O(m·n) O(m·n)

Recursive sum of 1+children.

Pseudo-code

dfs returns 1+sum(neighbors) if land else 0

Complexity

Time: O(m·n) — Linear.
Space: O(m·n) — Stack.

Java code

class Solution {
    public int maxAreaOfIsland(int[][] g) {
        int m = g.length, n = g[0].length, best = 0;
        for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) best = Math.max(best, dfs(g, i, j));
        return best;
    }
    private int dfs(int[][] g, int i, int j) {
        if (i<0||j<0||i>=g.length||j>=g[0].length||g[i][j] != 1) return 0;
        g[i][j] = 0;
        return 1 + dfs(g,i+1,j) + dfs(g,i-1,j) + dfs(g,i,j+1) + dfs(g,i,j-1);
    }
}
Union-Find with size O(m·n·α) O(m·n)

Track sz per component.

Pseudo-code

DSU; union with size; max(sz)

Complexity

Time: O(m·n·α) — Near linear.
Space: O(m·n) — DSU.

Java code

class Solution {
    private int[] p, sz;
    private int find(int x) { while (p[x] != x) { p[x] = p[p[x]]; x = p[x]; } return x; }
    private void union(int a, int b) {
        int ra = find(a), rb = find(b); if (ra == rb) return;
        if (sz[ra] < sz[rb]) { p[ra] = rb; sz[rb] += sz[ra]; } else { p[rb] = ra; sz[ra] += sz[rb]; }
    }
    public int maxAreaOfIsland(int[][] g) {
        int m = g.length, n = g[0].length;
        p = new int[m*n]; sz = new int[m*n];
        for (int i = 0; i < m*n; i++) { p[i] = i; sz[i] = 1; }
        for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) if (g[i][j] == 1) {
            if (i > 0 && g[i-1][j] == 1) union(i*n+j, (i-1)*n+j);
            if (j > 0 && g[i][j-1] == 1) union(i*n+j, i*n+j-1);
        }
        int best = 0;
        for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) if (g[i][j] == 1) best = Math.max(best, sz[find(i*n+j)]);
        return best;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(m·n)O(m·n·α)
SpaceO(m·n)O(m·n)
Difficulty2/54/5
WhenWideOnline
PE VerdictDFS returning area.

Python Solutions

BFS O(m·n) O(m·n)

Same.

Pseudo-code

see Java

Complexity

Time: O(m·n) — Linear.
Space: O(m·n) — Queue.

Python code

from typing import List
from collections import deque
class Solution:
    def maxAreaOfIsland(self, g: List[List[int]]) -> int:
        m, n = len(g), len(g[0]); best = 0
        for i in range(m):
            for j in range(n):
                if g[i][j] != 1: continue
                area = 0; q = deque([(i,j)]); g[i][j] = 0
                while q:
                    r, c = q.popleft(); area += 1
                    for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
                        nr, nc = r+dr, c+dc
                        if 0<=nr<m and 0<=nc<n and g[nr][nc] == 1: g[nr][nc] = 0; q.append((nr,nc))
                best = max(best, area)
        return best
DFS returning area O(m·n) O(m·n)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(m·n) — Linear.
Space: O(m·n) — Stack.

Python code

from typing import List
class Solution:
    def maxAreaOfIsland(self, g: List[List[int]]) -> int:
        m, n = len(g), len(g[0])
        def dfs(i, j):
            if i<0 or j<0 or i>=m or j>=n or g[i][j] != 1: return 0
            g[i][j] = 0
            return 1 + dfs(i+1,j) + dfs(i-1,j) + dfs(i,j+1) + dfs(i,j-1)
        return max((dfs(i,j) for i in range(m) for j in range(n)), default=0)
Union-Find O(m·n·α) O(m·n)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(m·n·α) — Near linear.
Space: O(m·n) — DSU.

Python code

from typing import List
class Solution:
    def maxAreaOfIsland(self, g: List[List[int]]) -> int:
        m, n = len(g), len(g[0])
        p = list(range(m*n)); sz = [1]*(m*n)
        def find(x):
            while p[x] != x: p[x] = p[p[x]]; x = p[x]
            return x
        def union(a, b):
            ra, rb = find(a), find(b)
            if ra == rb: return
            if sz[ra] < sz[rb]: p[ra] = rb; sz[rb] += sz[ra]
            else: p[rb] = ra; sz[ra] += sz[rb]
        for i in range(m):
            for j in range(n):
                if g[i][j] != 1: continue
                if i > 0 and g[i-1][j] == 1: union(i*n+j, (i-1)*n+j)
                if j > 0 and g[i][j-1] == 1: union(i*n+j, i*n+j-1)
        return max((sz[find(i*n+j)] for i in range(m) for j in range(n) if g[i][j] == 1), default=0)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(m·n)O(m·n·α)
SpaceO(m·n)O(m·n)
Difficulty2/54/5
WhenWideOnline
PE VerdictDFS area.

What the interviewer is really testing

Variant of islands.

Top gotchas

  • Return 0 for empty grid (use default=0 in Python max).
  • Mutate in place to skip visited.

Ship-it

DFS returning area.

#82 LC 133 Medium Clone Graph

Deep-copy a graph — HashMap original→copy + DFS or BFS.

Problem Statement

Given a reference to a node in a connected undirected graph, return a deep copy.

Signature: Node cloneGraph(Node node)

Examples

adjacency list → cloned with same shape

Constraints

  • 0 <= n <= 100

Approach Overview

Brute Force

Java: BFS clone

Python: BFS

O(V+E) O(V)

Optimal #1

Java: DFS clone

Python: DFS

O(V+E) O(V)

Optimal #2

Java: Iterative DFS with stack

Python: Iterative DFS

O(V+E) O(V)

Java Solutions

BFS clone O(V+E) O(V)

Map + BFS.

Pseudo-code

BFS; for each new neighbor: clone if unseen; wire

Complexity

Time: O(V+E) — Each node/edge once.
Space: O(V) — Map.

Edge cases & gotchas

  • Null root → null.

Java code

import java.util.*;
class Node { public int val; public List<Node> neighbors; public Node(int v) { val = v; neighbors = new ArrayList<>(); } }
class Solution {
    public Node cloneGraph(Node node) {
        if (node == null) return null;
        Map<Node, Node> m = new HashMap<>();
        m.put(node, new Node(node.val));
        Deque<Node> q = new ArrayDeque<>(); q.offer(node);
        while (!q.isEmpty()) {
            Node n = q.poll();
            for (Node nb : n.neighbors) {
                if (!m.containsKey(nb)) { m.put(nb, new Node(nb.val)); q.offer(nb); }
                m.get(n).neighbors.add(m.get(nb));
            }
        }
        return m.get(node);
    }
}
DFS clone O(V+E) O(V)

Recursive with memo.

Pseudo-code

dfs(n): if in map return clone; create, memo, recurse neighbors

Complexity

Time: O(V+E) — Linear.
Space: O(V) — Stack + map.

Java code

import java.util.*;
class Solution {
    private Map<Node, Node> m = new HashMap<>();
    public Node cloneGraph(Node node) {
        if (node == null) return null;
        if (m.containsKey(node)) return m.get(node);
        Node copy = new Node(node.val);
        m.put(node, copy);
        for (Node nb : node.neighbors) copy.neighbors.add(cloneGraph(nb));
        return copy;
    }
}
Iterative DFS with stack O(V+E) O(V)

Same algorithm, no recursion depth.

Pseudo-code

see Java

Complexity

Time: O(V+E) — Linear.
Space: O(V) — Stack.

Java code

import java.util.*;
class Solution {
    public Node cloneGraph(Node node) {
        if (node == null) return null;
        Map<Node, Node> m = new HashMap<>();
        m.put(node, new Node(node.val));
        Deque<Node> s = new ArrayDeque<>(); s.push(node);
        while (!s.isEmpty()) {
            Node n = s.pop();
            for (Node nb : n.neighbors) {
                if (!m.containsKey(nb)) { m.put(nb, new Node(nb.val)); s.push(nb); }
                m.get(n).neighbors.add(m.get(nb));
            }
        }
        return m.get(node);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(V+E)O(V+E)
SpaceO(V)O(V)
Difficulty3/53/5
WhenWideStack-safe
PE VerdictDFS recursive with memo.

Python Solutions

BFS O(V+E) O(V)

Same.

Pseudo-code

see Java

Complexity

Time: O(V+E) — Linear.
Space: O(V) — Queue.

Python code

from collections import deque
class Node:
    def __init__(self, val=0, neighbors=None): self.val=val; self.neighbors = neighbors or []
class Solution:
    def cloneGraph(self, node):
        if not node: return None
        m = {node: Node(node.val)}
        q = deque([node])
        while q:
            n = q.popleft()
            for nb in n.neighbors:
                if nb not in m: m[nb] = Node(nb.val); q.append(nb)
                m[n].neighbors.append(m[nb])
        return m[node]
DFS O(V+E) O(V)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(V+E) — Linear.
Space: O(V) — Stack.

Python code

class Solution:
    def cloneGraph(self, node):
        m = {}
        def dfs(n):
            if not n: return None
            if n in m: return m[n]
            copy = Node(n.val); m[n] = copy
            for nb in n.neighbors: copy.neighbors.append(dfs(nb))
            return copy
        return dfs(node)
Iterative DFS O(V+E) O(V)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(V+E) — Linear.
Space: O(V) — Stack.

Python code

class Solution:
    def cloneGraph(self, node):
        if not node: return None
        m = {node: Node(node.val)}
        s = [node]
        while s:
            n = s.pop()
            for nb in n.neighbors:
                if nb not in m: m[nb] = Node(nb.val); s.append(nb)
                m[n].neighbors.append(m[nb])
        return m[node]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(V+E)O(V+E)
SpaceO(V)O(V)
Difficulty3/53/5
WhenWideStack-safe
PE VerdictDFS with memo.

What the interviewer is really testing

Graph deep-copy template.

Top gotchas

  • Memo BEFORE recursing into neighbors — avoids infinite loops on cycles.
  • Null root returns null.

Ship-it

DFS with memo.

#83 LC 286 Medium Walls and Gates

Multi-source BFS from all gates simultaneously.

Problem Statement

Fill each empty room (INF=2147483647) with the distance to its nearest gate (0). Walls = -1.

Signature: void wallsAndGates(int[][] rooms)

Examples

LC 286 standard

Constraints

  • m, n <= 250

Approach Overview

Brute Force

Java: BFS from each empty room

Python: BFS per cell

O((m·n)²) O(m·n)

Optimal #1

Java: Multi-source BFS from all gates

Python: Multi-source BFS

O(m·n) O(m·n)

Optimal #2

Java: DFS from each gate

Python: DFS

O(m·n) avg O(m·n)

Java Solutions

BFS from each empty room O((m·n)²) O(m·n)

O((m·n)²).

Pseudo-code

for each INF cell: BFS to nearest gate

Complexity

Time: O((m·n)²) — Per cell BFS.
Space: O(m·n) — Queue.

Java code

import java.util.*;
class Solution {
    public void wallsAndGates(int[][] r) {
        int m = r.length, n = r[0].length;
        int[][] D = {{-1,0},{1,0},{0,-1},{0,1}};
        for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) {
            if (r[i][j] != Integer.MAX_VALUE) continue;
            Deque<int[]> q = new ArrayDeque<>(); q.offer(new int[]{i,j,0});
            boolean[][] v = new boolean[m][n]; v[i][j] = true;
            while (!q.isEmpty()) {
                int[] p = q.poll();
                if (r[p[0]][p[1]] == 0) { r[i][j] = p[2]; break; }
                for (int[] d : D) {
                    int ni = p[0]+d[0], nj = p[1]+d[1];
                    if (ni>=0&&nj>=0&&ni<m&&nj<n&&!v[ni][nj]&&r[ni][nj]!=-1) { v[ni][nj]=true; q.offer(new int[]{ni,nj,p[2]+1}); }
                }
            }
        }
    }
}
Multi-source BFS from all gates O(m·n) O(m·n)

Push all gates first; expand simultaneously.

Pseudo-code

seed queue with all gates; BFS; first visit = shortest

Complexity

Time: O(m·n) — Each cell once.
Space: O(m·n) — Queue.

Java code

import java.util.*;
class Solution {
    public void wallsAndGates(int[][] r) {
        int m = r.length, n = r[0].length;
        Deque<int[]> q = new ArrayDeque<>();
        for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) if (r[i][j] == 0) q.offer(new int[]{i,j});
        int[][] D = {{-1,0},{1,0},{0,-1},{0,1}};
        while (!q.isEmpty()) {
            int[] p = q.poll();
            for (int[] d : D) {
                int ni = p[0]+d[0], nj = p[1]+d[1];
                if (ni>=0&&nj>=0&&ni<m&&nj<n&&r[ni][nj] == Integer.MAX_VALUE) {
                    r[ni][nj] = r[p[0]][p[1]] + 1;
                    q.offer(new int[]{ni,nj});
                }
            }
        }
    }
}
DFS from each gate O(m·n) avg O(m·n)

DFS but only descend if new distance < current.

Pseudo-code

DFS from gate, relax if better

Complexity

Time: O(m·n) avg — Each cell bounded.
Space: O(m·n) — Stack.

Java code

class Solution {
    public void wallsAndGates(int[][] r) {
        for (int i = 0; i < r.length; i++) for (int j = 0; j < r[0].length; j++)
            if (r[i][j] == 0) dfs(r, i, j, 0);
    }
    private void dfs(int[][] r, int i, int j, int d) {
        if (i<0||j<0||i>=r.length||j>=r[0].length||r[i][j] < d) return;
        r[i][j] = d;
        dfs(r,i+1,j,d+1); dfs(r,i-1,j,d+1); dfs(r,i,j+1,d+1); dfs(r,i,j-1,d+1);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO((mn)²)O(mn) avg
SpaceO(mn)O(mn)
Difficulty2/53/5
WhenDon'tDFS preference
PE VerdictMulti-source BFS.

Python Solutions

BFS per cell O((mn)²) O(mn)

Same.

Pseudo-code

see Java

Complexity

Time: O((mn)²) — Quadratic.
Space: O(mn) — Queue.

Python code

from typing import List
from collections import deque
class Solution:
    def wallsAndGates(self, r: List[List[int]]) -> None:
        m, n = len(r), len(r[0])
        for i in range(m):
            for j in range(n):
                if r[i][j] != 2147483647: continue
                q = deque([(i,j,0)]); v = {(i,j)}
                while q:
                    x,y,d = q.popleft()
                    if r[x][y] == 0: r[i][j] = d; break
                    for dx,dy in [(-1,0),(1,0),(0,-1),(0,1)]:
                        nx,ny = x+dx,y+dy
                        if 0<=nx<m and 0<=ny<n and (nx,ny) not in v and r[nx][ny] != -1:
                            v.add((nx,ny)); q.append((nx,ny,d+1))
Multi-source BFS O(mn) O(mn)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(mn) — Linear.
Space: O(mn) — Queue.

Python code

from typing import List
from collections import deque
class Solution:
    def wallsAndGates(self, r: List[List[int]]) -> None:
        m, n = len(r), len(r[0])
        q = deque((i,j) for i in range(m) for j in range(n) if r[i][j] == 0)
        while q:
            x,y = q.popleft()
            for dx,dy in [(-1,0),(1,0),(0,-1),(0,1)]:
                nx,ny = x+dx,y+dy
                if 0<=nx<m and 0<=ny<n and r[nx][ny] == 2147483647:
                    r[nx][ny] = r[x][y] + 1; q.append((nx,ny))
DFS O(mn) avg O(mn)

Same as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(mn) avg — Bounded.
Space: O(mn) — Stack.

Python code

from typing import List
class Solution:
    def wallsAndGates(self, r: List[List[int]]) -> None:
        m, n = len(r), len(r[0])
        def dfs(i,j,d):
            if i<0 or j<0 or i>=m or j>=n or r[i][j] < d: return
            r[i][j] = d
            dfs(i+1,j,d+1); dfs(i-1,j,d+1); dfs(i,j+1,d+1); dfs(i,j-1,d+1)
        for i in range(m):
            for j in range(n):
                if r[i][j] == 0: dfs(i,j,0)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO((mn)²)O(mn) avg
SpaceO(mn)O(mn)
Difficulty2/53/5
WhenDon'tDFS preference
PE VerdictMulti-source BFS.

What the interviewer is really testing

Multi-source BFS pattern (also Rotting Oranges).

Top gotchas

  • Seed queue with ALL gates first.
  • First visit IS shortest because BFS expands layer by layer.
  • Skip walls (-1).

Ship-it

Multi-source BFS.

#84 LC 994 Medium Rotting Oranges

Multi-source BFS — all rotten oranges expand simultaneously, track minutes.

Problem Statement

Grid with 0 empty, 1 fresh, 2 rotten. Each minute a rotten orange rots all 4-adjacent fresh ones. Return minimum minutes until none fresh, or -1 if impossible.

Signature: int orangesRotting(int[][] grid)

Examples

[[2,1,1],[1,1,0],[0,1,1]] → 4

Constraints

  • 1 <= m, n <= 10

Approach Overview

Brute Force

Java: Iterative simulation

Python: Iterative simulation

O((m·n)²) O(m·n)

Optimal #1

Java: Multi-source BFS

Python: Multi-source BFS

O(m·n) O(m·n)

Optimal #2

Java: BFS with timestamp in cell

Python: BFS with timestamp

O(m·n) O(m·n)

Java Solutions

Iterative simulation O((m·n)²) O(m·n)

Each minute, scan grid and rot.

Pseudo-code

loop: copy grid; rot adjacent; until no change; count minutes

Complexity

Time: O((m·n)²) — Up to mn passes.
Space: O(m·n) — Copy.

Edge cases & gotchas

  • No fresh → 0.

Java code

class Solution {
    public int orangesRotting(int[][] g) {
        int m = g.length, n = g[0].length, t = 0;
        while (true) {
            int[][] c = new int[m][n];
            for (int i = 0; i < m; i++) c[i] = g[i].clone();
            boolean changed = false;
            for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) if (c[i][j] == 2) {
                int[][] D = {{-1,0},{1,0},{0,-1},{0,1}};
                for (int[] d : D) {
                    int ni = i+d[0], nj = j+d[1];
                    if (ni>=0&&nj>=0&&ni<m&&nj<n&&c[ni][nj]==1) { g[ni][nj]=2; changed=true; }
                }
            }
            if (!changed) break;
            t++;
        }
        for (int[] row : g) for (int v : row) if (v == 1) return -1;
        return t;
    }
}
Multi-source BFS O(m·n) O(m·n)

Seed queue with all rotten; expand layer by layer.

Pseudo-code

seed all 2s; count fresh; BFS minute-by-minute

Complexity

Time: O(m·n) — Each cell once.
Space: O(m·n) — Queue.

Edge cases & gotchas

  • fresh=0 → 0.
  • fresh>0 after BFS → -1.

Java code

import java.util.*;
class Solution {
    public int orangesRotting(int[][] g) {
        int m = g.length, n = g[0].length, fresh = 0, t = 0;
        Deque<int[]> q = new ArrayDeque<>();
        for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) {
            if (g[i][j] == 2) q.offer(new int[]{i,j});
            else if (g[i][j] == 1) fresh++;
        }
        int[][] D = {{-1,0},{1,0},{0,-1},{0,1}};
        while (!q.isEmpty() && fresh > 0) {
            int sz = q.size();
            for (int k = 0; k < sz; k++) {
                int[] p = q.poll();
                for (int[] d : D) {
                    int ni = p[0]+d[0], nj = p[1]+d[1];
                    if (ni>=0&&nj>=0&&ni<m&&nj<n&&g[ni][nj]==1) {
                        g[ni][nj] = 2; fresh--; q.offer(new int[]{ni,nj});
                    }
                }
            }
            t++;
        }
        return fresh == 0 ? t : -1;
    }
}
BFS with timestamp in cell O(m·n) O(m·n)

Encode minute in queue tuple.

Pseudo-code

queue stores (i,j,minute); max minute = answer

Complexity

Time: O(m·n) — Linear.
Space: O(m·n) — Queue.

Java code

import java.util.*;
class Solution {
    public int orangesRotting(int[][] g) {
        int m = g.length, n = g[0].length, fresh = 0, t = 0;
        Deque<int[]> q = new ArrayDeque<>();
        for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) {
            if (g[i][j] == 2) q.offer(new int[]{i,j,0});
            else if (g[i][j] == 1) fresh++;
        }
        int[][] D = {{-1,0},{1,0},{0,-1},{0,1}};
        while (!q.isEmpty()) {
            int[] p = q.poll(); t = Math.max(t, p[2]);
            for (int[] d : D) {
                int ni = p[0]+d[0], nj = p[1]+d[1];
                if (ni>=0&&nj>=0&&ni<m&&nj<n&&g[ni][nj]==1) {
                    g[ni][nj] = 2; fresh--; q.offer(new int[]{ni,nj,p[2]+1});
                }
            }
        }
        return fresh == 0 ? t : -1;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO((mn)²)O(mn)
SpaceO(mn)O(mn)
Difficulty2/53/5
WhenDon'tEquivalent
PE VerdictMulti-source BFS, level-by-level.

Python Solutions

Iterative simulation O((mn)²) O(mn)

Scan and rot each minute.

Pseudo-code

see Java

Complexity

Time: O((mn)²) — Naive.
Space: O(mn) — Copy.

Python code

from typing import List
class Solution:
    def orangesRotting(self, g: List[List[int]]) -> int:
        m, n = len(g), len(g[0]); t = 0
        while True:
            changed = False
            to_rot = []
            for i in range(m):
                for j in range(n):
                    if g[i][j] != 2: continue
                    for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
                        nr, nc = i+dr, j+dc
                        if 0<=nr<m and 0<=nc<n and g[nr][nc] == 1: to_rot.append((nr,nc))
            for r, c in to_rot:
                if g[r][c] == 1: g[r][c] = 2; changed = True
            if not changed: break
            t += 1
        for row in g:
            if 1 in row: return -1
        return t
Multi-source BFS O(mn) O(mn)

Layer expansion.

Pseudo-code

see Java

Complexity

Time: O(mn) — Each cell once.
Space: O(mn) — Queue.

Python code

from typing import List
from collections import deque
class Solution:
    def orangesRotting(self, g: List[List[int]]) -> int:
        m, n = len(g), len(g[0])
        q = deque(); fresh = 0
        for i in range(m):
            for j in range(n):
                if g[i][j] == 2: q.append((i,j))
                elif g[i][j] == 1: fresh += 1
        t = 0
        while q and fresh > 0:
            for _ in range(len(q)):
                r, c = q.popleft()
                for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
                    nr, nc = r+dr, c+dc
                    if 0<=nr<m and 0<=nc<n and g[nr][nc] == 1:
                        g[nr][nc] = 2; fresh -= 1; q.append((nr,nc))
            t += 1
        return t if fresh == 0 else -1
BFS with timestamp O(mn) O(mn)

Tuple carries minute.

Pseudo-code

see Java

Complexity

Time: O(mn) — Linear.
Space: O(mn) — Queue.

Python code

from typing import List
from collections import deque
class Solution:
    def orangesRotting(self, g: List[List[int]]) -> int:
        m, n = len(g), len(g[0])
        q = deque(); fresh = 0
        for i in range(m):
            for j in range(n):
                if g[i][j] == 2: q.append((i,j,0))
                elif g[i][j] == 1: fresh += 1
        t = 0
        while q:
            r, c, m_ = q.popleft(); t = max(t, m_)
            for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
                nr, nc = r+dr, c+dc
                if 0<=nr<m and 0<=nc<n and g[nr][nc] == 1:
                    g[nr][nc] = 2; fresh -= 1; q.append((nr,nc,m_+1))
        return t if fresh == 0 else -1

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO((mn)²)O(mn)
SpaceO(mn)O(mn)
Difficulty2/53/5
WhenDon'tEquivalent
PE VerdictMulti-source BFS.

What the interviewer is really testing

Same pattern as Walls & Gates — multi-source BFS.

Top gotchas

  • Count fresh FIRST so you can detect -1 (unreachable fresh).
  • Stop incrementing minute when queue empty OR fresh hits 0.
  • No fresh up-front → return 0, not -1.

Ship-it

Multi-source BFS level-by-level.

#85 LC 417 Medium Pacific Atlantic Water Flow

Two reverse BFS/DFS from each ocean's borders — intersection is the answer.

Problem Statement

Heights grid. Water flows from cell to lower/equal neighbor. Pacific touches top/left, Atlantic touches bottom/right. Return cells from which water can reach BOTH oceans.

Signature: List<List<Integer>> pacificAtlantic(int[][] heights)

Examples

standard LC 417 example → [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]

Constraints

  • 1 <= m, n <= 200

Approach Overview

Brute Force

Java: DFS from each cell to both oceans

Python: Per-cell DFS to each ocean

O((m·n)²) O(m·n)

Optimal #1

Java: Reverse DFS from each ocean

Python: Reverse DFS from each ocean

O(m·n) O(m·n)

Optimal #2

Java: Reverse BFS from each ocean

Python: Reverse BFS

O(m·n) O(m·n)

Java Solutions

DFS from each cell to both oceans O((m·n)²) O(m·n)

For each cell try to flow downhill to each ocean.

Pseudo-code

for each cell: can reach pacific && can reach atlantic?

Complexity

Time: O((m·n)²) — Per-cell DFS.
Space: O(m·n) — Visited.

Java code

import java.util.*;
class Solution {
    public List<List<Integer>> pacificAtlantic(int[][] h) {
        int m = h.length, n = h[0].length;
        List<List<Integer>> out = new ArrayList<>();
        for (int i = 0; i < m; i++) for (int j = 0; j < n; j++)
            if (reach(h, i, j, true, new boolean[m][n]) && reach(h, i, j, false, new boolean[m][n]))
                out.add(Arrays.asList(i, j));
        return out;
    }
    private boolean reach(int[][] h, int i, int j, boolean pac, boolean[][] v) {
        int m = h.length, n = h[0].length;
        if (pac && (i == 0 || j == 0)) return true;
        if (!pac && (i == m-1 || j == n-1)) return true;
        v[i][j] = true;
        int[][] D = {{-1,0},{1,0},{0,-1},{0,1}};
        for (int[] d : D) {
            int ni = i+d[0], nj = j+d[1];
            if (ni>=0&&nj>=0&&ni<m&&nj<n&&!v[ni][nj]&&h[ni][nj] <= h[i][j])
                if (reach(h, ni, nj, pac, v)) return true;
        }
        return false;
    }
}
Reverse DFS from each ocean O(m·n) O(m·n)

Climb UP from ocean borders; intersect.

Pseudo-code

DFS from top/left filling 'pac'; DFS from bot/right filling 'atl'; cells in both

Complexity

Time: O(m·n) — Each cell visited twice.
Space: O(m·n) — Two visited arrays.

Edge cases & gotchas

  • 1×1 grid → that cell.

Java code

import java.util.*;
class Solution {
    public List<List<Integer>> pacificAtlantic(int[][] h) {
        int m = h.length, n = h[0].length;
        boolean[][] pac = new boolean[m][n], atl = new boolean[m][n];
        for (int i = 0; i < m; i++) { dfs(h, i, 0, pac); dfs(h, i, n-1, atl); }
        for (int j = 0; j < n; j++) { dfs(h, 0, j, pac); dfs(h, m-1, j, atl); }
        List<List<Integer>> out = new ArrayList<>();
        for (int i = 0; i < m; i++) for (int j = 0; j < n; j++)
            if (pac[i][j] && atl[i][j]) out.add(Arrays.asList(i, j));
        return out;
    }
    private void dfs(int[][] h, int i, int j, boolean[][] v) {
        v[i][j] = true;
        int[][] D = {{-1,0},{1,0},{0,-1},{0,1}};
        for (int[] d : D) {
            int ni = i+d[0], nj = j+d[1];
            if (ni>=0&&nj>=0&&ni<h.length&&nj<h[0].length&&!v[ni][nj]&&h[ni][nj] >= h[i][j])
                dfs(h, ni, nj, v);
        }
    }
}
Reverse BFS from each ocean O(m·n) O(m·n)

Same idea, BFS to avoid stack overflow.

Pseudo-code

BFS from each border instead of DFS

Complexity

Time: O(m·n) — Linear.
Space: O(m·n) — Queue.

Java code

import java.util.*;
class Solution {
    public List<List<Integer>> pacificAtlantic(int[][] h) {
        int m = h.length, n = h[0].length;
        boolean[][] pac = new boolean[m][n], atl = new boolean[m][n];
        Deque<int[]> pq = new ArrayDeque<>(), aq = new ArrayDeque<>();
        for (int i = 0; i < m; i++) { pq.offer(new int[]{i,0}); pac[i][0]=true; aq.offer(new int[]{i,n-1}); atl[i][n-1]=true; }
        for (int j = 0; j < n; j++) { pq.offer(new int[]{0,j}); pac[0][j]=true; aq.offer(new int[]{m-1,j}); atl[m-1][j]=true; }
        bfs(h, pq, pac); bfs(h, aq, atl);
        List<List<Integer>> out = new ArrayList<>();
        for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) if (pac[i][j] && atl[i][j]) out.add(Arrays.asList(i, j));
        return out;
    }
    private void bfs(int[][] h, Deque<int[]> q, boolean[][] v) {
        int[][] D = {{-1,0},{1,0},{0,-1},{0,1}};
        while (!q.isEmpty()) {
            int[] p = q.poll();
            for (int[] d : D) {
                int ni = p[0]+d[0], nj = p[1]+d[1];
                if (ni>=0&&nj>=0&&ni<h.length&&nj<h[0].length&&!v[ni][nj]&&h[ni][nj]>=h[p[0]][p[1]]) { v[ni][nj]=true; q.offer(new int[]{ni,nj}); }
            }
        }
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO((mn)²)O(mn)
SpaceO(mn)O(mn)
Difficulty3/54/5
WhenDon'tWide grids
PE VerdictReverse DFS from each ocean's borders.

Python Solutions

Per-cell DFS to each ocean O((mn)²) O(mn)

Same.

Pseudo-code

see Java

Complexity

Time: O((mn)²) — Naive.
Space: O(mn) — Visited.

Python code

from typing import List
class Solution:
    def pacificAtlantic(self, h: List[List[int]]) -> List[List[int]]:
        m, n = len(h), len(h[0]); out = []
        def reach(i, j, pac, v):
            if pac and (i == 0 or j == 0): return True
            if not pac and (i == m-1 or j == n-1): return True
            v.add((i,j))
            for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
                nr, nc = i+dr, j+dc
                if 0<=nr<m and 0<=nc<n and (nr,nc) not in v and h[nr][nc] <= h[i][j]:
                    if reach(nr, nc, pac, v): return True
            return False
        for i in range(m):
            for j in range(n):
                if reach(i,j,True,set()) and reach(i,j,False,set()): out.append([i,j])
        return out
Reverse DFS from each ocean O(mn) O(mn)

Climb up from borders.

Pseudo-code

see Java

Complexity

Time: O(mn) — Each cell twice.
Space: O(mn) — Two visited sets.

Python code

from typing import List
class Solution:
    def pacificAtlantic(self, h: List[List[int]]) -> List[List[int]]:
        m, n = len(h), len(h[0])
        pac, atl = set(), set()
        def dfs(i, j, v):
            v.add((i,j))
            for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
                nr, nc = i+dr, j+dc
                if 0<=nr<m and 0<=nc<n and (nr,nc) not in v and h[nr][nc] >= h[i][j]:
                    dfs(nr, nc, v)
        for i in range(m): dfs(i, 0, pac); dfs(i, n-1, atl)
        for j in range(n): dfs(0, j, pac); dfs(m-1, j, atl)
        return [[i,j] for i,j in pac & atl]
Reverse BFS O(mn) O(mn)

Iterative alternative.

Pseudo-code

see Java

Complexity

Time: O(mn) — Linear.
Space: O(mn) — Queue.

Python code

from typing import List
from collections import deque
class Solution:
    def pacificAtlantic(self, h: List[List[int]]) -> List[List[int]]:
        m, n = len(h), len(h[0])
        pac, atl = set(), set()
        def bfs(starts, v):
            q = deque(starts)
            for s in starts: v.add(s)
            while q:
                r, c = q.popleft()
                for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
                    nr, nc = r+dr, c+dc
                    if 0<=nr<m and 0<=nc<n and (nr,nc) not in v and h[nr][nc] >= h[r][c]:
                        v.add((nr,nc)); q.append((nr,nc))
        bfs([(i,0) for i in range(m)] + [(0,j) for j in range(n)], pac)
        bfs([(i,n-1) for i in range(m)] + [(m-1,j) for j in range(n)], atl)
        return [[i,j] for i,j in pac & atl]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO((mn)²)O(mn)
SpaceO(mn)O(mn)
Difficulty3/54/5
WhenDon'tWide
PE VerdictReverse DFS from each ocean.

What the interviewer is really testing

Classic reverse-search trick — flip direction so you go from the answer back to the source.

Top gotchas

  • Water flows DOWN; reverse search climbs UP (>= not <=).
  • Run two independent searches; intersect.
  • Borders are seeds, not single cell.

Ship-it

Reverse DFS from each ocean.

#86 LC 130 Medium Surrounded Regions

Border DFS — mark border-connected 'O's safe, then flip the rest.

Problem Statement

Capture all 'O' regions surrounded by 'X'. A region is captured if no 'O' in it touches the border.

Signature: void solve(char[][] board)

Examples

[['X','X','X','X'],['X','O','O','X'],['X','X','O','X'],['X','O','X','X']] → flip middle 'O's

Constraints

  • m, n <= 200

Approach Overview

Brute Force

Java: For each 'O' region, BFS check border

Python: Per-region BFS

O((m·n)²) O(m·n)

Optimal #1

Java: Border DFS — mark safe

Python: Border DFS

O(m·n) O(m·n)

Optimal #2

Java: Union-Find with virtual border node

Python: Union-Find + sentinel

O(m·n·α) O(m·n)

Java Solutions

For each 'O' region, BFS check border O((m·n)²) O(m·n)

For each connected component, decide if touches border.

Pseudo-code

for each 'O' not seen: BFS; if any cell on border, keep; else flip

Complexity

Time: O((m·n)²) — Per-region scan.
Space: O(m·n) — Visited.

Java code

import java.util.*;
class Solution {
    public void solve(char[][] b) {
        int m = b.length, n = b[0].length;
        boolean[][] seen = new boolean[m][n];
        for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) {
            if (b[i][j] != 'O' || seen[i][j]) continue;
            List<int[]> comp = new ArrayList<>();
            Deque<int[]> q = new ArrayDeque<>(); q.offer(new int[]{i,j}); seen[i][j] = true;
            boolean border = false;
            while (!q.isEmpty()) {
                int[] p = q.poll(); comp.add(p);
                if (p[0]==0||p[1]==0||p[0]==m-1||p[1]==n-1) border = true;
                for (int[] d : new int[][]{{-1,0},{1,0},{0,-1},{0,1}}) {
                    int ni = p[0]+d[0], nj = p[1]+d[1];
                    if (ni>=0&&nj>=0&&ni<m&&nj<n&&b[ni][nj]=='O'&&!seen[ni][nj]) { seen[ni][nj]=true; q.offer(new int[]{ni,nj}); }
                }
            }
            if (!border) for (int[] p : comp) b[p[0]][p[1]] = 'X';
        }
    }
}
Border DFS — mark safe O(m·n) O(m·n)

DFS from each border 'O', mark with '#'; flip remaining 'O'→'X'; restore '#'→'O'.

Pseudo-code

DFS border Os → '#'; pass: O→X, #→O

Complexity

Time: O(m·n) — Each cell at most twice.
Space: O(m·n) — Stack.

Edge cases & gotchas

  • Empty grid.
  • All 'X'.

Java code

class Solution {
    public void solve(char[][] b) {
        int m = b.length, n = b[0].length;
        for (int i = 0; i < m; i++) { dfs(b, i, 0); dfs(b, i, n-1); }
        for (int j = 0; j < n; j++) { dfs(b, 0, j); dfs(b, m-1, j); }
        for (int i = 0; i < m; i++) for (int j = 0; j < n; j++)
            b[i][j] = b[i][j] == '#' ? 'O' : 'X';
    }
    private void dfs(char[][] b, int i, int j) {
        if (i<0||j<0||i>=b.length||j>=b[0].length||b[i][j] != 'O') return;
        b[i][j] = '#';
        dfs(b, i+1, j); dfs(b, i-1, j); dfs(b, i, j+1); dfs(b, i, j-1);
    }
}
Union-Find with virtual border node O(m·n·α) O(m·n)

Union border Os with sentinel; flip non-connected.

Pseudo-code

unite border 'O's with sentinel; cells whose root != sentinel → 'X'

Complexity

Time: O(m·n·α) — Near-linear.
Space: O(m·n) — DSU.

Java code

class Solution {
    private int[] p;
    private int find(int x) { while (p[x] != x) { p[x] = p[p[x]]; x = p[x]; } return x; }
    private void union(int a, int b) { int ra = find(a), rb = find(b); if (ra != rb) p[ra] = rb; }
    public void solve(char[][] b) {
        int m = b.length, n = b[0].length, S = m*n;
        p = new int[m*n + 1];
        for (int i = 0; i <= m*n; i++) p[i] = i;
        for (int i = 0; i < m; i++) for (int j = 0; j < n; j++) if (b[i][j] == 'O') {
            if (i==0||j==0||i==m-1||j==n-1) union(i*n+j, S);
            if (i+1<m && b[i+1][j]=='O') union(i*n+j, (i+1)*n+j);
            if (j+1<n && b[i][j+1]=='O') union(i*n+j, i*n+j+1);
        }
        int rs = find(S);
        for (int i = 0; i < m; i++) for (int j = 0; j < n; j++)
            if (b[i][j] == 'O' && find(i*n+j) != rs) b[i][j] = 'X';
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO((mn)²)O(mn·α)
SpaceO(mn)O(mn)
Difficulty3/54/5
WhenDon'tOnline
PE VerdictBorder DFS with sentinel marker.

Python Solutions

Per-region BFS O((mn)²) O(mn)

Same.

Pseudo-code

see Java

Complexity

Time: O((mn)²) — Naive.
Space: O(mn) — Visited.

Python code

from typing import List
from collections import deque
class Solution:
    def solve(self, b: List[List[str]]) -> None:
        m, n = len(b), len(b[0]); seen = [[False]*n for _ in range(m)]
        for i in range(m):
            for j in range(n):
                if b[i][j] != 'O' or seen[i][j]: continue
                comp = []; q = deque([(i,j)]); seen[i][j] = True; border = False
                while q:
                    r, c = q.popleft(); comp.append((r,c))
                    if r in (0,m-1) or c in (0,n-1): border = True
                    for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
                        nr, nc = r+dr, c+dc
                        if 0<=nr<m and 0<=nc<n and b[nr][nc] == 'O' and not seen[nr][nc]:
                            seen[nr][nc] = True; q.append((nr,nc))
                if not border:
                    for r, c in comp: b[r][c] = 'X'
Border DFS O(mn) O(mn)

Mark safe '#' then flip.

Pseudo-code

see Java

Complexity

Time: O(mn) — Linear.
Space: O(mn) — Stack.

Python code

from typing import List
class Solution:
    def solve(self, b: List[List[str]]) -> None:
        m, n = len(b), len(b[0])
        def dfs(i, j):
            if i<0 or j<0 or i>=m or j>=n or b[i][j] != 'O': return
            b[i][j] = '#'
            dfs(i+1,j); dfs(i-1,j); dfs(i,j+1); dfs(i,j-1)
        for i in range(m): dfs(i,0); dfs(i,n-1)
        for j in range(n): dfs(0,j); dfs(m-1,j)
        for i in range(m):
            for j in range(n):
                b[i][j] = 'O' if b[i][j] == '#' else 'X'
Union-Find + sentinel O(mn·α) O(mn)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(mn·α) — Near linear.
Space: O(mn) — DSU.

Python code

from typing import List
class Solution:
    def solve(self, b: List[List[str]]) -> None:
        m, n = len(b), len(b[0]); S = m*n
        p = list(range(m*n + 1))
        def find(x):
            while p[x] != x: p[x] = p[p[x]]; x = p[x]
            return x
        def union(a, c):
            ra, rb = find(a), find(c)
            if ra != rb: p[ra] = rb
        for i in range(m):
            for j in range(n):
                if b[i][j] != 'O': continue
                if i in (0,m-1) or j in (0,n-1): union(i*n+j, S)
                if i+1<m and b[i+1][j] == 'O': union(i*n+j, (i+1)*n+j)
                if j+1<n and b[i][j+1] == 'O': union(i*n+j, i*n+j+1)
        rs = find(S)
        for i in range(m):
            for j in range(n):
                if b[i][j] == 'O' and find(i*n+j) != rs: b[i][j] = 'X'

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO((mn)²)O(mn·α)
SpaceO(mn)O(mn)
Difficulty3/54/5
WhenDon'tOnline
PE VerdictBorder DFS marker trick.

What the interviewer is really testing

Reverse-search/sentinel pattern.

Top gotchas

  • Mark with a 3rd char ('#') so you can distinguish safe O's from to-flip O's.
  • Border DFS first, then sweep.
  • Union-Find variant uses a virtual border node — cleaner mental model.

Ship-it

Border DFS + flip.

#87 LC 207 Medium Course Schedule

Cycle detection in directed graph — Kahn's BFS (topo) or DFS 3-color.

Problem Statement

n courses, prerequisites [[a,b]] meaning b→a. Can you finish all? (i.e., is the graph a DAG?)

Signature: boolean canFinish(int numCourses, int[][] prerequisites)

Examples

n=2, prereq=[[1,0]] → true
n=2, [[1,0],[0,1]] → false

Constraints

  • 1 <= n <= 2000
  • 0 <= edges <= 5000

Approach Overview

Brute Force

Java: DFS with visited set per start

Python: Per-node DFS

O(V·(V+E)) O(V+E)

Optimal #1

Java: Kahn's BFS (topo sort)

Python: Kahn's BFS

O(V+E) O(V+E)

Optimal #2

Java: DFS 3-color cycle detection

Python: DFS 3-color

O(V+E) O(V+E)

Java Solutions

DFS with visited set per start O(V·(V+E)) O(V+E)

For each course, DFS to detect cycle from it.

Pseudo-code

for each course: DFS; if revisit a node on current path → cycle

Complexity

Time: O(V·(V+E)) — Repeated.
Space: O(V+E) — Adj + visited.

Java code

import java.util.*;
class Solution {
    public boolean canFinish(int n, int[][] pre) {
        List<List<Integer>> g = new ArrayList<>();
        for (int i = 0; i < n; i++) g.add(new ArrayList<>());
        for (int[] e : pre) g.get(e[1]).add(e[0]);
        for (int i = 0; i < n; i++) {
            boolean[] onPath = new boolean[n], seen = new boolean[n];
            if (hasCycle(g, i, onPath, seen)) return false;
        }
        return true;
    }
    private boolean hasCycle(List<List<Integer>> g, int u, boolean[] onPath, boolean[] seen) {
        if (onPath[u]) return true;
        if (seen[u]) return false;
        onPath[u] = true; seen[u] = true;
        for (int v : g.get(u)) if (hasCycle(g, v, onPath, seen)) return true;
        onPath[u] = false; return false;
    }
}
Kahn's BFS (topo sort) O(V+E) O(V+E)

Pop indegree=0 nodes; if processed all → DAG.

Pseudo-code

compute indegrees; enqueue 0s; for each pop, decrement neighbors; count == n?

Complexity

Time: O(V+E) — Each node/edge once.
Space: O(V+E) — Adj + queue.

Edge cases & gotchas

  • No prereqs → true.

Java code

import java.util.*;
class Solution {
    public boolean canFinish(int n, int[][] pre) {
        List<List<Integer>> g = new ArrayList<>();
        int[] indeg = new int[n];
        for (int i = 0; i < n; i++) g.add(new ArrayList<>());
        for (int[] e : pre) { g.get(e[1]).add(e[0]); indeg[e[0]]++; }
        Deque<Integer> q = new ArrayDeque<>();
        for (int i = 0; i < n; i++) if (indeg[i] == 0) q.offer(i);
        int taken = 0;
        while (!q.isEmpty()) {
            int u = q.poll(); taken++;
            for (int v : g.get(u)) if (--indeg[v] == 0) q.offer(v);
        }
        return taken == n;
    }
}
DFS 3-color cycle detection O(V+E) O(V+E)

WHITE=0, GRAY=1 (on path), BLACK=2 (done).

Pseudo-code

dfs(u): mark GRAY; for v: if GRAY → cycle; if WHITE → dfs(v); mark BLACK

Complexity

Time: O(V+E) — Each node once.
Space: O(V+E) — Adj + color.

Java code

import java.util.*;
class Solution {
    public boolean canFinish(int n, int[][] pre) {
        List<List<Integer>> g = new ArrayList<>();
        for (int i = 0; i < n; i++) g.add(new ArrayList<>());
        for (int[] e : pre) g.get(e[1]).add(e[0]);
        int[] color = new int[n];
        for (int i = 0; i < n; i++) if (color[i] == 0 && hasCycle(g, i, color)) return false;
        return true;
    }
    private boolean hasCycle(List<List<Integer>> g, int u, int[] color) {
        color[u] = 1;
        for (int v : g.get(u)) {
            if (color[v] == 1) return true;
            if (color[v] == 0 && hasCycle(g, v, color)) return true;
        }
        color[u] = 2; return false;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(V(V+E))O(V+E)
SpaceO(V+E)O(V+E)
Difficulty3/54/5
WhenDon'tStrict cycle test
PE VerdictKahn's BFS — clean and reusable for ordering.

Python Solutions

Per-node DFS O(V(V+E)) O(V+E)

Same.

Pseudo-code

see Java

Complexity

Time: O(V(V+E)) — Naive.
Space: O(V+E) — Adj.

Python code

from typing import List
class Solution:
    def canFinish(self, n: int, pre: List[List[int]]) -> bool:
        g = [[] for _ in range(n)]
        for a, b in pre: g[b].append(a)
        def cycle(u, on_path, seen):
            if u in on_path: return True
            if u in seen: return False
            on_path.add(u); seen.add(u)
            for v in g[u]:
                if cycle(v, on_path, seen): return True
            on_path.remove(u); return False
        for i in range(n):
            if cycle(i, set(), set()): return False
        return True
Kahn's BFS O(V+E) O(V+E)

Topo order.

Pseudo-code

see Java

Complexity

Time: O(V+E) — Linear.
Space: O(V+E) — Queue.

Python code

from typing import List
from collections import deque, defaultdict
class Solution:
    def canFinish(self, n: int, pre: List[List[int]]) -> bool:
        g = defaultdict(list); indeg = [0]*n
        for a, b in pre: g[b].append(a); indeg[a] += 1
        q = deque(i for i in range(n) if indeg[i] == 0)
        taken = 0
        while q:
            u = q.popleft(); taken += 1
            for v in g[u]:
                indeg[v] -= 1
                if indeg[v] == 0: q.append(v)
        return taken == n
DFS 3-color O(V+E) O(V+E)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(V+E) — Linear.
Space: O(V+E) — Recursion.

Python code

from typing import List
class Solution:
    def canFinish(self, n: int, pre: List[List[int]]) -> bool:
        g = [[] for _ in range(n)]
        for a, b in pre: g[b].append(a)
        color = [0]*n  # 0=white,1=gray,2=black
        def cycle(u):
            color[u] = 1
            for v in g[u]:
                if color[v] == 1: return True
                if color[v] == 0 and cycle(v): return True
            color[u] = 2; return False
        return not any(color[i] == 0 and cycle(i) for i in range(n))

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(V(V+E))O(V+E)
SpaceO(V+E)O(V+E)
Difficulty3/54/5
WhenDon'tStrict cycle
PE VerdictKahn's BFS.

What the interviewer is really testing

Topo sort canon.

Top gotchas

  • Build edge direction carefully — prereq[i]=[a,b] usually means take b before a (b→a).
  • Kahn's also gives a valid ordering for free — useful for Course Schedule II.
  • Track 'taken count' to detect cycle without separate scan.

Ship-it

Kahn's topological sort.

#88 LC 210 Medium Course Schedule II

Return a valid topological order; empty array if cycle.

Problem Statement

Same setup as Course Schedule, but return any valid order to take all courses; [] if impossible.

Signature: int[] findOrder(int numCourses, int[][] prerequisites)

Examples

n=4, [[1,0],[2,0],[3,1],[3,2]] → [0,1,2,3] or [0,2,1,3]

Constraints

  • 1 <= n <= 2000

Approach Overview

Brute Force

Java: Naive: pick any 0-indegree repeatedly

Python: Naive rescan

O(V·(V+E)) O(V+E)

Optimal #1

Java: Kahn's BFS

Python: Kahn's BFS

O(V+E) O(V+E)

Optimal #2

Java: DFS postorder (reverse)

Python: DFS postorder

O(V+E) O(V+E)

Java Solutions

Naive: pick any 0-indegree repeatedly O(V·(V+E)) O(V+E)

Rescan each iteration.

Pseudo-code

loop n times: find unvisited node with indeg 0; pop; decrement neighbors

Complexity

Time: O(V·(V+E)) — Rescan.
Space: O(V+E) — Adj.

Java code

import java.util.*;
class Solution {
    public int[] findOrder(int n, int[][] pre) {
        int[] indeg = new int[n];
        List<List<Integer>> g = new ArrayList<>();
        for (int i = 0; i < n; i++) g.add(new ArrayList<>());
        for (int[] e : pre) { g.get(e[1]).add(e[0]); indeg[e[0]]++; }
        int[] out = new int[n]; boolean[] done = new boolean[n]; int k = 0;
        for (int step = 0; step < n; step++) {
            int u = -1;
            for (int i = 0; i < n; i++) if (!done[i] && indeg[i] == 0) { u = i; break; }
            if (u == -1) return new int[0];
            done[u] = true; out[k++] = u;
            for (int v : g.get(u)) indeg[v]--;
        }
        return out;
    }
}
Kahn's BFS O(V+E) O(V+E)

Standard topo sort; emit in pop order.

Pseudo-code

build indegrees; enqueue 0s; emit; if count != n → []

Complexity

Time: O(V+E) — Each once.
Space: O(V+E) — Queue + adj.

Edge cases & gotchas

  • No prereqs → [0..n-1].

Java code

import java.util.*;
class Solution {
    public int[] findOrder(int n, int[][] pre) {
        int[] indeg = new int[n];
        List<List<Integer>> g = new ArrayList<>();
        for (int i = 0; i < n; i++) g.add(new ArrayList<>());
        for (int[] e : pre) { g.get(e[1]).add(e[0]); indeg[e[0]]++; }
        Deque<Integer> q = new ArrayDeque<>();
        for (int i = 0; i < n; i++) if (indeg[i] == 0) q.offer(i);
        int[] out = new int[n]; int k = 0;
        while (!q.isEmpty()) {
            int u = q.poll(); out[k++] = u;
            for (int v : g.get(u)) if (--indeg[v] == 0) q.offer(v);
        }
        return k == n ? out : new int[0];
    }
}
DFS postorder (reverse) O(V+E) O(V+E)

DFS finish time; reverse = topo.

Pseudo-code

dfs(u): mark GRAY; recurse; mark BLACK; push to stack. Pop stack → order.

Complexity

Time: O(V+E) — Linear.
Space: O(V+E) — Recursion + color.

Java code

import java.util.*;
class Solution {
    public int[] findOrder(int n, int[][] pre) {
        List<List<Integer>> g = new ArrayList<>();
        for (int i = 0; i < n; i++) g.add(new ArrayList<>());
        for (int[] e : pre) g.get(e[1]).add(e[0]);
        int[] color = new int[n];
        Deque<Integer> stack = new ArrayDeque<>();
        for (int i = 0; i < n; i++) if (color[i] == 0 && !dfs(g, i, color, stack)) return new int[0];
        int[] out = new int[n];
        for (int k = 0; k < n; k++) out[k] = stack.pop();
        return out;
    }
    private boolean dfs(List<List<Integer>> g, int u, int[] color, Deque<Integer> stack) {
        color[u] = 1;
        for (int v : g.get(u)) {
            if (color[v] == 1) return false;
            if (color[v] == 0 && !dfs(g, v, color, stack)) return false;
        }
        color[u] = 2; stack.push(u); return true;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(V(V+E))O(V+E)
SpaceO(V+E)O(V+E)
Difficulty2/54/5
WhenDon'tRecursive style
PE VerdictKahn's BFS — direct topo emit.

Python Solutions

Naive rescan O(V(V+E)) O(V+E)

Same.

Pseudo-code

see Java

Complexity

Time: O(V(V+E)) — Naive.
Space: O(V+E) — Adj.

Python code

from typing import List
class Solution:
    def findOrder(self, n: int, pre: List[List[int]]) -> List[int]:
        from collections import defaultdict
        g = defaultdict(list); indeg = [0]*n
        for a, b in pre: g[b].append(a); indeg[a] += 1
        out, done = [], [False]*n
        for _ in range(n):
            u = -1
            for i in range(n):
                if not done[i] and indeg[i] == 0: u = i; break
            if u == -1: return []
            done[u] = True; out.append(u)
            for v in g[u]: indeg[v] -= 1
        return out
Kahn's BFS O(V+E) O(V+E)

Topo emit.

Pseudo-code

see Java

Complexity

Time: O(V+E) — Linear.
Space: O(V+E) — Queue.

Python code

from typing import List
from collections import deque, defaultdict
class Solution:
    def findOrder(self, n: int, pre: List[List[int]]) -> List[int]:
        g = defaultdict(list); indeg = [0]*n
        for a, b in pre: g[b].append(a); indeg[a] += 1
        q = deque(i for i in range(n) if indeg[i] == 0)
        out = []
        while q:
            u = q.popleft(); out.append(u)
            for v in g[u]:
                indeg[v] -= 1
                if indeg[v] == 0: q.append(v)
        return out if len(out) == n else []
DFS postorder O(V+E) O(V+E)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(V+E) — Linear.
Space: O(V+E) — Recursion.

Python code

from typing import List
class Solution:
    def findOrder(self, n: int, pre: List[List[int]]) -> List[int]:
        g = [[] for _ in range(n)]
        for a, b in pre: g[b].append(a)
        color = [0]*n; order = []
        def dfs(u):
            color[u] = 1
            for v in g[u]:
                if color[v] == 1: return False
                if color[v] == 0 and not dfs(v): return False
            color[u] = 2; order.append(u); return True
        for i in range(n):
            if color[i] == 0 and not dfs(i): return []
        return order[::-1]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(V(V+E))O(V+E)
SpaceO(V+E)O(V+E)
Difficulty2/54/5
WhenDon'tRecursive
PE VerdictKahn's BFS.

What the interviewer is really testing

Direct extension of Course Schedule I.

Top gotchas

  • Output count < n ⇒ cycle ⇒ return [].
  • DFS variant pushes on finish; remember to REVERSE.
  • Indegree array can double as 'has been emitted' check.

Ship-it

Kahn's BFS, emit on dequeue.

#89 LC 261 Medium Graph Valid Tree

Tree ⇔ connected AND no cycle ⇔ Union-Find never reuses a root.

Problem Statement

n nodes (0..n-1) and edges. Return true if the graph forms a valid tree.

Signature: boolean validTree(int n, int[][] edges)

Examples

n=5, edges=[[0,1],[0,2],[0,3],[1,4]] → true
n=5, edges=[[0,1],[1,2],[2,3],[1,3],[1,4]] → false

Constraints

  • 1 <= n <= 2000

Approach Overview

Brute Force

Java: BFS connectivity + edge count check

Python: BFS + edge count

O(V+E) O(V+E)

Optimal #1

Java: Union-Find

Python: Union-Find

O(E·α) O(V)

Optimal #2

Java: DFS cycle detection

Python: DFS cycle check

O(V+E) O(V+E)

Java Solutions

BFS connectivity + edge count check O(V+E) O(V+E)

Tree has exactly n-1 edges and is connected.

Pseudo-code

if edges != n-1 → false; else BFS from 0; visited == n?

Complexity

Time: O(V+E) — Linear.
Space: O(V+E) — Adj.

Edge cases & gotchas

  • n=1, no edges → true.

Java code

import java.util.*;
class Solution {
    public boolean validTree(int n, int[][] edges) {
        if (edges.length != n - 1) return false;
        List<List<Integer>> g = new ArrayList<>();
        for (int i = 0; i < n; i++) g.add(new ArrayList<>());
        for (int[] e : edges) { g.get(e[0]).add(e[1]); g.get(e[1]).add(e[0]); }
        boolean[] v = new boolean[n];
        Deque<Integer> q = new ArrayDeque<>(); q.offer(0); v[0] = true;
        int cnt = 0;
        while (!q.isEmpty()) { int u = q.poll(); cnt++; for (int x : g.get(u)) if (!v[x]) { v[x] = true; q.offer(x); } }
        return cnt == n;
    }
}
Union-Find O(E·α) O(V)

Reject if union finds same root (cycle); end with 1 component.

Pseudo-code

DSU; for each edge: if same root → false; else union; final components == 1

Complexity

Time: O(E·α) — Near-linear.
Space: O(V) — DSU.

Edge cases & gotchas

  • Self-loop → cycle.

Java code

class Solution {
    private int[] p, sz;
    private int find(int x) { while (p[x] != x) { p[x] = p[p[x]]; x = p[x]; } return x; }
    private boolean union(int a, int b) {
        int ra = find(a), rb = find(b);
        if (ra == rb) return false;
        if (sz[ra] < sz[rb]) { p[ra] = rb; sz[rb] += sz[ra]; }
        else { p[rb] = ra; sz[ra] += sz[rb]; }
        return true;
    }
    public boolean validTree(int n, int[][] edges) {
        if (edges.length != n - 1) return false;
        p = new int[n]; sz = new int[n];
        for (int i = 0; i < n; i++) { p[i] = i; sz[i] = 1; }
        for (int[] e : edges) if (!union(e[0], e[1])) return false;
        return true;
    }
}
DFS cycle detection O(V+E) O(V+E)

Traverse from 0; if revisit non-parent → cycle.

Pseudo-code

dfs(u, parent): if u seen → cycle; mark; for v in adj: if v != parent: dfs

Complexity

Time: O(V+E) — Linear.
Space: O(V+E) — Stack + adj.

Java code

import java.util.*;
class Solution {
    public boolean validTree(int n, int[][] edges) {
        List<List<Integer>> g = new ArrayList<>();
        for (int i = 0; i < n; i++) g.add(new ArrayList<>());
        for (int[] e : edges) { g.get(e[0]).add(e[1]); g.get(e[1]).add(e[0]); }
        boolean[] v = new boolean[n];
        if (hasCycle(g, 0, -1, v)) return false;
        for (boolean b : v) if (!b) return false;
        return true;
    }
    private boolean hasCycle(List<List<Integer>> g, int u, int parent, boolean[] v) {
        v[u] = true;
        for (int x : g.get(u)) {
            if (!v[x]) { if (hasCycle(g, x, u, v)) return true; }
            else if (x != parent) return true;
        }
        return false;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(V+E)O(V+E)
SpaceO(V+E)O(V+E)
Difficulty2/53/5
WhenStaticRecursive friendly
PE VerdictUnion-Find — natural fit for cycle test.

Python Solutions

BFS + edge count O(V+E) O(V+E)

Same.

Pseudo-code

see Java

Complexity

Time: O(V+E) — Linear.
Space: O(V+E) — Adj.

Python code

from typing import List
from collections import deque
class Solution:
    def validTree(self, n: int, edges: List[List[int]]) -> bool:
        if len(edges) != n - 1: return False
        g = [[] for _ in range(n)]
        for a, b in edges: g[a].append(b); g[b].append(a)
        v = [False]*n
        q = deque([0]); v[0] = True; cnt = 0
        while q:
            u = q.popleft(); cnt += 1
            for x in g[u]:
                if not v[x]: v[x] = True; q.append(x)
        return cnt == n
Union-Find O(E·α) O(V)

Reject if union same root.

Pseudo-code

see Java

Complexity

Time: O(E·α) — Near linear.
Space: O(V) — DSU.

Python code

from typing import List
class Solution:
    def validTree(self, n: int, edges: List[List[int]]) -> bool:
        if len(edges) != n - 1: return False
        p = list(range(n)); sz = [1]*n
        def find(x):
            while p[x] != x: p[x] = p[p[x]]; x = p[x]
            return x
        def union(a, b):
            ra, rb = find(a), find(b)
            if ra == rb: return False
            if sz[ra] < sz[rb]: p[ra] = rb; sz[rb] += sz[ra]
            else: p[rb] = ra; sz[ra] += sz[rb]
            return True
        return all(union(a, b) for a, b in edges)
DFS cycle check O(V+E) O(V+E)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(V+E) — Linear.
Space: O(V+E) — Stack.

Python code

from typing import List
class Solution:
    def validTree(self, n: int, edges: List[List[int]]) -> bool:
        g = [[] for _ in range(n)]
        for a, b in edges: g[a].append(b); g[b].append(a)
        v = [False]*n
        def cycle(u, parent):
            v[u] = True
            for x in g[u]:
                if not v[x]:
                    if cycle(x, u): return True
                elif x != parent: return True
            return False
        if cycle(0, -1): return False
        return all(v)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(V+E)O(V+E)
SpaceO(V+E)O(V+E)
Difficulty2/53/5
WhenStaticRecursive
PE VerdictUnion-Find.

What the interviewer is really testing

Tree definition: n-1 edges + connected + acyclic — any 2 ⇒ third.

Top gotchas

  • Edges != n-1 is an instant short-circuit.
  • Undirected DFS needs PARENT tracking to avoid trivial back-edge.
  • Union-Find detects cycle and connectivity in one pass.

Ship-it

Union-Find.

#90 LC 323 Medium Number of Connected Components in an Undirected Graph

Union-Find or DFS — count distinct components.

Problem Statement

n nodes (0..n-1) and undirected edges. Return number of connected components.

Signature: int countComponents(int n, int[][] edges)

Examples

n=5, edges=[[0,1],[1,2],[3,4]] → 2

Constraints

  • 1 <= n <= 2000

Approach Overview

Brute Force

Java: DFS per node

Python: DFS

O(V+E) O(V+E)

Optimal #1

Java: Union-Find

Python: Union-Find

O(E·α) O(V)

Optimal #2

Java: BFS per node

Python: BFS

O(V+E) O(V+E)

Java Solutions

DFS per node O(V+E) O(V+E)

For each unseen node, DFS; count starts.

Pseudo-code

build adj; for each unseen: DFS; cnt++

Complexity

Time: O(V+E) — Linear.
Space: O(V+E) — Adj.

Edge cases & gotchas

  • n=0 → 0.
  • No edges → n.

Java code

import java.util.*;
class Solution {
    public int countComponents(int n, int[][] edges) {
        List<List<Integer>> g = new ArrayList<>();
        for (int i = 0; i < n; i++) g.add(new ArrayList<>());
        for (int[] e : edges) { g.get(e[0]).add(e[1]); g.get(e[1]).add(e[0]); }
        boolean[] v = new boolean[n]; int cnt = 0;
        for (int i = 0; i < n; i++) if (!v[i]) { cnt++; dfs(g, i, v); }
        return cnt;
    }
    private void dfs(List<List<Integer>> g, int u, boolean[] v) {
        v[u] = true;
        for (int x : g.get(u)) if (!v[x]) dfs(g, x, v);
    }
}
Union-Find O(E·α) O(V)

Start at n; decrement each successful union.

Pseudo-code

comps = n; for each edge: if union succeeds → comps--

Complexity

Time: O(E·α) — Near linear.
Space: O(V) — DSU.

Java code

class Solution {
    private int[] p, sz;
    private int find(int x) { while (p[x] != x) { p[x] = p[p[x]]; x = p[x]; } return x; }
    private boolean union(int a, int b) {
        int ra = find(a), rb = find(b);
        if (ra == rb) return false;
        if (sz[ra] < sz[rb]) { p[ra] = rb; sz[rb] += sz[ra]; }
        else { p[rb] = ra; sz[ra] += sz[rb]; }
        return true;
    }
    public int countComponents(int n, int[][] edges) {
        p = new int[n]; sz = new int[n];
        for (int i = 0; i < n; i++) { p[i] = i; sz[i] = 1; }
        int comps = n;
        for (int[] e : edges) if (union(e[0], e[1])) comps--;
        return comps;
    }
}
BFS per node O(V+E) O(V+E)

Iterative variant of brute.

Pseudo-code

for each unseen: BFS; cnt++

Complexity

Time: O(V+E) — Linear.
Space: O(V+E) — Queue.

Java code

import java.util.*;
class Solution {
    public int countComponents(int n, int[][] edges) {
        List<List<Integer>> g = new ArrayList<>();
        for (int i = 0; i < n; i++) g.add(new ArrayList<>());
        for (int[] e : edges) { g.get(e[0]).add(e[1]); g.get(e[1]).add(e[0]); }
        boolean[] v = new boolean[n]; int cnt = 0;
        for (int i = 0; i < n; i++) {
            if (v[i]) continue;
            cnt++;
            Deque<Integer> q = new ArrayDeque<>(); q.offer(i); v[i] = true;
            while (!q.isEmpty()) { int u = q.poll(); for (int x : g.get(u)) if (!v[x]) { v[x] = true; q.offer(x); } }
        }
        return cnt;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(V+E)O(V+E)
SpaceO(V+E)O(V+E)
Difficulty2/52/5
WhenStaticWide
PE VerdictUnion-Find — single-pass component counting.

Python Solutions

DFS O(V+E) O(V+E)

Same.

Pseudo-code

see Java

Complexity

Time: O(V+E) — Linear.
Space: O(V+E) — Adj.

Python code

from typing import List
class Solution:
    def countComponents(self, n: int, edges: List[List[int]]) -> int:
        g = [[] for _ in range(n)]
        for a, b in edges: g[a].append(b); g[b].append(a)
        v = [False]*n; cnt = 0
        def dfs(u):
            v[u] = True
            for x in g[u]:
                if not v[x]: dfs(x)
        for i in range(n):
            if not v[i]: cnt += 1; dfs(i)
        return cnt
Union-Find O(E·α) O(V)

Decrement on union.

Pseudo-code

see Java

Complexity

Time: O(E·α) — Near linear.
Space: O(V) — DSU.

Python code

from typing import List
class Solution:
    def countComponents(self, n: int, edges: List[List[int]]) -> int:
        p = list(range(n)); sz = [1]*n; comps = n
        def find(x):
            while p[x] != x: p[x] = p[p[x]]; x = p[x]
            return x
        def union(a, b):
            ra, rb = find(a), find(b)
            if ra == rb: return False
            if sz[ra] < sz[rb]: p[ra] = rb; sz[rb] += sz[ra]
            else: p[rb] = ra; sz[ra] += sz[rb]
            return True
        for a, b in edges:
            if union(a, b): comps -= 1
        return comps
BFS O(V+E) O(V+E)

Same.

Pseudo-code

see Java

Complexity

Time: O(V+E) — Linear.
Space: O(V+E) — Queue.

Python code

from typing import List
from collections import deque
class Solution:
    def countComponents(self, n: int, edges: List[List[int]]) -> int:
        g = [[] for _ in range(n)]
        for a, b in edges: g[a].append(b); g[b].append(a)
        v = [False]*n; cnt = 0
        for i in range(n):
            if v[i]: continue
            cnt += 1
            q = deque([i]); v[i] = True
            while q:
                u = q.popleft()
                for x in g[u]:
                    if not v[x]: v[x] = True; q.append(x)
        return cnt

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(V+E)O(V+E)
SpaceO(V+E)O(V+E)
Difficulty2/52/5
WhenStaticWide
PE VerdictUnion-Find.

What the interviewer is really testing

Classic DSU drill.

Top gotchas

  • Start comps at n, then subtract on successful union.
  • DFS/BFS variant works fine and is easier to write.
  • Isolated nodes still count as components — Union-Find handles for free.

Ship-it

Union-Find with path compression.

#91 LC 684 Medium Redundant Connection

Union-Find — first edge whose endpoints already share a root is the answer.

Problem Statement

Tree with one extra edge added. Return that extra edge (the one appearing last in input that forms a cycle).

Signature: int[] findRedundantConnection(int[][] edges)

Examples

[[1,2],[1,3],[2,3]] → [2,3]

Constraints

  • 3 <= n <= 1000

Approach Overview

Brute Force

Java: BFS/DFS after each edge

Python: BFS per edge

O(n²) O(n)

Optimal #1

Java: Union-Find

Python: Union-Find

O(n·α) O(n)

Optimal #2

Java: DFS cycle finder

Python: DFS reverse-removal

O(n²) O(n²)

Java Solutions

BFS/DFS after each edge O(n²) O(n)

Add edges one by one; after each, check if cycle forms.

Pseudo-code

for each edge: if endpoints already connected (BFS) → return; else add

Complexity

Time: O(n²) — n BFS scans.
Space: O(n) — Adj.

Java code

import java.util.*;
class Solution {
    public int[] findRedundantConnection(int[][] edges) {
        int n = edges.length;
        List<List<Integer>> g = new ArrayList<>();
        for (int i = 0; i <= n; i++) g.add(new ArrayList<>());
        for (int[] e : edges) {
            if (connected(g, e[0], e[1])) return e;
            g.get(e[0]).add(e[1]); g.get(e[1]).add(e[0]);
        }
        return new int[0];
    }
    private boolean connected(List<List<Integer>> g, int a, int b) {
        Set<Integer> v = new HashSet<>(); v.add(a);
        Deque<Integer> q = new ArrayDeque<>(); q.offer(a);
        while (!q.isEmpty()) {
            int u = q.poll();
            if (u == b) return true;
            for (int x : g.get(u)) if (v.add(x)) q.offer(x);
        }
        return false;
    }
}
Union-Find O(n·α) O(n)

First edge whose union fails is the answer.

Pseudo-code

DSU on n+1; for each edge: if same root → return; else union

Complexity

Time: O(n·α) — Near linear.
Space: O(n) — DSU.

Java code

class Solution {
    private int[] p, sz;
    private int find(int x) { while (p[x] != x) { p[x] = p[p[x]]; x = p[x]; } return x; }
    private boolean union(int a, int b) {
        int ra = find(a), rb = find(b);
        if (ra == rb) return false;
        if (sz[ra] < sz[rb]) { p[ra] = rb; sz[rb] += sz[ra]; }
        else { p[rb] = ra; sz[ra] += sz[rb]; }
        return true;
    }
    public int[] findRedundantConnection(int[][] edges) {
        int n = edges.length;
        p = new int[n + 1]; sz = new int[n + 1];
        for (int i = 0; i <= n; i++) { p[i] = i; sz[i] = 1; }
        for (int[] e : edges) if (!union(e[0], e[1])) return e;
        return new int[0];
    }
}
DFS cycle finder O(n²) O(n²)

Build full graph; DFS to find the cycle; return last input edge in cycle.

Pseudo-code

build adj; for each edge in reverse: check if removing it leaves connected tree

Complexity

Time: O(n²) — Repeated.
Space: O(n²) — Adj + visited.

Java code

import java.util.*;
class Solution {
    public int[] findRedundantConnection(int[][] edges) {
        int n = edges.length;
        for (int i = n - 1; i >= 0; i--) {
            Map<Integer, List<Integer>> g = new HashMap<>();
            for (int j = 0; j < n; j++) {
                if (j == i) continue;
                g.computeIfAbsent(edges[j][0], k -> new ArrayList<>()).add(edges[j][1]);
                g.computeIfAbsent(edges[j][1], k -> new ArrayList<>()).add(edges[j][0]);
            }
            Set<Integer> v = new HashSet<>();
            Deque<Integer> q = new ArrayDeque<>(); q.offer(1); v.add(1);
            while (!q.isEmpty()) { int u = q.poll(); for (int x : g.getOrDefault(u, new ArrayList<>())) if (v.add(x)) q.offer(x); }
            if (v.size() == n) return edges[i];
        }
        return new int[0];
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n²)
SpaceO(n)O(n²)
Difficulty3/54/5
WhenTinyDemonstrative
PE VerdictUnion-Find — short, fast, idiomatic.

Python Solutions

BFS per edge O(n²) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²) — Naive.
Space: O(n) — Adj.

Python code

from typing import List
from collections import defaultdict, deque
class Solution:
    def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
        g = defaultdict(list)
        def connected(a, b):
            v = {a}; q = deque([a])
            while q:
                u = q.popleft()
                if u == b: return True
                for x in g[u]:
                    if x not in v: v.add(x); q.append(x)
            return False
        for a, b in edges:
            if connected(a, b): return [a, b]
            g[a].append(b); g[b].append(a)
        return []
Union-Find O(n·α) O(n)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(n·α) — Near linear.
Space: O(n) — DSU.

Python code

from typing import List
class Solution:
    def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
        n = len(edges); p = list(range(n+1)); sz = [1]*(n+1)
        def find(x):
            while p[x] != x: p[x] = p[p[x]]; x = p[x]
            return x
        def union(a, b):
            ra, rb = find(a), find(b)
            if ra == rb: return False
            if sz[ra] < sz[rb]: p[ra] = rb; sz[rb] += sz[ra]
            else: p[rb] = ra; sz[ra] += sz[rb]
            return True
        for a, b in edges:
            if not union(a, b): return [a, b]
        return []
DFS reverse-removal O(n²) O(n²)

Same idea as Java opt2.

Pseudo-code

see Java

Complexity

Time: O(n²) — Repeated.
Space: O(n²) — Adj.

Python code

from typing import List
from collections import defaultdict, deque
class Solution:
    def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
        n = len(edges)
        for i in range(n-1, -1, -1):
            g = defaultdict(list)
            for j in range(n):
                if j == i: continue
                g[edges[j][0]].append(edges[j][1]); g[edges[j][1]].append(edges[j][0])
            v = {1}; q = deque([1])
            while q:
                u = q.popleft()
                for x in g[u]:
                    if x not in v: v.add(x); q.append(x)
            if len(v) == n: return edges[i]
        return []

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n²)
SpaceO(n)O(n²)
Difficulty3/54/5
WhenTinyDemonstrative
PE VerdictUnion-Find — first failing union.

What the interviewer is really testing

Show DSU instinct.

Top gotchas

  • The problem says return the edge appearing LAST in input that forms a cycle — Union-Find processes in order; the first failing union IS that edge.
  • Allocate n+1 since nodes are 1-indexed.
  • Path compression + union by size for full credit.

Ship-it

Union-Find.

#92 LC 127 Hard Word Ladder

BFS over words; neighbors = strings differing in exactly one letter.

Problem Statement

Shortest transformation sequence length from beginWord to endWord; each step must be in wordList and change exactly one letter.

Signature: int ladderLength(String beginWord, String endWord, List<String> wordList)

Examples

begin='hit', end='cog', words=['hot','dot','dog','lot','log','cog'] → 5

Constraints

  • 1 <= len(word) <= 10
  • 1 <= wordList.length <= 5000

Approach Overview

Brute Force

Java: BFS, compare every pair

Python: BFS, compare all

O(N²·L) O(N·L)

Optimal #1

Java: BFS with wildcard pattern map

Python: Pattern-map BFS

O(N·L²) O(N·L²)

Optimal #2

Java: Bidirectional BFS

Python: Bidirectional BFS

O(N·L²) O(N·L²)

Java Solutions

BFS, compare every pair O(N²·L) O(N·L)

Each step scan whole list to find neighbors.

Pseudo-code

BFS; for current word: scan list, neighbor if Hamming==1

Complexity

Time: O(N²·L) — All-pairs each level.
Space: O(N·L) — Queue.

Edge cases & gotchas

  • end not in list → 0.

Java code

import java.util.*;
class Solution {
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        Set<String> dict = new HashSet<>(wordList);
        if (!dict.contains(endWord)) return 0;
        Deque<String> q = new ArrayDeque<>(); q.offer(beginWord);
        Set<String> visited = new HashSet<>(); visited.add(beginWord);
        int steps = 1;
        while (!q.isEmpty()) {
            int sz = q.size();
            for (int k = 0; k < sz; k++) {
                String w = q.poll();
                if (w.equals(endWord)) return steps;
                for (String d : new ArrayList<>(dict)) {
                    if (!visited.contains(d) && diff1(w, d)) { visited.add(d); q.offer(d); }
                }
            }
            steps++;
        }
        return 0;
    }
    private boolean diff1(String a, String b) {
        if (a.length() != b.length()) return false;
        int d = 0;
        for (int i = 0; i < a.length(); i++) if (a.charAt(i) != b.charAt(i) && ++d > 1) return false;
        return d == 1;
    }
}
BFS with wildcard pattern map O(N·L²) O(N·L²)

Pre-build map 'h*t' → ['hot','hit',...]; O(L) neighbor lookup.

Pseudo-code

build pattern map; BFS; neighbors = map[pattern]

Complexity

Time: O(N·L²) — Each word L patterns.
Space: O(N·L²) — Pattern map.

Java code

import java.util.*;
class Solution {
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        Set<String> dict = new HashSet<>(wordList);
        if (!dict.contains(endWord)) return 0;
        int L = beginWord.length();
        Map<String, List<String>> pat = new HashMap<>();
        for (String w : wordList) for (int i = 0; i < L; i++) {
            String p = w.substring(0, i) + '*' + w.substring(i + 1);
            pat.computeIfAbsent(p, k -> new ArrayList<>()).add(w);
        }
        Deque<String> q = new ArrayDeque<>(); q.offer(beginWord);
        Set<String> seen = new HashSet<>(); seen.add(beginWord);
        int steps = 1;
        while (!q.isEmpty()) {
            int sz = q.size();
            for (int k = 0; k < sz; k++) {
                String w = q.poll();
                if (w.equals(endWord)) return steps;
                for (int i = 0; i < L; i++) {
                    String p = w.substring(0, i) + '*' + w.substring(i + 1);
                    for (String nb : pat.getOrDefault(p, new ArrayList<>())) {
                        if (seen.add(nb)) q.offer(nb);
                    }
                }
            }
            steps++;
        }
        return 0;
    }
}
Bidirectional BFS O(N·L²) O(N·L²)

Expand from both ends; meet in middle.

Pseudo-code

two sets begin/end; expand smaller; if neighbor in other → done

Complexity

Time: O(N·L²) — Halved branching.
Space: O(N·L²) — Two frontiers.

Java code

import java.util.*;
class Solution {
    public int ladderLength(String beginWord, String endWord, List<String> wordList) {
        Set<String> dict = new HashSet<>(wordList);
        if (!dict.contains(endWord)) return 0;
        Set<String> bs = new HashSet<>(), es = new HashSet<>(), seen = new HashSet<>();
        bs.add(beginWord); es.add(endWord); seen.add(beginWord);
        int steps = 1;
        while (!bs.isEmpty() && !es.isEmpty()) {
            if (bs.size() > es.size()) { Set<String> t = bs; bs = es; es = t; }
            Set<String> next = new HashSet<>();
            for (String w : bs) {
                char[] arr = w.toCharArray();
                for (int i = 0; i < arr.length; i++) {
                    char orig = arr[i];
                    for (char c = 'a'; c <= 'z'; c++) {
                        arr[i] = c;
                        String nb = new String(arr);
                        if (es.contains(nb)) return steps + 1;
                        if (dict.contains(nb) && seen.add(nb)) next.add(nb);
                    }
                    arr[i] = orig;
                }
            }
            bs = next; steps++;
        }
        return 0;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(N²·L)O(N·L²)
SpaceO(N·L)O(N·L²)
Difficulty2/55/5
WhenDon'tLong words, big dict
PE VerdictPattern-map BFS; bidirectional for very large inputs.

Python Solutions

BFS, compare all O(N²·L) O(N·L)

Same.

Pseudo-code

see Java

Complexity

Time: O(N²·L) — Naive.
Space: O(N·L) — Set.

Python code

from typing import List
from collections import deque
class Solution:
    def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
        d = set(wordList)
        if endWord not in d: return 0
        q = deque([(beginWord, 1)]); seen = {beginWord}
        while q:
            w, steps = q.popleft()
            if w == endWord: return steps
            for nb in list(d):
                if nb not in seen and sum(a != b for a, b in zip(w, nb)) == 1:
                    seen.add(nb); q.append((nb, steps+1))
        return 0
Pattern-map BFS O(N·L²) O(N·L²)

h*t → [hot, hit].

Pseudo-code

see Java

Complexity

Time: O(N·L²) — Each word L patterns.
Space: O(N·L²) — Map.

Python code

from typing import List
from collections import deque, defaultdict
class Solution:
    def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
        d = set(wordList)
        if endWord not in d: return 0
        L = len(beginWord)
        pat = defaultdict(list)
        for w in wordList:
            for i in range(L):
                pat[w[:i] + '*' + w[i+1:]].append(w)
        q = deque([(beginWord, 1)]); seen = {beginWord}
        while q:
            w, steps = q.popleft()
            if w == endWord: return steps
            for i in range(L):
                key = w[:i] + '*' + w[i+1:]
                for nb in pat[key]:
                    if nb not in seen:
                        seen.add(nb); q.append((nb, steps+1))
                pat[key] = []  # prune to avoid revisits
        return 0
Bidirectional BFS O(N·L²) O(N·L²)

Two frontiers meeting.

Pseudo-code

see Java

Complexity

Time: O(N·L²) — Halved branching.
Space: O(N·L²) — Two sets.

Python code

from typing import List
class Solution:
    def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
        d = set(wordList)
        if endWord not in d: return 0
        bs, es, seen = {beginWord}, {endWord}, {beginWord}
        steps = 1
        while bs and es:
            if len(bs) > len(es): bs, es = es, bs
            nxt = set()
            for w in bs:
                for i in range(len(w)):
                    for c in 'abcdefghijklmnopqrstuvwxyz':
                        nb = w[:i] + c + w[i+1:]
                        if nb in es: return steps + 1
                        if nb in d and nb not in seen:
                            seen.add(nb); nxt.add(nb)
            bs = nxt; steps += 1
        return 0

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(N²·L)O(N·L²)
SpaceO(N·L)O(N·L²)
Difficulty2/55/5
WhenDon'tLarge
PE VerdictPattern-map BFS.

What the interviewer is really testing

Shortest path on implicit graph — BFS is natural.

Top gotchas

  • endWord MUST be in dict, else return 0 instantly.
  • Pattern map is the trick for O(L²) per node instead of O(N·L).
  • Bidirectional BFS roughly square-roots the work.

Ship-it

BFS with wildcard pattern map.

Advanced Graphs

#93 LC 743 Medium Network Delay Time

Single-source shortest path on weighted DAG — Dijkstra with min-heap.

Problem Statement

Directed weighted graph, n nodes. times[i]=[u,v,w]. Return min time for signal from k to reach ALL nodes (or -1).

Signature: int networkDelayTime(int[][] times, int n, int k)

Examples

times=[[2,1,1],[2,3,1],[3,4,1]], n=4, k=2 → 2

Constraints

  • 1 <= n <= 100
  • 1 <= times.length <= 6000

Approach Overview

Brute Force

Java: Bellman-Ford

Python: Bellman-Ford

O(V·E) O(V)

Optimal #1

Java: Dijkstra (min-heap)

Python: Dijkstra

O((V+E)logV) O(V+E)

Optimal #2

Java: SPFA (Bellman-Ford queue)

Python: SPFA

O(V·E) worst O(V)

Java Solutions

Bellman-Ford O(V·E) O(V)

Relax all edges n-1 times.

Pseudo-code

dist[k]=0; for V-1 iters: for each edge: relax; max(dist)

Complexity

Time: O(V·E) — V passes.
Space: O(V) — dist.

Edge cases & gotchas

  • Unreachable → -1.

Java code

import java.util.*;
class Solution {
    public int networkDelayTime(int[][] times, int n, int k) {
        int[] dist = new int[n + 1];
        Arrays.fill(dist, Integer.MAX_VALUE);
        dist[k] = 0;
        for (int i = 0; i < n - 1; i++) {
            for (int[] e : times) {
                if (dist[e[0]] != Integer.MAX_VALUE && dist[e[0]] + e[2] < dist[e[1]])
                    dist[e[1]] = dist[e[0]] + e[2];
            }
        }
        int ans = 0;
        for (int i = 1; i <= n; i++) {
            if (dist[i] == Integer.MAX_VALUE) return -1;
            ans = Math.max(ans, dist[i]);
        }
        return ans;
    }
}
Dijkstra (min-heap) O((V+E)logV) O(V+E)

Pop smallest; relax neighbors.

Pseudo-code

heap.push((0,k)); while heap: pop min; for each neighbor relax & push

Complexity

Time: O((V+E)logV) — Heap ops.
Space: O(V+E) — Adj + dist.

Edge cases & gotchas

  • k unreachable to some → -1.

Java code

import java.util.*;
class Solution {
    public int networkDelayTime(int[][] times, int n, int k) {
        List<List<int[]>> g = new ArrayList<>();
        for (int i = 0; i <= n; i++) g.add(new ArrayList<>());
        for (int[] t : times) g.get(t[0]).add(new int[]{t[1], t[2]});
        int[] dist = new int[n + 1];
        Arrays.fill(dist, Integer.MAX_VALUE);
        dist[k] = 0;
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]);
        pq.offer(new int[]{k, 0});
        while (!pq.isEmpty()) {
            int[] p = pq.poll();
            int u = p[0], d = p[1];
            if (d > dist[u]) continue;
            for (int[] e : g.get(u)) {
                int v = e[0], w = e[1];
                if (d + w < dist[v]) { dist[v] = d + w; pq.offer(new int[]{v, dist[v]}); }
            }
        }
        int ans = 0;
        for (int i = 1; i <= n; i++) {
            if (dist[i] == Integer.MAX_VALUE) return -1;
            ans = Math.max(ans, dist[i]);
        }
        return ans;
    }
}
SPFA (Bellman-Ford queue) O(V·E) worst O(V)

Relax via queue; only re-enqueue if improved.

Pseudo-code

enqueue k; pop u; relax edges; enqueue improved v

Complexity

Time: O(V·E) worst — Often near linear.
Space: O(V) — Queue.

Java code

import java.util.*;
class Solution {
    public int networkDelayTime(int[][] times, int n, int k) {
        List<List<int[]>> g = new ArrayList<>();
        for (int i = 0; i <= n; i++) g.add(new ArrayList<>());
        for (int[] t : times) g.get(t[0]).add(new int[]{t[1], t[2]});
        int[] dist = new int[n + 1];
        Arrays.fill(dist, Integer.MAX_VALUE);
        dist[k] = 0;
        Deque<Integer> q = new ArrayDeque<>(); q.offer(k);
        boolean[] inQ = new boolean[n + 1]; inQ[k] = true;
        while (!q.isEmpty()) {
            int u = q.poll(); inQ[u] = false;
            for (int[] e : g.get(u)) {
                int v = e[0], w = e[1];
                if (dist[u] + w < dist[v]) {
                    dist[v] = dist[u] + w;
                    if (!inQ[v]) { q.offer(v); inQ[v] = true; }
                }
            }
        }
        int ans = 0;
        for (int i = 1; i <= n; i++) {
            if (dist[i] == Integer.MAX_VALUE) return -1;
            ans = Math.max(ans, dist[i]);
        }
        return ans;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(V·E)O(V·E) worst
SpaceO(V)O(V)
Difficulty2/54/5
WhenNegative edgesSparse + dense mix
PE VerdictDijkstra with min-heap.

Python Solutions

Bellman-Ford O(V·E) O(V)

Same.

Pseudo-code

see Java

Complexity

Time: O(V·E) — V passes.
Space: O(V) — dist.

Python code

from typing import List
class Solution:
    def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
        INF = float('inf')
        dist = [INF] * (n + 1); dist[k] = 0
        for _ in range(n - 1):
            for u, v, w in times:
                if dist[u] + w < dist[v]: dist[v] = dist[u] + w
        ans = max(dist[1:])
        return ans if ans < INF else -1
Dijkstra O((V+E)logV) O(V+E)

heapq min-heap.

Pseudo-code

see Java

Complexity

Time: O((V+E)logV) — Heap.
Space: O(V+E) — Adj + dist.

Python code

from typing import List
import heapq
from collections import defaultdict
class Solution:
    def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
        g = defaultdict(list)
        for u, v, w in times: g[u].append((v, w))
        INF = float('inf')
        dist = [INF] * (n + 1); dist[k] = 0
        h = [(0, k)]
        while h:
            d, u = heapq.heappop(h)
            if d > dist[u]: continue
            for v, w in g[u]:
                if d + w < dist[v]:
                    dist[v] = d + w; heapq.heappush(h, (dist[v], v))
        ans = max(dist[1:])
        return ans if ans < INF else -1
SPFA O(V·E) worst O(V)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(V·E) worst — Often fast.
Space: O(V) — Queue.

Python code

from typing import List
from collections import defaultdict, deque
class Solution:
    def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
        g = defaultdict(list)
        for u, v, w in times: g[u].append((v, w))
        INF = float('inf')
        dist = [INF] * (n + 1); dist[k] = 0
        q = deque([k]); inQ = [False] * (n + 1); inQ[k] = True
        while q:
            u = q.popleft(); inQ[u] = False
            for v, w in g[u]:
                if dist[u] + w < dist[v]:
                    dist[v] = dist[u] + w
                    if not inQ[v]: q.append(v); inQ[v] = True
        ans = max(dist[1:])
        return ans if ans < INF else -1

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(V·E)O(V·E) worst
SpaceO(V)O(V)
Difficulty2/54/5
WhenNegative edgesMixed
PE VerdictDijkstra.

What the interviewer is really testing

Classic Dijkstra single-source shortest path.

Top gotchas

  • Answer is the MAX of all dist (last node to receive the signal).
  • Any unreachable node → return -1.
  • Don't relax stale heap entries — skip if d > dist[u].

Ship-it

Dijkstra with heap, skip stale.

#94 LC 332 Hard Reconstruct Itinerary

Eulerian path — Hierholzer's algorithm with lexicographic min-heap.

Problem Statement

Use all tickets exactly once, starting from JFK. Return itinerary with lexicographically smallest order on ties.

Signature: List<String> findItinerary(List<List<String>> tickets)

Examples

[["MUC","LHR"],["JFK","MUC"],["SFO","SJC"],["LHR","SFO"]] → [JFK,MUC,LHR,SFO,SJC]

Constraints

  • 1 <= tickets.length <= 300

Approach Overview

Brute Force

Java: Backtracking try-all

Python: Backtracking

O(E!) O(E)

Optimal #1

Java: Hierholzer's with min-heap

Python: Hierholzer's with min-heap

O(E·logE) O(E)

Optimal #2

Java: Iterative Hierholzer

Python: Iterative Hierholzer

O(E·logE) O(E)

Java Solutions

Backtracking try-all O(E!) O(E)

Sort tickets; try paths greedily, backtrack.

Pseudo-code

sort; from JFK try edges in order; if dead end, backtrack

Complexity

Time: O(E!) — Exponential.
Space: O(E) — Recursion.

Java code

import java.util.*;
class Solution {
    public List<String> findItinerary(List<List<String>> tickets) {
        Map<String, List<String>> g = new HashMap<>();
        for (List<String> t : tickets) g.computeIfAbsent(t.get(0), k -> new ArrayList<>()).add(t.get(1));
        for (List<String> v : g.values()) Collections.sort(v);
        List<String> path = new ArrayList<>(); path.add("JFK");
        dfs("JFK", g, path, tickets.size() + 1);
        return path;
    }
    private boolean dfs(String u, Map<String, List<String>> g, List<String> path, int target) {
        if (path.size() == target) return true;
        List<String> nbs = g.get(u);
        if (nbs == null) return false;
        for (int i = 0; i < nbs.size(); i++) {
            String v = nbs.remove(i);
            path.add(v);
            if (dfs(v, g, path, target)) return true;
            path.remove(path.size() - 1);
            nbs.add(i, v);
        }
        return false;
    }
}
Hierholzer's with min-heap O(E·logE) O(E)

DFS, append on stack-unwind; reverse at end.

Pseudo-code

min-heap of dests per node; dfs(u): while heap nonempty: pop; dfs(v); append u

Complexity

Time: O(E·logE) — Heap per edge.
Space: O(E) — Adj + path.

Edge cases & gotchas

  • Disconnected (won't happen if valid).

Java code

import java.util.*;
class Solution {
    public List<String> findItinerary(List<List<String>> tickets) {
        Map<String, PriorityQueue<String>> g = new HashMap<>();
        for (List<String> t : tickets) g.computeIfAbsent(t.get(0), k -> new PriorityQueue<>()).offer(t.get(1));
        LinkedList<String> path = new LinkedList<>();
        dfs("JFK", g, path);
        return path;
    }
    private void dfs(String u, Map<String, PriorityQueue<String>> g, LinkedList<String> path) {
        PriorityQueue<String> pq = g.get(u);
        while (pq != null && !pq.isEmpty()) dfs(pq.poll(), g, path);
        path.addFirst(u);
    }
}
Iterative Hierholzer O(E·logE) O(E)

Stack-based version; same idea no recursion.

Pseudo-code

stack=[JFK]; while stack: peek u; if has out-edge: push smallest dest; else pop into result

Complexity

Time: O(E·logE) — Heap.
Space: O(E) — Stack.

Java code

import java.util.*;
class Solution {
    public List<String> findItinerary(List<List<String>> tickets) {
        Map<String, PriorityQueue<String>> g = new HashMap<>();
        for (List<String> t : tickets) g.computeIfAbsent(t.get(0), k -> new PriorityQueue<>()).offer(t.get(1));
        LinkedList<String> path = new LinkedList<>();
        Deque<String> stack = new ArrayDeque<>(); stack.push("JFK");
        while (!stack.isEmpty()) {
            String u = stack.peek();
            PriorityQueue<String> pq = g.get(u);
            if (pq == null || pq.isEmpty()) path.addFirst(stack.pop());
            else stack.push(pq.poll());
        }
        return path;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(E!)O(E·logE)
SpaceO(E)O(E)
Difficulty3/54/5
WhenDon'tStack-safe
PE VerdictHierholzer's with min-heap.

Python Solutions

Backtracking O(E!) O(E)

Same.

Pseudo-code

see Java

Complexity

Time: O(E!) — Exponential.
Space: O(E) — Recursion.

Python code

from typing import List
from collections import defaultdict
class Solution:
    def findIterary_brute(self, tickets):
        g = defaultdict(list)
        for a, b in tickets: g[a].append(b)
        for v in g.values(): v.sort()
        target = len(tickets) + 1
        path = ['JFK']
        def dfs(u):
            if len(path) == target: return True
            for i in range(len(g[u])):
                v = g[u].pop(i); path.append(v)
                if dfs(v): return True
                path.pop(); g[u].insert(i, v)
            return False
        dfs('JFK')
        return path
    def findItinerary(self, tickets):
        return self.findIterary_brute(tickets)
Hierholzer's with min-heap O(E·logE) O(E)

DFS, prepend on unwind.

Pseudo-code

see Java

Complexity

Time: O(E·logE) — Heap.
Space: O(E) — Adj.

Python code

from typing import List
import heapq
from collections import defaultdict
class Solution:
    def findItinerary(self, tickets: List[List[str]]) -> List[str]:
        g = defaultdict(list)
        for a, b in tickets: heapq.heappush(g[a], b)
        path = []
        def dfs(u):
            while g[u]: dfs(heapq.heappop(g[u]))
            path.append(u)
        dfs('JFK')
        return path[::-1]
Iterative Hierholzer O(E·logE) O(E)

Stack version.

Pseudo-code

see Java

Complexity

Time: O(E·logE) — Heap.
Space: O(E) — Stack.

Python code

from typing import List
import heapq
from collections import defaultdict
class Solution:
    def findItinerary(self, tickets: List[List[str]]) -> List[str]:
        g = defaultdict(list)
        for a, b in tickets: heapq.heappush(g[a], b)
        path = []; stack = ['JFK']
        while stack:
            u = stack[-1]
            if g[u]: stack.append(heapq.heappop(g[u]))
            else: path.append(stack.pop())
        return path[::-1]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(E!)O(E·logE)
SpaceO(E)O(E)
Difficulty3/54/5
WhenDon'tStack-safe
PE VerdictHierholzer's DFS.

What the interviewer is really testing

Eulerian path with lex constraint.

Top gotchas

  • Append to path on stack unwind (NOT pre-order) — this is the Hierholzer trick.
  • Reverse at the end.
  • Min-heap (or sorted list) keeps lexicographic order.

Ship-it

Hierholzer's DFS with min-heap.

#95 LC 1584 Medium Min Cost to Connect All Points

Minimum spanning tree on a complete graph — Prim's (heap) or Kruskal's (DSU).

Problem Statement

Given 2D points, edges are Manhattan distance. Return min cost to connect all points.

Signature: int minCostConnectPoints(int[][] points)

Examples

[[0,0],[2,2],[3,10],[5,2],[7,0]] → 20

Constraints

  • 1 <= n <= 1000

Approach Overview

Brute Force

Java: Prim's without heap

Python: Prim's O(V²)

O(V²) O(V)

Optimal #1

Java: Prim's with min-heap

Python: Prim's with heap

O(V²·logV) O(V²)

Optimal #2

Java: Kruskal's with DSU

Python: Kruskal's

O(V²·logV) O(V²)

Java Solutions

Prim's without heap O(V²) O(V)

O(V²) repeatedly pick min-key vertex.

Pseudo-code

key[0]=0; loop V: pick min unvisited; mark; relax all neighbors

Complexity

Time: O(V²) — V passes V scan.
Space: O(V) — key + inMST.

Edge cases & gotchas

  • n=1 → 0.

Java code

class Solution {
    public int minCostConnectPoints(int[][] p) {
        int n = p.length;
        int[] key = new int[n];
        boolean[] in = new boolean[n];
        java.util.Arrays.fill(key, Integer.MAX_VALUE);
        key[0] = 0; int total = 0;
        for (int i = 0; i < n; i++) {
            int u = -1, best = Integer.MAX_VALUE;
            for (int j = 0; j < n; j++) if (!in[j] && key[j] < best) { best = key[j]; u = j; }
            in[u] = true; total += best;
            for (int v = 0; v < n; v++) if (!in[v]) {
                int d = Math.abs(p[u][0]-p[v][0]) + Math.abs(p[u][1]-p[v][1]);
                if (d < key[v]) key[v] = d;
            }
        }
        return total;
    }
}
Prim's with min-heap O(V²·logV) O(V²)

Pop lightest cross edge; expand frontier.

Pseudo-code

heap.push((0,0)); while not all visited: pop (w,u); if seen skip; total+=w; push edges to unseen

Complexity

Time: O(V²·logV) — Heap ops.
Space: O(V²) — Heap.

Java code

import java.util.*;
class Solution {
    public int minCostConnectPoints(int[][] p) {
        int n = p.length;
        boolean[] in = new boolean[n];
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);
        pq.offer(new int[]{0, 0});
        int total = 0, taken = 0;
        while (taken < n) {
            int[] cur = pq.poll();
            int w = cur[0], u = cur[1];
            if (in[u]) continue;
            in[u] = true; total += w; taken++;
            for (int v = 0; v < n; v++) if (!in[v]) {
                int d = Math.abs(p[u][0]-p[v][0]) + Math.abs(p[u][1]-p[v][1]);
                pq.offer(new int[]{d, v});
            }
        }
        return total;
    }
}
Kruskal's with DSU O(V²·logV) O(V²)

Sort all V² edges; union; stop at V-1 edges.

Pseudo-code

build all edges; sort; for each: if union: total+=w

Complexity

Time: O(V²·logV) — Sort.
Space: O(V²) — Edges + DSU.

Java code

import java.util.*;
class Solution {
    private int[] par, sz;
    private int find(int x) { while (par[x] != x) { par[x] = par[par[x]]; x = par[x]; } return x; }
    private boolean union(int a, int b) {
        int ra = find(a), rb = find(b); if (ra == rb) return false;
        if (sz[ra] < sz[rb]) { par[ra] = rb; sz[rb] += sz[ra]; } else { par[rb] = ra; sz[ra] += sz[rb]; }
        return true;
    }
    public int minCostConnectPoints(int[][] p) {
        int n = p.length;
        List<int[]> edges = new ArrayList<>();
        for (int i = 0; i < n; i++) for (int j = i + 1; j < n; j++)
            edges.add(new int[]{Math.abs(p[i][0]-p[j][0]) + Math.abs(p[i][1]-p[j][1]), i, j});
        edges.sort((a, b) -> a[0] - b[0]);
        par = new int[n]; sz = new int[n];
        for (int i = 0; i < n; i++) { par[i] = i; sz[i] = 1; }
        int total = 0, taken = 0;
        for (int[] e : edges) {
            if (taken == n - 1) break;
            if (union(e[1], e[2])) { total += e[0]; taken++; }
        }
        return total;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(V²)O(V²·logV)
SpaceO(V)O(V²)
Difficulty3/54/5
WhenDense defaultSparse natural
PE VerdictPrim's with min-heap (Kruskal's also fine).

Python Solutions

Prim's O(V²) O(V²) O(V)

Same.

Pseudo-code

see Java

Complexity

Time: O(V²) — Scan each iter.
Space: O(V) — key.

Python code

from typing import List
class Solution:
    def minCostConnectPoints(self, p: List[List[int]]) -> int:
        n = len(p)
        INF = float('inf')
        key = [INF]*n; in_ = [False]*n; key[0] = 0; total = 0
        for _ in range(n):
            u, best = -1, INF
            for j in range(n):
                if not in_[j] and key[j] < best: best = key[j]; u = j
            in_[u] = True; total += best
            for v in range(n):
                if not in_[v]:
                    d = abs(p[u][0]-p[v][0]) + abs(p[u][1]-p[v][1])
                    if d < key[v]: key[v] = d
        return total
Prim's with heap O(V²·logV) O(V²)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(V²·logV) — Heap.
Space: O(V²) — Heap.

Python code

from typing import List
import heapq
class Solution:
    def minCostConnectPoints(self, p: List[List[int]]) -> int:
        n = len(p); in_ = [False]*n
        h = [(0, 0)]; total = 0; taken = 0
        while taken < n:
            w, u = heapq.heappop(h)
            if in_[u]: continue
            in_[u] = True; total += w; taken += 1
            for v in range(n):
                if not in_[v]:
                    d = abs(p[u][0]-p[v][0]) + abs(p[u][1]-p[v][1])
                    heapq.heappush(h, (d, v))
        return total
Kruskal's O(V²·logV) O(V²)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(V²·logV) — Sort.
Space: O(V²) — Edges + DSU.

Python code

from typing import List
class Solution:
    def minCostConnectPoints(self, p: List[List[int]]) -> int:
        n = len(p)
        edges = []
        for i in range(n):
            for j in range(i+1, n):
                edges.append((abs(p[i][0]-p[j][0]) + abs(p[i][1]-p[j][1]), i, j))
        edges.sort()
        par = list(range(n)); sz = [1]*n
        def find(x):
            while par[x] != x: par[x] = par[par[x]]; x = par[x]
            return x
        def union(a, b):
            ra, rb = find(a), find(b)
            if ra == rb: return False
            if sz[ra] < sz[rb]: par[ra] = rb; sz[rb] += sz[ra]
            else: par[rb] = ra; sz[ra] += sz[rb]
            return True
        total = 0; taken = 0
        for w, i, j in edges:
            if taken == n - 1: break
            if union(i, j): total += w; taken += 1
        return total

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(V²)O(V²·logV)
SpaceO(V)O(V²)
Difficulty3/54/5
WhenDenseSparse natural
PE VerdictPrim's with heap (Kruskal's equivalent).

What the interviewer is really testing

MST on complete graph.

Top gotchas

  • Graph is implicit: every pair is an edge → V² edges.
  • Prim's O(V²) actually faster than O(V²logV) heap on this since V²edges.
  • Stop Kruskal's once V-1 edges chosen.

Ship-it

Prim's with min-heap.

#96 LC 778 Hard Swim in Rising Water

Minimize MAX elevation on path — Dijkstra (heap) or binary search + BFS.

Problem Statement

n×n grid of elevations. Time t lets you swim into any cell with elevation ≤ t. Return min time to reach (n-1,n-1) from (0,0).

Signature: int swimInWater(int[][] grid)

Examples

[[0,2],[1,3]] → 3

Constraints

  • 1 <= n <= 50

Approach Overview

Brute Force

Java: Binary search + BFS

Python: Binary search + BFS

O(n²·log(n²)) O(n²)

Optimal #1

Java: Dijkstra (min-heap)

Python: Dijkstra

O(n²·logn) O(n²)

Optimal #2

Java: Union-Find by elevation

Python: Union-Find by elevation

O(n²·α) O(n²)

Java Solutions

Binary search + BFS O(n²·log(n²)) O(n²)

Try t in [0..n²-1]; BFS to see if reachable.

Pseudo-code

lo=grid[0][0]; hi=n²-1; binsearch smallest t s.t. BFS reaches end

Complexity

Time: O(n²·log(n²)) — Each test linear.
Space: O(n²) — Visited.

Edge cases & gotchas

  • 1x1 → grid[0][0].

Java code

import java.util.*;
class Solution {
    public int swimInWater(int[][] g) {
        int n = g.length;
        int lo = g[0][0], hi = n * n - 1;
        while (lo < hi) {
            int mid = (lo + hi) >>> 1;
            if (reachable(g, mid)) hi = mid; else lo = mid + 1;
        }
        return lo;
    }
    private boolean reachable(int[][] g, int t) {
        if (g[0][0] > t) return false;
        int n = g.length;
        boolean[][] v = new boolean[n][n]; v[0][0] = true;
        Deque<int[]> q = new ArrayDeque<>(); q.offer(new int[]{0,0});
        int[][] D = {{-1,0},{1,0},{0,-1},{0,1}};
        while (!q.isEmpty()) {
            int[] p = q.poll();
            if (p[0] == n-1 && p[1] == n-1) return true;
            for (int[] d : D) {
                int ni = p[0]+d[0], nj = p[1]+d[1];
                if (ni>=0&&nj>=0&&ni<n&&nj<n&&!v[ni][nj]&&g[ni][nj]<=t) { v[ni][nj]=true; q.offer(new int[]{ni,nj}); }
            }
        }
        return false;
    }
}
Dijkstra (min-heap) O(n²·logn) O(n²)

State = max elevation seen on path; pop smallest.

Pseudo-code

heap.push((g[0][0],0,0)); pop (t,i,j); for each nb push (max(t,g[nb]),nb)

Complexity

Time: O(n²·logn) — Each cell once.
Space: O(n²) — Heap.

Java code

import java.util.*;
class Solution {
    public int swimInWater(int[][] g) {
        int n = g.length;
        boolean[][] v = new boolean[n][n];
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);
        pq.offer(new int[]{g[0][0], 0, 0});
        int[][] D = {{-1,0},{1,0},{0,-1},{0,1}};
        while (!pq.isEmpty()) {
            int[] p = pq.poll();
            int t = p[0], i = p[1], j = p[2];
            if (v[i][j]) continue;
            v[i][j] = true;
            if (i == n-1 && j == n-1) return t;
            for (int[] d : D) {
                int ni = i+d[0], nj = j+d[1];
                if (ni>=0&&nj>=0&&ni<n&&nj<n&&!v[ni][nj])
                    pq.offer(new int[]{Math.max(t, g[ni][nj]), ni, nj});
            }
        }
        return -1;
    }
}
Union-Find by elevation O(n²·α) O(n²)

Add cells in elevation order; check when (0,0) and (n-1,n-1) connect.

Pseudo-code

sort cells by elevation; activate one at a time; union with active neighbors; stop when both endpoints connected

Complexity

Time: O(n²·α) — Near-linear after sort.
Space: O(n²) — DSU.

Java code

class Solution {
    private int[] p, sz;
    private int find(int x) { while (p[x] != x) { p[x] = p[p[x]]; x = p[x]; } return x; }
    private void union(int a, int b) {
        int ra = find(a), rb = find(b); if (ra == rb) return;
        if (sz[ra] < sz[rb]) { p[ra] = rb; sz[rb] += sz[ra]; } else { p[rb] = ra; sz[ra] += sz[rb]; }
    }
    public int swimInWater(int[][] g) {
        int n = g.length;
        int[] cells = new int[n*n];
        for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) cells[g[i][j]] = i*n + j;
        p = new int[n*n]; sz = new int[n*n];
        for (int i = 0; i < n*n; i++) { p[i] = i; sz[i] = 1; }
        boolean[] active = new boolean[n*n];
        int[][] D = {{-1,0},{1,0},{0,-1},{0,1}};
        for (int t = 0; t < n*n; t++) {
            int idx = cells[t];
            int i = idx / n, j = idx % n;
            active[idx] = true;
            for (int[] d : D) {
                int ni = i+d[0], nj = j+d[1];
                if (ni>=0&&nj>=0&&ni<n&&nj<n&&active[ni*n+nj]) union(idx, ni*n+nj);
            }
            if (find(0) == find(n*n - 1)) return t;
        }
        return -1;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²·log(n²))O(n²·α)
SpaceO(n²)O(n²)
Difficulty4/54/5
WhenSimple to reasonElegant alt
PE VerdictDijkstra over min-max paths.

Python Solutions

Binary search + BFS O(n²·log(n²)) O(n²)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²·log(n²)) — Each test linear.
Space: O(n²) — Visited.

Python code

from typing import List
from collections import deque
class Solution:
    def swimInWater(self, g: List[List[int]]) -> int:
        n = len(g)
        def ok(t):
            if g[0][0] > t: return False
            v = [[False]*n for _ in range(n)]; v[0][0] = True
            q = deque([(0,0)])
            while q:
                i, j = q.popleft()
                if (i, j) == (n-1, n-1): return True
                for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
                    ni, nj = i+dr, j+dc
                    if 0<=ni<n and 0<=nj<n and not v[ni][nj] and g[ni][nj] <= t:
                        v[ni][nj] = True; q.append((ni, nj))
            return False
        lo, hi = g[0][0], n*n - 1
        while lo < hi:
            mid = (lo + hi) // 2
            if ok(mid): hi = mid
            else: lo = mid + 1
        return lo
Dijkstra O(n²·logn) O(n²)

Min-max path with heap.

Pseudo-code

see Java

Complexity

Time: O(n²·logn) — Each cell once.
Space: O(n²) — Heap.

Python code

from typing import List
import heapq
class Solution:
    def swimInWater(self, g: List[List[int]]) -> int:
        n = len(g)
        v = [[False]*n for _ in range(n)]
        h = [(g[0][0], 0, 0)]
        while h:
            t, i, j = heapq.heappop(h)
            if v[i][j]: continue
            v[i][j] = True
            if (i, j) == (n-1, n-1): return t
            for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
                ni, nj = i+dr, j+dc
                if 0<=ni<n and 0<=nj<n and not v[ni][nj]:
                    heapq.heappush(h, (max(t, g[ni][nj]), ni, nj))
        return -1
Union-Find by elevation O(n²·α) O(n²)

Activate cells in order.

Pseudo-code

see Java

Complexity

Time: O(n²·α) — Near-linear.
Space: O(n²) — DSU.

Python code

from typing import List
class Solution:
    def swimInWater(self, g: List[List[int]]) -> int:
        n = len(g)
        cells = [0]*(n*n)
        for i in range(n):
            for j in range(n): cells[g[i][j]] = i*n + j
        p = list(range(n*n)); sz = [1]*(n*n); active = [False]*(n*n)
        def find(x):
            while p[x] != x: p[x] = p[p[x]]; x = p[x]
            return x
        def union(a, b):
            ra, rb = find(a), find(b)
            if ra == rb: return
            if sz[ra] < sz[rb]: p[ra] = rb; sz[rb] += sz[ra]
            else: p[rb] = ra; sz[ra] += sz[rb]
        for t in range(n*n):
            idx = cells[t]; i, j = divmod(idx, n)
            active[idx] = True
            for dr, dc in [(-1,0),(1,0),(0,-1),(0,1)]:
                ni, nj = i+dr, j+dc
                if 0<=ni<n and 0<=nj<n and active[ni*n+nj]: union(idx, ni*n+nj)
            if find(0) == find(n*n - 1): return t
        return -1

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²·log(n²))O(n²·α)
SpaceO(n²)O(n²)
Difficulty4/54/5
WhenEasy to explainElegant alt
PE VerdictDijkstra (modified — min of max).

What the interviewer is really testing

Min-max path → Dijkstra variant.

Top gotchas

  • Cost of an edge is the MAX of cell values, not the sum.
  • Heap state is (max_so_far, i, j).
  • Union-Find by elevation is a beautiful alternative — activate cells in elevation order.

Ship-it

Dijkstra with max-elevation key.

#97 LC 269 Hard Alien Dictionary

Topological sort over characters — extract precedence from adjacent word diffs.

Problem Statement

Given sorted (alien-lex) words, return any order of the alphabet consistent with them, or "" if invalid.

Signature: String alienOrder(String[] words)

Examples

[wrt, wrf, er, ett, rftt] → wertf

Constraints

  • 1 <= words.length <= 100
  • 1 <= words[i].length <= 100

Approach Overview

Brute Force

Java: DFS topo with cycle detection

Python: DFS topo

O(C) O(1) alphabet

Optimal #1

Java: Kahn's BFS

Python: Kahn's BFS

O(C) O(1)

Optimal #2

Java: Kahn's with array indegrees

Python: Kahn's array

O(C) O(1)

Java Solutions

DFS topo with cycle detection O(C) O(1) alphabet

Build graph, DFS 3-color, post-order.

Pseudo-code

build edges; for each char DFS; emit post-order; reverse

Complexity

Time: O(C) — Linear in total chars.
Space: O(1) alphabet — Adj.

Edge cases & gotchas

  • Prefix issue: ['abc','ab'] → invalid.

Java code

import java.util.*;
class Solution {
    public String alienOrder(String[] words) {
        Map<Character, Set<Character>> g = new HashMap<>();
        for (String w : words) for (char c : w.toCharArray()) g.putIfAbsent(c, new HashSet<>());
        for (int i = 0; i < words.length - 1; i++) {
            String a = words[i], b = words[i+1];
            if (a.length() > b.length() && a.startsWith(b)) return "";
            for (int k = 0; k < Math.min(a.length(), b.length()); k++) {
                if (a.charAt(k) != b.charAt(k)) { g.get(a.charAt(k)).add(b.charAt(k)); break; }
            }
        }
        Map<Character, Integer> color = new HashMap<>();
        StringBuilder sb = new StringBuilder();
        for (char c : g.keySet()) if (!color.containsKey(c)) if (!dfs(c, g, color, sb)) return "";
        return sb.reverse().toString();
    }
    private boolean dfs(char u, Map<Character, Set<Character>> g, Map<Character, Integer> color, StringBuilder sb) {
        color.put(u, 1);
        for (char v : g.get(u)) {
            if (color.getOrDefault(v, 0) == 1) return false;
            if (color.getOrDefault(v, 0) == 0 && !dfs(v, g, color, sb)) return false;
        }
        color.put(u, 2); sb.append(u); return true;
    }
}
Kahn's BFS O(C) O(1)

Topo via indegree.

Pseudo-code

build graph + indeg; enqueue indeg=0 chars; pop, append, decrement neighbors

Complexity

Time: O(C) — Linear.
Space: O(1) — Adj.

Edge cases & gotchas

  • Same prefix invalid.

Java code

import java.util.*;
class Solution {
    public String alienOrder(String[] words) {
        Map<Character, Set<Character>> g = new HashMap<>();
        Map<Character, Integer> indeg = new HashMap<>();
        for (String w : words) for (char c : w.toCharArray()) { g.putIfAbsent(c, new HashSet<>()); indeg.putIfAbsent(c, 0); }
        for (int i = 0; i < words.length - 1; i++) {
            String a = words[i], b = words[i+1];
            if (a.length() > b.length() && a.startsWith(b)) return "";
            for (int k = 0; k < Math.min(a.length(), b.length()); k++) {
                char ca = a.charAt(k), cb = b.charAt(k);
                if (ca != cb) {
                    if (g.get(ca).add(cb)) indeg.merge(cb, 1, Integer::sum);
                    break;
                }
            }
        }
        Deque<Character> q = new ArrayDeque<>();
        for (Map.Entry<Character, Integer> e : indeg.entrySet()) if (e.getValue() == 0) q.offer(e.getKey());
        StringBuilder sb = new StringBuilder();
        while (!q.isEmpty()) {
            char u = q.poll(); sb.append(u);
            for (char v : g.get(u)) if (indeg.merge(v, -1, Integer::sum) == 0) q.offer(v);
        }
        return sb.length() == indeg.size() ? sb.toString() : "";
    }
}
Kahn's with array indegrees O(C) O(1)

Avoid HashMap overhead by fixed 26 array.

Pseudo-code

see opt1 with int[26]

Complexity

Time: O(C) — Linear.
Space: O(1) — Arrays.

Java code

import java.util.*;
class Solution {
    public String alienOrder(String[] words) {
        Set<Character>[] g = new HashSet[26];
        int[] indeg = new int[26];
        boolean[] present = new boolean[26];
        for (int i = 0; i < 26; i++) g[i] = new HashSet<>();
        for (String w : words) for (char c : w.toCharArray()) present[c - 'a'] = true;
        for (int i = 0; i < words.length - 1; i++) {
            String a = words[i], b = words[i+1];
            if (a.length() > b.length() && a.startsWith(b)) return "";
            for (int k = 0; k < Math.min(a.length(), b.length()); k++) {
                char ca = a.charAt(k), cb = b.charAt(k);
                if (ca != cb) { if (g[ca-'a'].add(cb)) indeg[cb-'a']++; break; }
            }
        }
        Deque<Integer> q = new ArrayDeque<>();
        int total = 0;
        for (int i = 0; i < 26; i++) if (present[i]) { total++; if (indeg[i] == 0) q.offer(i); }
        StringBuilder sb = new StringBuilder();
        while (!q.isEmpty()) {
            int u = q.poll(); sb.append((char)('a' + u));
            for (char v : g[u]) if (--indeg[v - 'a'] == 0) q.offer(v - 'a');
        }
        return sb.length() == total ? sb.toString() : "";
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(C)O(C)
SpaceO(1)O(1)
Difficulty4/54/5
WhenRecursion okTight perf
PE VerdictKahn's BFS with hashmap for clarity.

Python Solutions

DFS topo O(C) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(C) — Linear.
Space: O(1) — Adj.

Python code

from typing import List
from collections import defaultdict
class Solution:
    def alienOrder(self, words: List[str]) -> str:
        g = defaultdict(set); chars = set()
        for w in words: chars.update(w)
        for a, b in zip(words, words[1:]):
            if len(a) > len(b) and a.startswith(b): return ""
            for x, y in zip(a, b):
                if x != y: g[x].add(y); break
        color = {}; out = []
        def dfs(u):
            color[u] = 1
            for v in g[u]:
                if color.get(v, 0) == 1: return False
                if color.get(v, 0) == 0 and not dfs(v): return False
            color[u] = 2; out.append(u); return True
        for c in chars:
            if c not in color and not dfs(c): return ""
        return ''.join(reversed(out))
Kahn's BFS O(C) O(1)

Indegree topo.

Pseudo-code

see Java

Complexity

Time: O(C) — Linear.
Space: O(1) — Adj.

Python code

from typing import List
from collections import defaultdict, deque
class Solution:
    def alienOrder(self, words: List[str]) -> str:
        g = defaultdict(set); indeg = {c: 0 for w in words for c in w}
        for a, b in zip(words, words[1:]):
            if len(a) > len(b) and a.startswith(b): return ""
            for x, y in zip(a, b):
                if x != y:
                    if y not in g[x]: g[x].add(y); indeg[y] += 1
                    break
        q = deque(c for c in indeg if indeg[c] == 0); out = []
        while q:
            u = q.popleft(); out.append(u)
            for v in g[u]:
                indeg[v] -= 1
                if indeg[v] == 0: q.append(v)
        return ''.join(out) if len(out) == len(indeg) else ""
Kahn's array O(C) O(1)

Fixed 26 arrays.

Pseudo-code

see Java

Complexity

Time: O(C) — Linear.
Space: O(1) — Arrays.

Python code

from typing import List
from collections import deque
class Solution:
    def alienOrder(self, words: List[str]) -> str:
        g = [set() for _ in range(26)]; indeg = [0]*26; present = [False]*26
        for w in words:
            for c in w: present[ord(c)-97] = True
        for a, b in zip(words, words[1:]):
            if len(a) > len(b) and a.startswith(b): return ""
            for x, y in zip(a, b):
                if x != y:
                    xi, yi = ord(x)-97, ord(y)-97
                    if yi not in g[xi]: g[xi].add(yi); indeg[yi] += 1
                    break
        total = sum(present)
        q = deque(i for i in range(26) if present[i] and indeg[i] == 0)
        out = []
        while q:
            u = q.popleft(); out.append(chr(u+97))
            for v in g[u]:
                indeg[v] -= 1
                if indeg[v] == 0: q.append(v)
        return ''.join(out) if len(out) == total else ""

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(C)O(C)
SpaceO(1)O(1)
Difficulty4/54/5
WhenRecursionTight
PE VerdictKahn's BFS.

What the interviewer is really testing

Topo sort with edge inference from sorted strings.

Top gotchas

  • EDGE CASE: ['abc', 'ab'] is INVALID — a longer prefix can't come before its prefix.
  • Add chars to indeg even if no constraint involves them.
  • Only the FIRST differing char between adjacent words yields a precedence.

Ship-it

Kahn's BFS over character DAG.

#98 LC 787 Medium Cheapest Flights Within K Stops

Bellman-Ford limited to k+1 relaxations — naturally caps stops.

Problem Statement

n cities, flights[i]=[from,to,price]. Cheapest from src to dst with at most k stops, or -1.

Signature: int findCheapestPrice(int n, int[][] flights, int src, int dst, int k)

Examples

n=3, flights=[[0,1,100],[1,2,100],[0,2,500]], src=0, dst=2, k=1 → 200

Constraints

  • 1 <= n <= 100
  • 0 <= flights.length <= n*(n-1)/2

Approach Overview

Brute Force

Java: DFS with stop limit

Python: DFS with hop limit

O(n^k) O(n+k)

Optimal #1

Java: Bellman-Ford k+1 passes

Python: Bellman-Ford k+1

O(k·E) O(n)

Optimal #2

Java: Dijkstra with stop count

Python: Dijkstra with stops

O(E·k·log(V·k)) O(V·k)

Java Solutions

DFS with stop limit O(n^k) O(n+k)

Try every path within k stops; min cost.

Pseudo-code

dfs(u, stops_left, cost): if u==dst track; for nb: recurse if stops_left>=0

Complexity

Time: O(n^k) — Exponential.
Space: O(n+k) — Recursion.

Edge cases & gotchas

  • src==dst → 0.

Java code

import java.util.*;
class Solution {
    private int best;
    public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
        Map<Integer, List<int[]>> g = new HashMap<>();
        for (int[] f : flights) g.computeIfAbsent(f[0], x -> new ArrayList<>()).add(new int[]{f[1], f[2]});
        best = Integer.MAX_VALUE;
        dfs(src, dst, k + 1, 0, g);
        return best == Integer.MAX_VALUE ? -1 : best;
    }
    private void dfs(int u, int dst, int hops, int cost, Map<Integer, List<int[]>> g) {
        if (u == dst) { best = Math.min(best, cost); return; }
        if (hops == 0 || cost >= best) return;
        for (int[] nb : g.getOrDefault(u, new ArrayList<>())) dfs(nb[0], dst, hops - 1, cost + nb[1], g);
    }
}
Bellman-Ford k+1 passes O(k·E) O(n)

Relax with edges array; snapshot prev distances each round.

Pseudo-code

dist = [INF]; dist[src]=0; repeat k+1 times: prev=dist; for each edge relax using prev

Complexity

Time: O(k·E) — k iterations.
Space: O(n) — Two dist arrays.

Edge cases & gotchas

  • k=0 means direct only.

Java code

import java.util.*;
class Solution {
    public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
        int[] dist = new int[n];
        Arrays.fill(dist, Integer.MAX_VALUE);
        dist[src] = 0;
        for (int i = 0; i <= k; i++) {
            int[] prev = dist.clone();
            for (int[] f : flights) {
                if (prev[f[0]] != Integer.MAX_VALUE && prev[f[0]] + f[2] < dist[f[1]])
                    dist[f[1]] = prev[f[0]] + f[2];
            }
        }
        return dist[dst] == Integer.MAX_VALUE ? -1 : dist[dst];
    }
}
Dijkstra with stop count O(E·k·log(V·k)) O(V·k)

Heap of (cost, node, stops_used); skip if exceeds k.

Pseudo-code

heap.push((0,src,0)); pop (c,u,s); if u==dst return; if s<=k: push neighbors

Complexity

Time: O(E·k·log(V·k)) — More states.
Space: O(V·k) — Visited table by (node, stops).

Java code

import java.util.*;
class Solution {
    public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
        Map<Integer, List<int[]>> g = new HashMap<>();
        for (int[] f : flights) g.computeIfAbsent(f[0], x -> new ArrayList<>()).add(new int[]{f[1], f[2]});
        int[][] best = new int[n][k + 2];
        for (int[] row : best) Arrays.fill(row, Integer.MAX_VALUE);
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);
        pq.offer(new int[]{0, src, 0});
        while (!pq.isEmpty()) {
            int[] p = pq.poll();
            int c = p[0], u = p[1], s = p[2];
            if (u == dst) return c;
            if (s > k) continue;
            for (int[] nb : g.getOrDefault(u, new ArrayList<>())) {
                int nc = c + nb[1];
                if (nc < best[nb[0]][s + 1]) { best[nb[0]][s + 1] = nc; pq.offer(new int[]{nc, nb[0], s + 1}); }
            }
        }
        return -1;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n^k)O(E·k·log(V·k))
SpaceO(n+k)O(V·k)
Difficulty3/55/5
WhenTinySparse + small k
PE VerdictBellman-Ford limited to k+1 relaxations.

Python Solutions

DFS with hop limit O(n^k) O(n+k)

Same.

Pseudo-code

see Java

Complexity

Time: O(n^k) — Exp.
Space: O(n+k) — Recursion.

Python code

from typing import List
from collections import defaultdict
class Solution:
    def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int:
        g = defaultdict(list)
        for u, v, w in flights: g[u].append((v, w))
        best = float('inf')
        def dfs(u, hops, cost):
            nonlocal best
            if u == dst: best = min(best, cost); return
            if hops == 0 or cost >= best: return
            for v, w in g[u]: dfs(v, hops-1, cost+w)
        dfs(src, k+1, 0)
        return -1 if best == float('inf') else best
Bellman-Ford k+1 O(k·E) O(n)

Snapshot dist each round.

Pseudo-code

see Java

Complexity

Time: O(k·E) — k iters.
Space: O(n) — Two arrays.

Python code

from typing import List
class Solution:
    def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int:
        INF = float('inf')
        dist = [INF]*n; dist[src] = 0
        for _ in range(k + 1):
            prev = dist[:]
            for u, v, w in flights:
                if prev[u] + w < dist[v]: dist[v] = prev[u] + w
        return -1 if dist[dst] == INF else dist[dst]
Dijkstra with stops O(E·k·log(V·k)) O(V·k)

State = (cost, node, stops).

Pseudo-code

see Java

Complexity

Time: O(E·k·log(V·k)) — More states.
Space: O(V·k) — Best table.

Python code

from typing import List
import heapq
from collections import defaultdict
class Solution:
    def findCheapestPrice(self, n: int, flights: List[List[int]], src: int, dst: int, k: int) -> int:
        g = defaultdict(list)
        for u, v, w in flights: g[u].append((v, w))
        h = [(0, src, 0)]
        best = {}
        while h:
            c, u, s = heapq.heappop(h)
            if u == dst: return c
            if s > k: continue
            for v, w in g[u]:
                nc = c + w
                if best.get((v, s+1), float('inf')) > nc:
                    best[(v, s+1)] = nc
                    heapq.heappush(h, (nc, v, s+1))
        return -1

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n^k)O(E·k·log(V·k))
SpaceO(n+k)O(V·k)
Difficulty3/55/5
WhenTinySparse
PE VerdictBellman-Ford with k+1 rounds.

What the interviewer is really testing

Bellman-Ford fits naturally: each round = one extra hop.

Top gotchas

  • MUST snapshot prev distances — else you can take >k edges in one round.
  • 'k stops' = k+1 edges.
  • Plain Dijkstra fails: cheaper-to-cur path may use too many stops; you need stops in the state.

Ship-it

Bellman-Ford with snapshot.

1-D Dynamic Programming

#99 LC 70 Easy Climbing Stairs

Fibonacci in disguise — f(n)=f(n-1)+f(n-2).

Problem Statement

n stairs, 1 or 2 steps at a time. Count distinct ways to reach the top.

Signature: int climbStairs(int n)

Examples

n=3 → 3 (1+1+1, 1+2, 2+1)

Constraints

  • 1 <= n <= 45

Approach Overview

Brute Force

Java: Naive recursion

Python: Recursion

O(2^n) O(n)

Optimal #1

Java: Bottom-up O(1) space

Python: Rolling O(1)

O(n) O(1)

Optimal #2

Java: Matrix exponentiation

Python: Matrix expo

O(log n) O(1)

Java Solutions

Naive recursion O(2^n) O(n)

Exponential.

Pseudo-code

f(n) = f(n-1) + f(n-2)

Complexity

Time: O(2^n) — Tree.
Space: O(n) — Stack.

Edge cases & gotchas

  • n=1→1, n=2→2.

Java code

class Solution {
    public int climbStairs(int n) {
        if (n <= 2) return n;
        return climbStairs(n - 1) + climbStairs(n - 2);
    }
}
Bottom-up O(1) space O(n) O(1)

Two rolling vars.

Pseudo-code

a=1,b=2; for i in 3..n: c=a+b; a=b; b=c

Complexity

Time: O(n) — One pass.
Space: O(1) — Two ints.

Java code

class Solution {
    public int climbStairs(int n) {
        if (n <= 2) return n;
        int a = 1, b = 2;
        for (int i = 3; i <= n; i++) { int c = a + b; a = b; b = c; }
        return b;
    }
}
Matrix exponentiation O(log n) O(1)

O(log n) for huge n.

Pseudo-code

[[1,1],[1,0]]^n → top-left = f(n+1)

Complexity

Time: O(log n) — Power matrix.
Space: O(1) — Constants.

Java code

class Solution {
    public int climbStairs(int n) {
        long[][] M = {{1,1},{1,0}};
        long[][] R = pow(M, n);
        return (int) R[0][0];
    }
    private long[][] pow(long[][] m, int p) {
        long[][] r = {{1,0},{0,1}};
        while (p > 0) { if ((p & 1) == 1) r = mul(r, m); m = mul(m, m); p >>= 1; }
        return r;
    }
    private long[][] mul(long[][] a, long[][] b) {
        return new long[][]{{a[0][0]*b[0][0]+a[0][1]*b[1][0], a[0][0]*b[0][1]+a[0][1]*b[1][1]},
                            {a[1][0]*b[0][0]+a[1][1]*b[1][0], a[1][0]*b[0][1]+a[1][1]*b[1][1]}};
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(log n)
SpaceO(n)O(1)
Difficulty1/54/5
WhenDon'tHuge n only
PE VerdictTwo rolling vars.

Python Solutions

Recursion O(2^n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(2^n) — Tree.
Space: O(n) — Stack.

Python code

class Solution:
    def climbStairs(self, n: int) -> int:
        if n <= 2: return n
        return self.climbStairs(n-1) + self.climbStairs(n-2)
Rolling O(1) O(n) O(1)

Two ints.

Pseudo-code

see Java

Complexity

Time: O(n) — One pass.
Space: O(1) — Two ints.

Python code

class Solution:
    def climbStairs(self, n: int) -> int:
        if n <= 2: return n
        a, b = 1, 2
        for _ in range(3, n+1): a, b = b, a + b
        return b
Matrix expo O(log n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(log n) — Power.
Space: O(1) — Const.

Python code

class Solution:
    def climbStairs(self, n: int) -> int:
        def mul(a, b):
            return [[a[0][0]*b[0][0]+a[0][1]*b[1][0], a[0][0]*b[0][1]+a[0][1]*b[1][1]],
                    [a[1][0]*b[0][0]+a[1][1]*b[1][0], a[1][0]*b[0][1]+a[1][1]*b[1][1]]]
        def power(m, p):
            r = [[1,0],[0,1]]
            while p:
                if p & 1: r = mul(r, m)
                m = mul(m, m); p >>= 1
            return r
        return power([[1,1],[1,0]], n)[0][0]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(log n)
SpaceO(n)O(1)
Difficulty1/54/5
WhenDon'tHuge n
PE VerdictRolling O(1).

What the interviewer is really testing

DP intro.

Top gotchas

  • Base cases n=1→1, n=2→2.
  • Naive recursion TLE at n=45.
  • Matrix expo is overkill but worth mentioning.

Ship-it

Two rolling vars.

#100 LC 746 Easy Min Cost Climbing Stairs

DP: cost to land at step i = cost[i] + min(prev, prev2).

Problem Statement

Pay cost[i] to step on stair i, then step 1 or 2. Start at 0 or 1. Min total cost to reach the top (beyond last).

Signature: int minCostClimbingStairs(int[] cost)

Examples

[10,15,20] → 15
[1,100,1,1,1,100,1,1,100,1] → 6

Constraints

  • 2 <= n <= 1000

Approach Overview

Brute Force

Java: Recursion

Python: Recursion

O(2^n) O(n)

Optimal #1

Java: Bottom-up O(1)

Python: Rolling O(1)

O(n) O(1)

Optimal #2

Java: DP array

Python: DP array

O(n) O(n)

Java Solutions

Recursion O(2^n) O(n)

Try every reachable step.

Pseudo-code

minCost(i)=cost[i]+min(minCost(i-1),minCost(i-2))

Complexity

Time: O(2^n) — Tree.
Space: O(n) — Stack.

Java code

class Solution {
    public int minCostClimbingStairs(int[] cost) {
        return Math.min(rec(cost, cost.length - 1), rec(cost, cost.length - 2));
    }
    private int rec(int[] c, int i) {
        if (i < 0) return 0;
        if (i <= 1) return c[i];
        return c[i] + Math.min(rec(c, i-1), rec(c, i-2));
    }
}
Bottom-up O(1) O(n) O(1)

Two rolling vars.

Pseudo-code

a=cost[0]; b=cost[1]; for i=2..n-1: c=cost[i]+min(a,b); shift; return min(a,b)

Complexity

Time: O(n) — Single pass.
Space: O(1) — Two ints.

Java code

class Solution {
    public int minCostClimbingStairs(int[] cost) {
        int n = cost.length;
        int a = cost[0], b = cost[1];
        for (int i = 2; i < n; i++) {
            int c = cost[i] + Math.min(a, b);
            a = b; b = c;
        }
        return Math.min(a, b);
    }
}
DP array O(n) O(n)

Explicit dp[].

Pseudo-code

dp[i] = cost[i] + min(dp[i-1], dp[i-2])

Complexity

Time: O(n) — Linear.
Space: O(n) — Array.

Java code

class Solution {
    public int minCostClimbingStairs(int[] cost) {
        int n = cost.length;
        int[] dp = new int[n];
        dp[0] = cost[0]; dp[1] = cost[1];
        for (int i = 2; i < n; i++) dp[i] = cost[i] + Math.min(dp[i-1], dp[i-2]);
        return Math.min(dp[n-1], dp[n-2]);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n)
SpaceO(n)O(n)
Difficulty1/52/5
WhenDon'tPedagogical
PE VerdictRolling O(1).

Python Solutions

Recursion O(2^n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(2^n) — Tree.
Space: O(n) — Stack.

Python code

from typing import List
class Solution:
    def minCostClimbingStairs(self, cost: List[int]) -> int:
        def rec(i):
            if i < 0: return 0
            if i <= 1: return cost[i]
            return cost[i] + min(rec(i-1), rec(i-2))
        n = len(cost)
        return min(rec(n-1), rec(n-2))
Rolling O(1) O(n) O(1)

Two ints.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — Two ints.

Python code

from typing import List
class Solution:
    def minCostClimbingStairs(self, cost: List[int]) -> int:
        a, b = cost[0], cost[1]
        for i in range(2, len(cost)):
            a, b = b, cost[i] + min(a, b)
        return min(a, b)
DP array O(n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(n) — Array.

Python code

from typing import List
class Solution:
    def minCostClimbingStairs(self, cost: List[int]) -> int:
        n = len(cost); dp = cost[:]
        for i in range(2, n): dp[i] = cost[i] + min(dp[i-1], dp[i-2])
        return min(dp[-1], dp[-2])

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n)
SpaceO(n)O(n)
Difficulty1/52/5
WhenDon'tPedagogical
PE VerdictRolling O(1).

What the interviewer is really testing

Variant of climbing stairs.

Top gotchas

  • You pay cost[i] to STEP ON i, then move from i.
  • Goal is BEYOND last index — answer is min(dp[n-1], dp[n-2]).
  • Start can be at 0 OR 1 — handle both base cases.

Ship-it

Rolling DP.

#101 LC 198 Medium House Robber

Max non-adjacent sum — dp[i] = max(dp[i-1], dp[i-2] + nums[i]).

Problem Statement

Cannot rob two adjacent houses. Max amount.

Signature: int rob(int[] nums)

Examples

[1,2,3,1] → 4
[2,7,9,3,1] → 12

Constraints

  • 1 <= n <= 100

Approach Overview

Brute Force

Java: Recursion

Python: Recursion

O(2^n) O(n)

Optimal #1

Java: Bottom-up O(1)

Python: Rolling O(1)

O(n) O(1)

Optimal #2

Java: Memoized recursion

Python: Memoized

O(n) O(n)

Java Solutions

Recursion O(2^n) O(n)

Try rob/skip at each house.

Pseudo-code

rob(i)=max(rob(i-1), rob(i-2)+nums[i])

Complexity

Time: O(2^n) — Branching.
Space: O(n) — Stack.

Java code

class Solution {
    public int rob(int[] nums) { return rec(nums, nums.length - 1); }
    private int rec(int[] n, int i) {
        if (i < 0) return 0;
        return Math.max(rec(n, i-1), rec(n, i-2) + n[i]);
    }
}
Bottom-up O(1) O(n) O(1)

Two rolling vars.

Pseudo-code

prev2=0; prev1=0; for x in nums: cur=max(prev1, prev2+x); shift

Complexity

Time: O(n) — Linear.
Space: O(1) — Two ints.

Edge cases & gotchas

  • n=1 → nums[0].

Java code

class Solution {
    public int rob(int[] nums) {
        int prev2 = 0, prev1 = 0;
        for (int x : nums) { int cur = Math.max(prev1, prev2 + x); prev2 = prev1; prev1 = cur; }
        return prev1;
    }
}
Memoized recursion O(n) O(n)

Top-down with cache.

Pseudo-code

memo + rec

Complexity

Time: O(n) — Each subproblem once.
Space: O(n) — Memo.

Java code

class Solution {
    private Integer[] memo;
    public int rob(int[] nums) {
        memo = new Integer[nums.length];
        return rec(nums, nums.length - 1);
    }
    private int rec(int[] n, int i) {
        if (i < 0) return 0;
        if (memo[i] != null) return memo[i];
        return memo[i] = Math.max(rec(n, i-1), rec(n, i-2) + n[i]);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n)
SpaceO(n)O(n)
Difficulty1/52/5
WhenDon'tTop-down clarity
PE VerdictBottom-up O(1).

Python Solutions

Recursion O(2^n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(2^n) — Tree.
Space: O(n) — Stack.

Python code

from typing import List
class Solution:
    def rob(self, nums: List[int]) -> int:
        def rec(i):
            if i < 0: return 0
            return max(rec(i-1), rec(i-2) + nums[i])
        return rec(len(nums) - 1)
Rolling O(1) O(n) O(1)

Two ints.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — Two ints.

Python code

from typing import List
class Solution:
    def rob(self, nums: List[int]) -> int:
        prev2 = prev1 = 0
        for x in nums:
            prev2, prev1 = prev1, max(prev1, prev2 + x)
        return prev1
Memoized O(n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Each once.
Space: O(n) — Memo.

Python code

from typing import List
from functools import lru_cache
class Solution:
    def rob(self, nums: List[int]) -> int:
        @lru_cache(None)
        def rec(i):
            if i < 0: return 0
            return max(rec(i-1), rec(i-2) + nums[i])
        return rec(len(nums) - 1)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n)
SpaceO(n)O(n)
Difficulty1/52/5
WhenDon'tTop-down
PE VerdictRolling DP.

What the interviewer is really testing

Foundational DP recurrence.

Top gotchas

  • Decision at each house: rob (skip prev) or skip (carry max).
  • Empty array → 0.
  • Sliding two vars suffices.

Ship-it

Two-variable rolling DP.

#102 LC 213 Medium House Robber II

Circle — split into two linear House Robber problems; take max.

Problem Statement

Houses in a circle (first and last adjacent). Same rule: no two adjacent. Max amount.

Signature: int rob(int[] nums)

Examples

[2,3,2] → 3
[1,2,3,1] → 4

Constraints

  • 1 <= n <= 100

Approach Overview

Brute Force

Java: Recursion with both options

Python: Recursion

O(2^n) O(n)

Optimal #1

Java: Two linear scans

Python: Two linear scans

O(n) O(1)

Optimal #2

Java: Memoized rec with start/end

Python: Memoized

O(n) O(n)

Java Solutions

Recursion with both options O(2^n) O(n)

Try with-first and without-first.

Pseudo-code

max(rob(0..n-2), rob(1..n-1))

Complexity

Time: O(2^n) — Branching.
Space: O(n) — Stack.

Edge cases & gotchas

  • n=1 → nums[0].

Java code

class Solution {
    public int rob(int[] nums) {
        if (nums.length == 1) return nums[0];
        return Math.max(rec(nums, 0, nums.length - 2), rec(nums, 1, nums.length - 1));
    }
    private int rec(int[] n, int l, int r) {
        if (l > r) return 0;
        return Math.max(rec(n, l, r-1), rec(n, l, r-2) + n[r]);
    }
}
Two linear scans O(n) O(1)

Run House Robber I on [0..n-2] and [1..n-1].

Pseudo-code

max(rob_linear(0,n-2), rob_linear(1,n-1))

Complexity

Time: O(n) — Two passes.
Space: O(1) — Two ints.

Edge cases & gotchas

  • n=1 → nums[0].

Java code

class Solution {
    public int rob(int[] nums) {
        if (nums.length == 1) return nums[0];
        return Math.max(robLinear(nums, 0, nums.length - 2), robLinear(nums, 1, nums.length - 1));
    }
    private int robLinear(int[] n, int l, int r) {
        int prev2 = 0, prev1 = 0;
        for (int i = l; i <= r; i++) { int cur = Math.max(prev1, prev2 + n[i]); prev2 = prev1; prev1 = cur; }
        return prev1;
    }
}
Memoized rec with start/end O(n) O(n)

Top-down for both windows.

Pseudo-code

memo rob(start, i) for two windows

Complexity

Time: O(n) — Linear.
Space: O(n) — Memo.

Java code

class Solution {
    public int rob(int[] nums) {
        if (nums.length == 1) return nums[0];
        return Math.max(memo(nums, 0, nums.length - 2), memo(nums, 1, nums.length - 1));
    }
    private int memo(int[] n, int l, int r) {
        Integer[] m = new Integer[n.length];
        return rec(n, l, r, m);
    }
    private int rec(int[] n, int l, int i, Integer[] m) {
        if (i < l) return 0;
        if (m[i] != null) return m[i];
        return m[i] = Math.max(rec(n, l, i-1, m), rec(n, l, i-2, m) + n[i]);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n)
SpaceO(n)O(n)
Difficulty2/53/5
WhenDon'tTop-down
PE VerdictTwo linear scans.

Python Solutions

Recursion O(2^n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(2^n) — Tree.
Space: O(n) — Stack.

Python code

from typing import List
class Solution:
    def rob(self, nums: List[int]) -> int:
        if len(nums) == 1: return nums[0]
        def rec(l, r):
            if l > r: return 0
            return max(rec(l, r-1), rec(l, r-2) + nums[r])
        return max(rec(0, len(nums)-2), rec(1, len(nums)-1))
Two linear scans O(n) O(1)

Run linear DP on two slices.

Pseudo-code

see Java

Complexity

Time: O(n) — Two passes.
Space: O(1) — Two ints.

Python code

from typing import List
class Solution:
    def rob(self, nums: List[int]) -> int:
        if len(nums) == 1: return nums[0]
        def helper(arr):
            prev2 = prev1 = 0
            for x in arr:
                prev2, prev1 = prev1, max(prev1, prev2 + x)
            return prev1
        return max(helper(nums[:-1]), helper(nums[1:]))
Memoized O(n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(n) — Memo.

Python code

from typing import List
from functools import lru_cache
class Solution:
    def rob(self, nums: List[int]) -> int:
        if len(nums) == 1: return nums[0]
        def helper(l, r):
            @lru_cache(None)
            def rec(i):
                if i < l: return 0
                return max(rec(i-1), rec(i-2) + nums[i])
            res = rec(r)
            rec.cache_clear()
            return res
        return max(helper(0, len(nums)-2), helper(1, len(nums)-1))

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n)
SpaceO(n)O(n)
Difficulty2/53/5
WhenDon'tTop-down
PE VerdictTwo linear scans.

What the interviewer is really testing

Reduction trick: circular → two linear.

Top gotchas

  • n=1 is a special case — return nums[0].
  • First and last can't BOTH be robbed, so either drop first or drop last.
  • Otherwise it's just House Robber I.

Ship-it

Run linear robber on nums[:-1] and nums[1:].

#103 LC 5 Medium Longest Palindromic Substring

Expand around each center — O(n²) time, O(1) space.

Problem Statement

Return any longest palindromic substring of s.

Signature: String longestPalindrome(String s)

Examples

'babad' → 'bab' or 'aba'
'cbbd' → 'bb'

Constraints

  • 1 <= n <= 1000

Approach Overview

Brute Force

Java: Check every substring

Python: Triple loop

O(n³) O(1)

Optimal #1

Java: Expand around center

Python: Expand around center

O(n²) O(1)

Optimal #2

Java: Manacher's algorithm

Python: Manacher's

O(n) O(n)

Java Solutions

Check every substring O(n³) O(1)

For each (i,j) check palindrome.

Pseudo-code

track best; for each i<j: if isPalin → update

Complexity

Time: O(n³) — n² substrings × O(n).
Space: O(1) — None.

Edge cases & gotchas

  • n=1 → s itself.

Java code

class Solution {
    public String longestPalindrome(String s) {
        int bestL = 0, bestR = 0;
        for (int i = 0; i < s.length(); i++) for (int j = i; j < s.length(); j++)
            if (isPal(s, i, j) && j - i > bestR - bestL) { bestL = i; bestR = j; }
        return s.substring(bestL, bestR + 1);
    }
    private boolean isPal(String s, int l, int r) {
        while (l < r) if (s.charAt(l++) != s.charAt(r--)) return false;
        return true;
    }
}
Expand around center O(n²) O(1)

2n-1 centers; expand outward.

Pseudo-code

for each i: expand(i,i) and expand(i,i+1); track longest

Complexity

Time: O(n²) — Each center O(n).
Space: O(1) — None.

Java code

class Solution {
    public String longestPalindrome(String s) {
        int start = 0, end = 0;
        for (int i = 0; i < s.length(); i++) {
            int l1 = expand(s, i, i);
            int l2 = expand(s, i, i + 1);
            int len = Math.max(l1, l2);
            if (len > end - start) { start = i - (len - 1) / 2; end = i + len / 2; }
        }
        return s.substring(start, end + 1);
    }
    private int expand(String s, int l, int r) {
        while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) { l--; r++; }
        return r - l - 1;
    }
}
Manacher's algorithm O(n) O(n)

O(n) using mirror property.

Pseudo-code

transform s with separators; compute palindrome radii using center & right boundary

Complexity

Time: O(n) — Linear.
Space: O(n) — Radius array.

Java code

class Solution {
    public String longestPalindrome(String s) {
        StringBuilder sb = new StringBuilder("^");
        for (char c : s.toCharArray()) sb.append('#').append(c);
        sb.append("#$");
        String t = sb.toString();
        int n = t.length(), c = 0, r = 0;
        int[] p = new int[n];
        for (int i = 1; i < n - 1; i++) {
            if (r > i) p[i] = Math.min(r - i, p[2*c - i]);
            while (t.charAt(i + p[i] + 1) == t.charAt(i - p[i] - 1)) p[i]++;
            if (i + p[i] > r) { c = i; r = i + p[i]; }
        }
        int maxLen = 0, center = 0;
        for (int i = 1; i < n - 1; i++) if (p[i] > maxLen) { maxLen = p[i]; center = i; }
        int start = (center - maxLen) / 2;
        return s.substring(start, start + maxLen);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n³)O(n)
SpaceO(1)O(n)
Difficulty1/55/5
WhenDon'tHot path / huge n
PE VerdictExpand around center — clean and fast.

Python Solutions

Triple loop O(n³) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n³) — Naive.
Space: O(1) — None.

Python code

class Solution:
    def longestPalindrome(self, s: str) -> str:
        best = ''
        for i in range(len(s)):
            for j in range(i, len(s)):
                sub = s[i:j+1]
                if sub == sub[::-1] and len(sub) > len(best): best = sub
        return best
Expand around center O(n²) O(1)

2n-1 centers.

Pseudo-code

see Java

Complexity

Time: O(n²) — Each O(n).
Space: O(1) — None.

Python code

class Solution:
    def longestPalindrome(self, s: str) -> str:
        def expand(l, r):
            while l >= 0 and r < len(s) and s[l] == s[r]: l -= 1; r += 1
            return s[l+1:r]
        best = ''
        for i in range(len(s)):
            a = expand(i, i); b = expand(i, i+1)
            for c in (a, b):
                if len(c) > len(best): best = c
        return best
Manacher's O(n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(n) — Radii.

Python code

class Solution:
    def longestPalindrome(self, s: str) -> str:
        t = '^#' + '#'.join(s) + '#$'
        n = len(t); p = [0]*n; c = r = 0
        for i in range(1, n-1):
            if r > i: p[i] = min(r - i, p[2*c - i])
            while t[i + p[i] + 1] == t[i - p[i] - 1]: p[i] += 1
            if i + p[i] > r: c = i; r = i + p[i]
        ml = max(p); ci = p.index(ml)
        start = (ci - ml) // 2
        return s[start:start + ml]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n³)O(n)
SpaceO(1)O(n)
Difficulty1/55/5
WhenDon'tHot path
PE VerdictExpand around center.

What the interviewer is really testing

Center-expansion is the go-to for interviews.

Top gotchas

  • 2n-1 centers: each char and each gap.
  • Track BOTH odd (i,i) and even (i,i+1) lengths.
  • Index math after expand: l = i - (len-1)/2, r = i + len/2.

Ship-it

Expand around center.

#104 LC 647 Medium Palindromic Substrings

Count palindromes — expand around each center, count valid expansions.

Problem Statement

Count distinct (by position) palindromic substrings of s.

Signature: int countSubstrings(String s)

Examples

'abc' → 3
'aaa' → 6

Constraints

  • 1 <= n <= 1000

Approach Overview

Brute Force

Java: Check every substring

Python: Triple loop

O(n³) O(1)

Optimal #1

Java: Expand around center

Python: Expand around center

O(n²) O(1)

Optimal #2

Java: DP table

Python: DP table

O(n²) O(n²)

Java Solutions

Check every substring O(n³) O(1)

Same as LPS brute.

Pseudo-code

for each (i,j): if isPalin → cnt++

Complexity

Time: O(n³) — n² subs × O(n).
Space: O(1) — None.

Java code

class Solution {
    public int countSubstrings(String s) {
        int cnt = 0;
        for (int i = 0; i < s.length(); i++) for (int j = i; j < s.length(); j++)
            if (isPal(s, i, j)) cnt++;
        return cnt;
    }
    private boolean isPal(String s, int l, int r) {
        while (l < r) if (s.charAt(l++) != s.charAt(r--)) return false;
        return true;
    }
}
Expand around center O(n²) O(1)

Each successful expansion = one palindrome.

Pseudo-code

for each center: while can expand: cnt++

Complexity

Time: O(n²) — 2n-1 centers × O(n).
Space: O(1) — None.

Java code

class Solution {
    public int countSubstrings(String s) {
        int cnt = 0;
        for (int i = 0; i < s.length(); i++) { cnt += expand(s, i, i); cnt += expand(s, i, i+1); }
        return cnt;
    }
    private int expand(String s, int l, int r) {
        int c = 0;
        while (l >= 0 && r < s.length() && s.charAt(l) == s.charAt(r)) { c++; l--; r++; }
        return c;
    }
}
DP table O(n²) O(n²)

dp[i][j] = is s[i..j] palindrome.

Pseudo-code

dp[i][j] = s[i]==s[j] && (j-i<2 || dp[i+1][j-1])

Complexity

Time: O(n²) — Fill table.
Space: O(n²) — Table.

Java code

class Solution {
    public int countSubstrings(String s) {
        int n = s.length(), cnt = 0;
        boolean[][] dp = new boolean[n][n];
        for (int i = n - 1; i >= 0; i--) for (int j = i; j < n; j++)
            if (s.charAt(i) == s.charAt(j) && (j - i < 2 || dp[i+1][j-1])) { dp[i][j] = true; cnt++; }
        return cnt;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n³)O(n²)
SpaceO(1)O(n²)
Difficulty1/53/5
WhenDon'tUseful when reusing
PE VerdictExpand around center.

Python Solutions

Triple loop O(n³) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n³) — Naive.
Space: O(1) — None.

Python code

class Solution:
    def countSubstrings(self, s: str) -> int:
        cnt = 0
        for i in range(len(s)):
            for j in range(i, len(s)):
                sub = s[i:j+1]
                if sub == sub[::-1]: cnt += 1
        return cnt
Expand around center O(n²) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²) — 2n-1 centers.
Space: O(1) — None.

Python code

class Solution:
    def countSubstrings(self, s: str) -> int:
        def expand(l, r):
            c = 0
            while l >= 0 and r < len(s) and s[l] == s[r]: c += 1; l -= 1; r += 1
            return c
        return sum(expand(i, i) + expand(i, i+1) for i in range(len(s)))
DP table O(n²) O(n²)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²) — Table.
Space: O(n²) — Table.

Python code

class Solution:
    def countSubstrings(self, s: str) -> int:
        n = len(s); cnt = 0
        dp = [[False]*n for _ in range(n)]
        for i in range(n-1, -1, -1):
            for j in range(i, n):
                if s[i] == s[j] and (j - i < 2 or dp[i+1][j-1]):
                    dp[i][j] = True; cnt += 1
        return cnt

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n³)O(n²)
SpaceO(1)O(n²)
Difficulty1/53/5
WhenDon'tReusable
PE VerdictExpand around center.

What the interviewer is really testing

Cousin of Longest Palindromic Substring.

Top gotchas

  • EACH successful expansion adds 1 to count.
  • Both odd and even centers.
  • DP table is a clean alternative if you'll reuse palindrome info.

Ship-it

Expand around center; sum counts.

#105 LC 91 Medium Decode Ways

DP: dp[i] = dp[i-1] (single) + dp[i-2] (pair if 10..26).

Problem Statement

Decode digit string using A=1..Z=26. Count distinct decodings.

Signature: int numDecodings(String s)

Examples

'12' → 2 (AB, L)
'226' → 3 (BBF, BZ, VF)
'06' → 0

Constraints

  • 1 <= n <= 100

Approach Overview

Brute Force

Java: Recursion

Python: Recursion

O(2^n) O(n)

Optimal #1

Java: Bottom-up O(1)

Python: Rolling O(1)

O(n) O(1)

Optimal #2

Java: Memoized recursion

Python: Memoized

O(n) O(n)

Java Solutions

Recursion O(2^n) O(n)

Try take 1 / take 2.

Pseudo-code

rec(i): if i==n return 1; if s[i]==0 return 0; res=rec(i+1); if 2-digit valid: res+=rec(i+2)

Complexity

Time: O(2^n) — Branching.
Space: O(n) — Stack.

Edge cases & gotchas

  • Leading 0 → 0.

Java code

class Solution {
    public int numDecodings(String s) { return rec(s, 0); }
    private int rec(String s, int i) {
        if (i == s.length()) return 1;
        if (s.charAt(i) == '0') return 0;
        int r = rec(s, i + 1);
        if (i + 1 < s.length()) {
            int v = (s.charAt(i)-'0')*10 + (s.charAt(i+1)-'0');
            if (v <= 26) r += rec(s, i + 2);
        }
        return r;
    }
}
Bottom-up O(1) O(n) O(1)

Two rolling vars from the right (or left).

Pseudo-code

dp[n]=1; for i=n-1..0: dp[i]=s[i]!='0' ? dp[i+1] + (valid pair ? dp[i+2] : 0) : 0

Complexity

Time: O(n) — One pass.
Space: O(1) — Two ints.

Java code

class Solution {
    public int numDecodings(String s) {
        int n = s.length();
        int next1 = 1, next2 = 0;
        for (int i = n - 1; i >= 0; i--) {
            int cur = 0;
            if (s.charAt(i) != '0') {
                cur = next1;
                if (i + 1 < n) {
                    int v = (s.charAt(i)-'0')*10 + (s.charAt(i+1)-'0');
                    if (v >= 10 && v <= 26) cur += next2;
                }
            }
            next2 = next1; next1 = cur;
        }
        return next1;
    }
}
Memoized recursion O(n) O(n)

Top-down cached.

Pseudo-code

memo + rec

Complexity

Time: O(n) — Each once.
Space: O(n) — Memo.

Java code

class Solution {
    private Integer[] memo;
    public int numDecodings(String s) { memo = new Integer[s.length()]; return rec(s, 0); }
    private int rec(String s, int i) {
        if (i == s.length()) return 1;
        if (s.charAt(i) == '0') return 0;
        if (memo[i] != null) return memo[i];
        int r = rec(s, i + 1);
        if (i + 1 < s.length()) {
            int v = (s.charAt(i)-'0')*10 + (s.charAt(i+1)-'0');
            if (v <= 26) r += rec(s, i + 2);
        }
        return memo[i] = r;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n)
SpaceO(n)O(n)
Difficulty2/53/5
WhenDon'tTop-down
PE VerdictBottom-up O(1).

Python Solutions

Recursion O(2^n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(2^n) — Tree.
Space: O(n) — Stack.

Python code

class Solution:
    def numDecodings(self, s: str) -> int:
        def rec(i):
            if i == len(s): return 1
            if s[i] == '0': return 0
            r = rec(i + 1)
            if i + 1 < len(s) and 10 <= int(s[i:i+2]) <= 26: r += rec(i + 2)
            return r
        return rec(0)
Rolling O(1) O(n) O(1)

Same as Java.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — Two ints.

Python code

class Solution:
    def numDecodings(self, s: str) -> int:
        n = len(s); next1, next2 = 1, 0
        for i in range(n - 1, -1, -1):
            cur = 0
            if s[i] != '0':
                cur = next1
                if i + 1 < n and 10 <= int(s[i:i+2]) <= 26: cur += next2
            next2, next1 = next1, cur
        return next1
Memoized O(n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Each once.
Space: O(n) — Memo.

Python code

from functools import lru_cache
class Solution:
    def numDecodings(self, s: str) -> int:
        @lru_cache(None)
        def rec(i):
            if i == len(s): return 1
            if s[i] == '0': return 0
            r = rec(i+1)
            if i+1 < len(s) and 10 <= int(s[i:i+2]) <= 26: r += rec(i+2)
            return r
        return rec(0)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n)
SpaceO(n)O(n)
Difficulty2/53/5
WhenDon'tTop-down
PE VerdictRolling O(1).

What the interviewer is really testing

Variant on Fibonacci with validity constraints.

Top gotchas

  • '0' alone is never decodable — only 10, 20 count as paired.
  • Pair must be in [10, 26] (not [01, 26]).
  • Empty string convention: dp[n] = 1.

Ship-it

Bottom-up O(1).

#106 LC 322 Medium Coin Change

Unbounded knapsack — dp[a] = min over coins c of dp[a-c] + 1.

Problem Statement

Fewest coins summing to amount; -1 if impossible.

Signature: int coinChange(int[] coins, int amount)

Examples

coins=[1,2,5], amount=11 → 3 (5+5+1)

Constraints

  • 1 <= coins.length <= 12
  • 0 <= amount <= 10^4

Approach Overview

Brute Force

Java: Recursion

Python: Recursion

O(c^a) O(a)

Optimal #1

Java: Bottom-up DP

Python: Bottom-up DP

O(a·n) O(a)

Optimal #2

Java: BFS by amount

Python: BFS by amount

O(a·n) O(a)

Java Solutions

Recursion O(c^a) O(a)

Try each coin, recurse on remainder.

Pseudo-code

rec(rem): if 0 return 0; if <0 return INF; for c: best = min(best, rec(rem-c)+1)

Complexity

Time: O(c^a) — Branching.
Space: O(a) — Stack.

Edge cases & gotchas

  • amount=0 → 0.

Java code

class Solution {
    public int coinChange(int[] coins, int amount) {
        int r = rec(coins, amount);
        return r == Integer.MAX_VALUE ? -1 : r;
    }
    private int rec(int[] coins, int rem) {
        if (rem == 0) return 0;
        if (rem < 0) return Integer.MAX_VALUE;
        int best = Integer.MAX_VALUE;
        for (int c : coins) {
            int sub = rec(coins, rem - c);
            if (sub != Integer.MAX_VALUE) best = Math.min(best, sub + 1);
        }
        return best;
    }
}
Bottom-up DP O(a·n) O(a)

dp[a] = min over coins of dp[a-c]+1.

Pseudo-code

dp[0]=0; for a=1..amount: for c: if a-c>=0 and dp[a-c]+1<dp[a]: dp[a]=dp[a-c]+1

Complexity

Time: O(a·n) — Fill array.
Space: O(a) — dp[].

Edge cases & gotchas

  • dp[a]=INF means unreachable.

Java code

class Solution {
    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount + 1];
        java.util.Arrays.fill(dp, amount + 1);
        dp[0] = 0;
        for (int a = 1; a <= amount; a++) for (int c : coins)
            if (a - c >= 0) dp[a] = Math.min(dp[a], dp[a - c] + 1);
        return dp[amount] > amount ? -1 : dp[amount];
    }
}
BFS by amount O(a·n) O(a)

Level = #coins; first time we hit amount.

Pseudo-code

queue starts at 0; expand by +coin; level when reach amount

Complexity

Time: O(a·n) — Each amount once.
Space: O(a) — Visited.

Java code

import java.util.*;
class Solution {
    public int coinChange(int[] coins, int amount) {
        if (amount == 0) return 0;
        boolean[] seen = new boolean[amount + 1];
        Deque<Integer> q = new ArrayDeque<>(); q.offer(0); seen[0] = true;
        int level = 0;
        while (!q.isEmpty()) {
            level++;
            int sz = q.size();
            for (int k = 0; k < sz; k++) {
                int cur = q.poll();
                for (int c : coins) {
                    int nxt = cur + c;
                    if (nxt == amount) return level;
                    if (nxt < amount && !seen[nxt]) { seen[nxt] = true; q.offer(nxt); }
                }
            }
        }
        return -1;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(c^a)O(a·n)
SpaceO(a)O(a)
Difficulty2/53/5
WhenDon'tShortest-coins emphasis
PE VerdictBottom-up DP.

Python Solutions

Recursion O(c^a) O(a)

Same.

Pseudo-code

see Java

Complexity

Time: O(c^a) — Tree.
Space: O(a) — Stack.

Python code

from typing import List
class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        def rec(rem):
            if rem == 0: return 0
            if rem < 0: return float('inf')
            return min((rec(rem - c) + 1 for c in coins), default=float('inf'))
        r = rec(amount)
        return -1 if r == float('inf') else r
Bottom-up DP O(a·n) O(a)

Same.

Pseudo-code

see Java

Complexity

Time: O(a·n) — Fill.
Space: O(a) — Array.

Python code

from typing import List
class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        dp = [amount + 1] * (amount + 1); dp[0] = 0
        for a in range(1, amount + 1):
            for c in coins:
                if a - c >= 0: dp[a] = min(dp[a], dp[a-c] + 1)
        return -1 if dp[amount] > amount else dp[amount]
BFS by amount O(a·n) O(a)

Same.

Pseudo-code

see Java

Complexity

Time: O(a·n) — Linear.
Space: O(a) — Visited.

Python code

from typing import List
from collections import deque
class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        if amount == 0: return 0
        seen = [False]*(amount + 1)
        q = deque([0]); seen[0] = True; level = 0
        while q:
            level += 1
            for _ in range(len(q)):
                cur = q.popleft()
                for c in coins:
                    nxt = cur + c
                    if nxt == amount: return level
                    if nxt < amount and not seen[nxt]:
                        seen[nxt] = True; q.append(nxt)
        return -1

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(c^a)O(a·n)
SpaceO(a)O(a)
Difficulty2/53/5
WhenDon'tBFS framing
PE VerdictBottom-up DP.

What the interviewer is really testing

Canonical unbounded knapsack.

Top gotchas

  • Use amount+1 as 'infinity' sentinel — strictly larger than any valid count.
  • amount=0 returns 0 (no coins needed).
  • BFS works because each coin adds exactly 1 to the count.

Ship-it

Bottom-up DP.

#107 LC 152 Medium Maximum Product Subarray

Track min AND max ending at i — a negative flips them.

Problem Statement

Max product of any contiguous subarray.

Signature: int maxProduct(int[] nums)

Examples

[2,3,-2,4] → 6
[-2,0,-1] → 0

Constraints

  • 1 <= n <= 2·10^4

Approach Overview

Brute Force

Java: All subarrays

Python: All subarrays

O(n²) O(1)

Optimal #1

Java: Track min & max

Python: Track min & max

O(n) O(1)

Optimal #2

Java: Two-direction sweep

Python: Two-direction sweep

O(n) O(1)

Java Solutions

All subarrays O(n²) O(1)

O(n²) products.

Pseudo-code

for each l: prod=1; for r=l..: prod*=nums[r]; best=max

Complexity

Time: O(n²) — Nested.
Space: O(1) — None.

Edge cases & gotchas

  • Single element.

Java code

class Solution {
    public int maxProduct(int[] nums) {
        int best = Integer.MIN_VALUE;
        for (int l = 0; l < nums.length; l++) {
            int p = 1;
            for (int r = l; r < nums.length; r++) { p *= nums[r]; best = Math.max(best, p); }
        }
        return best;
    }
}
Track min & max O(n) O(1)

Negative number swaps min↔max.

Pseudo-code

maxEnd=minEnd=nums[0]; for x: if x<0 swap; maxEnd=max(x, maxEnd*x); minEnd=min(x, minEnd*x); best=max(best, maxEnd)

Complexity

Time: O(n) — Single pass.
Space: O(1) — Three ints.

Edge cases & gotchas

  • Zeros reset to x itself.

Java code

class Solution {
    public int maxProduct(int[] nums) {
        int maxEnd = nums[0], minEnd = nums[0], best = nums[0];
        for (int i = 1; i < nums.length; i++) {
            int x = nums[i];
            if (x < 0) { int t = maxEnd; maxEnd = minEnd; minEnd = t; }
            maxEnd = Math.max(x, maxEnd * x);
            minEnd = Math.min(x, minEnd * x);
            best = Math.max(best, maxEnd);
        }
        return best;
    }
}
Two-direction sweep O(n) O(1)

Forward & backward product sweep; reset on 0.

Pseudo-code

prefix product L→R then R→L, reset to 1 after 0; max over all

Complexity

Time: O(n) — Two passes.
Space: O(1) — Two ints.

Java code

class Solution {
    public int maxProduct(int[] nums) {
        int n = nums.length, best = Integer.MIN_VALUE;
        int p = 1;
        for (int i = 0; i < n; i++) { p = p == 0 ? 1 : p; p *= nums[i]; best = Math.max(best, p); }
        p = 1;
        for (int i = n - 1; i >= 0; i--) { p = p == 0 ? 1 : p; p *= nums[i]; best = Math.max(best, p); }
        return best;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(1)O(1)
Difficulty1/53/5
WhenDon'tPythonic alt
PE VerdictTrack min & max ending at i.

Python Solutions

All subarrays O(n²) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²) — Nested.
Space: O(1) — None.

Python code

from typing import List
class Solution:
    def maxProduct(self, nums: List[int]) -> int:
        best = nums[0]
        for l in range(len(nums)):
            p = 1
            for r in range(l, len(nums)):
                p *= nums[r]; best = max(best, p)
        return best
Track min & max O(n) O(1)

Negative swaps.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — Three ints.

Python code

from typing import List
class Solution:
    def maxProduct(self, nums: List[int]) -> int:
        maxE = minE = best = nums[0]
        for x in nums[1:]:
            if x < 0: maxE, minE = minE, maxE
            maxE = max(x, maxE * x)
            minE = min(x, minE * x)
            best = max(best, maxE)
        return best
Two-direction sweep O(n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Two passes.
Space: O(1) — Const.

Python code

from typing import List
class Solution:
    def maxProduct(self, nums: List[int]) -> int:
        best = nums[0]; p = 1
        for x in nums:
            p = 1 if p == 0 else p
            p *= x; best = max(best, p)
        p = 1
        for x in reversed(nums):
            p = 1 if p == 0 else p
            p *= x; best = max(best, p)
        return best

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(1)O(1)
Difficulty1/53/5
WhenDon'tAlt
PE VerdictTrack min & max.

What the interviewer is really testing

Classic min/max DP trick.

Top gotchas

  • Negative number → min becomes max, vice versa. SWAP before update.
  • Always compare against x alone (reset on zero implicitly).
  • Two-direction sweep with reset-on-zero is a slick alternative.

Ship-it

Track min and max ending at i.

#108 LC 139 Medium Word Break

DP — dp[i]=true if some j<i has dp[j] && s[j..i] in dict.

Problem Statement

Can s be split into a sequence of dictionary words?

Signature: boolean wordBreak(String s, List<String> wordDict)

Examples

'leetcode', ['leet','code'] → true
'catsandog', ['cats','dog','sand','and','cat'] → false

Constraints

  • 1 <= s.length <= 300

Approach Overview

Brute Force

Java: Recursion

Python: Recursion

O(2^n) O(n)

Optimal #1

Java: Bottom-up DP

Python: Bottom-up DP

O(n²·L) O(n)

Optimal #2

Java: Memoized recursion

Python: Memoized

O(n²·L) O(n)

Java Solutions

Recursion O(2^n) O(n)

Try every prefix split.

Pseudo-code

rec(start): if start==n return true; for end>start: if s[start..end] in dict and rec(end): return true

Complexity

Time: O(2^n) — Branching.
Space: O(n) — Stack.

Java code

import java.util.*;
class Solution {
    public boolean wordBreak(String s, List<String> dict) {
        Set<String> d = new HashSet<>(dict);
        return rec(s, 0, d);
    }
    private boolean rec(String s, int start, Set<String> d) {
        if (start == s.length()) return true;
        for (int end = start + 1; end <= s.length(); end++)
            if (d.contains(s.substring(start, end)) && rec(s, end, d)) return true;
        return false;
    }
}
Bottom-up DP O(n²·L) O(n)

dp[i]=true if any j<i has dp[j] && s[j..i] in dict.

Pseudo-code

dp[0]=true; for i=1..n: for j<i: if dp[j] && s[j..i] in dict: dp[i]=true; break

Complexity

Time: O(n²·L) — n² splits, L substring.
Space: O(n) — dp[].

Java code

import java.util.*;
class Solution {
    public boolean wordBreak(String s, List<String> dict) {
        Set<String> d = new HashSet<>(dict);
        int n = s.length();
        boolean[] dp = new boolean[n + 1];
        dp[0] = true;
        for (int i = 1; i <= n; i++) for (int j = 0; j < i; j++)
            if (dp[j] && d.contains(s.substring(j, i))) { dp[i] = true; break; }
        return dp[n];
    }
}
Memoized recursion O(n²·L) O(n)

Top-down with cache.

Pseudo-code

memo[start]; rec

Complexity

Time: O(n²·L) — Each start once.
Space: O(n) — Memo.

Java code

import java.util.*;
class Solution {
    private Boolean[] memo;
    public boolean wordBreak(String s, List<String> dict) {
        Set<String> d = new HashSet<>(dict);
        memo = new Boolean[s.length()];
        return rec(s, 0, d);
    }
    private boolean rec(String s, int start, Set<String> d) {
        if (start == s.length()) return true;
        if (memo[start] != null) return memo[start];
        for (int end = start + 1; end <= s.length(); end++)
            if (d.contains(s.substring(start, end)) && rec(s, end, d)) return memo[start] = true;
        return memo[start] = false;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n²·L)
SpaceO(n)O(n)
Difficulty2/53/5
WhenDon'tTop-down
PE VerdictBottom-up DP.

Python Solutions

Recursion O(2^n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(2^n) — Tree.
Space: O(n) — Stack.

Python code

from typing import List
class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        d = set(wordDict)
        def rec(start):
            if start == len(s): return True
            return any(s[start:end] in d and rec(end) for end in range(start+1, len(s)+1))
        return rec(0)
Bottom-up DP O(n²·L) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²·L) — Linear-ish.
Space: O(n) — dp[].

Python code

from typing import List
class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        d = set(wordDict); n = len(s)
        dp = [False]*(n+1); dp[0] = True
        for i in range(1, n+1):
            for j in range(i):
                if dp[j] and s[j:i] in d: dp[i] = True; break
        return dp[n]
Memoized O(n²·L) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²·L) — Each start once.
Space: O(n) — Memo.

Python code

from typing import List
from functools import lru_cache
class Solution:
    def wordBreak(self, s: str, wordDict: List[str]) -> bool:
        d = set(wordDict)
        @lru_cache(None)
        def rec(start):
            if start == len(s): return True
            return any(s[start:end] in d and rec(end) for end in range(start+1, len(s)+1))
        return rec(0)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n²·L)
SpaceO(n)O(n)
Difficulty2/53/5
WhenDon'tTop-down
PE VerdictBottom-up DP.

What the interviewer is really testing

Classic 1-D DP — match prefixes against dict.

Top gotchas

  • dp[0] = true (empty prefix).
  • Hash the dict — O(1) lookups.
  • Pruning: only check substrings up to max word length.

Ship-it

Bottom-up DP with set lookup.

#109 LC 300 Medium Longest Increasing Subsequence

Patience sorting / binary search — O(n log n).

Problem Statement

Length of longest strictly increasing subsequence.

Signature: int lengthOfLIS(int[] nums)

Examples

[10,9,2,5,3,7,101,18] → 4 ([2,3,7,101])

Constraints

  • 1 <= n <= 2500

Approach Overview

Brute Force

Java: Recursion: take/skip

Python: Recursion

O(2^n) O(n)

Optimal #1

Java: Patience + binary search

Python: Patience

O(n log n) O(n)

Optimal #2

Java: DP O(n²)

Python: DP O(n²)

O(n²) O(n)

Java Solutions

Recursion: take/skip O(2^n) O(n)

Try each element with/without prev constraint.

Pseudo-code

rec(i, prev): max(rec(i+1, prev), rec(i+1, i) + 1 if nums[i]>prev)

Complexity

Time: O(2^n) — Branching.
Space: O(n) — Stack.

Java code

class Solution {
    public int lengthOfLIS(int[] nums) { return rec(nums, 0, Integer.MIN_VALUE); }
    private int rec(int[] n, int i, int prev) {
        if (i == n.length) return 0;
        int skip = rec(n, i + 1, prev);
        int take = n[i] > prev ? 1 + rec(n, i + 1, n[i]) : 0;
        return Math.max(skip, take);
    }
}
Patience + binary search O(n log n) O(n)

Maintain 'tails[]': smallest tail of each LIS length.

Pseudo-code

for x: idx=lower_bound(tails, x); if idx==len: append else tails[idx]=x

Complexity

Time: O(n log n) — Binary insert.
Space: O(n) — tails.

Edge cases & gotchas

  • Strictly increasing → use lower_bound (not upper).

Java code

import java.util.*;
class Solution {
    public int lengthOfLIS(int[] nums) {
        int[] tails = new int[nums.length]; int len = 0;
        for (int x : nums) {
            int lo = 0, hi = len;
            while (lo < hi) { int m = (lo + hi) >>> 1; if (tails[m] < x) lo = m + 1; else hi = m; }
            tails[lo] = x;
            if (lo == len) len++;
        }
        return len;
    }
}
DP O(n²) O(n²) O(n)

dp[i] = LIS ending at i.

Pseudo-code

dp[i]=1+max(dp[j] for j<i if nums[j]<nums[i])

Complexity

Time: O(n²) — Nested.
Space: O(n) — dp[].

Java code

class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length, best = 1;
        int[] dp = new int[n];
        java.util.Arrays.fill(dp, 1);
        for (int i = 1; i < n; i++) {
            for (int j = 0; j < i; j++) if (nums[j] < nums[i]) dp[i] = Math.max(dp[i], dp[j] + 1);
            best = Math.max(best, dp[i]);
        }
        return best;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n²)
SpaceO(n)O(n)
Difficulty2/52/5
WhenDon'tPedagogical
PE VerdictPatience + binary search.

Python Solutions

Recursion O(2^n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(2^n) — Tree.
Space: O(n) — Stack.

Python code

from typing import List
class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        def rec(i, prev):
            if i == len(nums): return 0
            skip = rec(i+1, prev)
            take = 1 + rec(i+1, nums[i]) if nums[i] > prev else 0
            return max(skip, take)
        return rec(0, float('-inf'))
Patience O(n log n) O(n)

bisect_left.

Pseudo-code

see Java

Complexity

Time: O(n log n) — Binary insert.
Space: O(n) — tails.

Python code

from typing import List
from bisect import bisect_left
class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        tails = []
        for x in nums:
            i = bisect_left(tails, x)
            if i == len(tails): tails.append(x)
            else: tails[i] = x
        return len(tails)
DP O(n²) O(n²) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²) — Nested.
Space: O(n) — dp[].

Python code

from typing import List
class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        n = len(nums); dp = [1]*n
        for i in range(1, n):
            for j in range(i):
                if nums[j] < nums[i]: dp[i] = max(dp[i], dp[j] + 1)
        return max(dp)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n²)
SpaceO(n)O(n)
Difficulty2/52/5
WhenDon'tPedagogical
PE VerdictPatience + bisect.

What the interviewer is really testing

Patience sorting trick — every L6 should know it.

Top gotchas

  • tails[] is NOT the actual LIS — just smallest possible tail for each length.
  • Strictly increasing → bisect_left; non-decreasing → bisect_right.
  • O(n²) DP is easier to explain but slower.

Ship-it

Patience + binary search.

#110 LC 416 Medium Partition Equal Subset Sum

Subset-sum DP — can we reach sum/2 using subset of nums? (0/1 knapsack).

Problem Statement

Can nums be split into two subsets with equal sum?

Signature: boolean canPartition(int[] nums)

Examples

[1,5,11,5] → true (1+5+5 = 11)
[1,2,3,5] → false

Constraints

  • 1 <= n <= 200
  • 1 <= nums[i] <= 100

Approach Overview

Brute Force

Java: Recursion try take/skip

Python: Recursion

O(2^n) O(n)

Optimal #1

Java: 1-D bitset/bool DP

Python: 1-D DP

O(n·s) O(s)

Optimal #2

Java: BitSet (java.util.BitSet)

Python: BigInt as bitset

O(n·s/64) O(s/64)

Java Solutions

Recursion try take/skip O(2^n) O(n)

Subset-sum search.

Pseudo-code

rec(i, target): take or skip; true if target==0

Complexity

Time: O(2^n) — Branching.
Space: O(n) — Stack.

Edge cases & gotchas

  • odd total → false.

Java code

class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0; for (int x : nums) sum += x;
        if ((sum & 1) == 1) return false;
        return rec(nums, 0, sum / 2);
    }
    private boolean rec(int[] n, int i, int t) {
        if (t == 0) return true;
        if (i == n.length || t < 0) return false;
        return rec(n, i+1, t - n[i]) || rec(n, i+1, t);
    }
}
1-D bitset/bool DP O(n·s) O(s)

dp[t] = can reach sum t; iterate right-to-left.

Pseudo-code

dp[0]=true; for x in nums: for t from sum/2 down to x: dp[t] |= dp[t-x]

Complexity

Time: O(n·s) — s = sum/2.
Space: O(s) — Bool array.

Java code

class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0; for (int x : nums) sum += x;
        if ((sum & 1) == 1) return false;
        int target = sum / 2;
        boolean[] dp = new boolean[target + 1];
        dp[0] = true;
        for (int x : nums) for (int t = target; t >= x; t--) dp[t] |= dp[t - x];
        return dp[target];
    }
}
BitSet (java.util.BitSet) O(n·s/64) O(s/64)

Shift bitset by x each step.

Pseudo-code

bits=01; for x: bits = bits | (bits << x); return bits[target]

Complexity

Time: O(n·s/64) — Bit ops.
Space: O(s/64) — BitSet.

Java code

import java.util.*;
class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0; for (int x : nums) sum += x;
        if ((sum & 1) == 1) return false;
        int target = sum / 2;
        BitSet bits = new BitSet(target + 1);
        bits.set(0);
        for (int x : nums) {
            BitSet shifted = new BitSet(target + 1);
            for (int i = bits.nextSetBit(0); i >= 0 && i + x <= target; i = bits.nextSetBit(i + 1)) shifted.set(i + x);
            bits.or(shifted);
        }
        return bits.get(target);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n·s/64)
SpaceO(n)O(s/64)
Difficulty2/54/5
WhenDon'tHot path / large s
PE Verdict1-D DP (right-to-left to avoid double count).

Python Solutions

Recursion O(2^n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(2^n) — Tree.
Space: O(n) — Stack.

Python code

from typing import List
class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        total = sum(nums)
        if total % 2: return False
        def rec(i, t):
            if t == 0: return True
            if i == len(nums) or t < 0: return False
            return rec(i+1, t - nums[i]) or rec(i+1, t)
        return rec(0, total // 2)
1-D DP O(n·s) O(s)

Same.

Pseudo-code

see Java

Complexity

Time: O(n·s) — Linear.
Space: O(s) — Bool array.

Python code

from typing import List
class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        total = sum(nums)
        if total % 2: return False
        target = total // 2
        dp = [False]*(target + 1); dp[0] = True
        for x in nums:
            for t in range(target, x - 1, -1):
                dp[t] = dp[t] or dp[t - x]
        return dp[target]
BigInt as bitset O(n·s/64) O(s/64)

Python ints are arbitrary precision.

Pseudo-code

bits = 1; for x: bits |= bits << x; return (bits >> target) & 1

Complexity

Time: O(n·s/64) — Bit shift.
Space: O(s/64) — Int.

Python code

from typing import List
class Solution:
    def canPartition(self, nums: List[int]) -> bool:
        total = sum(nums)
        if total % 2: return False
        target = total // 2
        bits = 1
        for x in nums: bits |= bits << x
        return (bits >> target) & 1 == 1

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n·s/64)
SpaceO(n)O(s/64)
Difficulty2/54/5
WhenDon'tHot path
PE Verdict1-D DP.

What the interviewer is really testing

Subset-sum is THE canonical 0/1 knapsack.

Top gotchas

  • If total sum is ODD → return false immediately.
  • Iterate t DOWN to x — otherwise you'd reuse same nums[i] multiple times.
  • Python's arbitrary-precision int makes the bitset trick a one-liner.

Ship-it

1-D bool DP iterating right-to-left.

2-D Dynamic Programming

#111 LC 62 Medium Unique Paths

Lattice paths — dp[i][j] = dp[i-1][j] + dp[i][j-1].

Problem Statement

m×n grid, start top-left, end bottom-right. Only right or down moves. Count unique paths.

Signature: int uniquePaths(int m, int n)

Examples

m=3, n=7 → 28
m=3, n=2 → 3

Constraints

  • 1 <= m, n <= 100

Approach Overview

Brute Force

Java: Recursion

Python: Recursion

O(2^(m+n)) O(m+n)

Optimal #1

Java: 1-D DP

Python: 1-D DP

O(m·n) O(n)

Optimal #2

Java: Combinatorial C(m+n-2, m-1)

Python: Combinatorial

O(min(m,n)) O(1)

Java Solutions

Recursion O(2^(m+n)) O(m+n)

Explore all paths.

Pseudo-code

paths(i,j) = paths(i-1,j)+paths(i,j-1); base i==0 or j==0 → 1

Complexity

Time: O(2^(m+n)) — Branching.
Space: O(m+n) — Stack.

Edge cases & gotchas

  • 1×1 → 1.

Java code

class Solution {
    public int uniquePaths(int m, int n) {
        if (m == 1 || n == 1) return 1;
        return uniquePaths(m - 1, n) + uniquePaths(m, n - 1);
    }
}
1-D DP O(m·n) O(n)

Roll one row.

Pseudo-code

dp = [1]*n; repeat m-1 times: for j=1..n-1: dp[j] += dp[j-1]

Complexity

Time: O(m·n) — Fill grid.
Space: O(n) — One row.

Java code

class Solution {
    public int uniquePaths(int m, int n) {
        int[] dp = new int[n];
        java.util.Arrays.fill(dp, 1);
        for (int i = 1; i < m; i++) for (int j = 1; j < n; j++) dp[j] += dp[j - 1];
        return dp[n - 1];
    }
}
Combinatorial C(m+n-2, m-1) O(min(m,n)) O(1)

Closed form.

Pseudo-code

answer = (m+n-2)! / ((m-1)!(n-1)!) computed iteratively

Complexity

Time: O(min(m,n)) — One product.
Space: O(1) — None.

Java code

class Solution {
    public int uniquePaths(int m, int n) {
        long ans = 1;
        int k = Math.min(m - 1, n - 1), N = m + n - 2;
        for (int i = 1; i <= k; i++) ans = ans * (N - k + i) / i;
        return (int) ans;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^(m+n))O(min(m,n))
SpaceO(m+n)O(1)
Difficulty1/54/5
WhenDon'tMath elegance
PE Verdict1-D rolling DP.

Python Solutions

Recursion O(2^(m+n)) O(m+n)

Same.

Pseudo-code

see Java

Complexity

Time: O(2^(m+n)) — Tree.
Space: O(m+n) — Stack.

Python code

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        if m == 1 or n == 1: return 1
        return self.uniquePaths(m-1, n) + self.uniquePaths(m, n-1)
1-D DP O(m·n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(m·n) — Fill.
Space: O(n) — Row.

Python code

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        dp = [1]*n
        for _ in range(1, m):
            for j in range(1, n): dp[j] += dp[j-1]
        return dp[-1]
Combinatorial O(min(m,n)) O(1)

math.comb.

Pseudo-code

see Java

Complexity

Time: O(min(m,n)) — Product.
Space: O(1) — None.

Python code

from math import comb
class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        return comb(m + n - 2, m - 1)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^(m+n))O(min(m,n))
SpaceO(m+n)O(1)
Difficulty1/54/5
WhenDon'tMath
PE Verdict1-D rolling DP.

What the interviewer is really testing

DP warmup; combinatorial form is the flex.

Top gotchas

  • 1×n or m×1 → 1 path.
  • Math identity: paths = C(m+n-2, m-1).
  • 2-D is fine but 1-D is trivial since we only need prev row.

Ship-it

1-D rolling DP.

#112 LC 1143 Medium Longest Common Subsequence

Classic DP: match → 1+diag, else max(top, left).

Problem Statement

Length of longest common subsequence of two strings.

Signature: int longestCommonSubsequence(String text1, String text2)

Examples

'abcde', 'ace' → 3
'abc', 'def' → 0

Constraints

  • 1 <= n, m <= 1000

Approach Overview

Brute Force

Java: Recursion

Python: Recursion

O(2^(n+m)) O(n+m)

Optimal #1

Java: 2-D DP

Python: 2-D DP

O(n·m) O(n·m)

Optimal #2

Java: 1-D rolling DP

Python: 1-D rolling

O(n·m) O(min(n,m))

Java Solutions

Recursion O(2^(n+m)) O(n+m)

Match or skip one side.

Pseudo-code

lcs(i,j): if either ==0: 0; if equal: 1+lcs(i-1,j-1); else max(lcs(i-1,j), lcs(i,j-1))

Complexity

Time: O(2^(n+m)) — Tree.
Space: O(n+m) — Stack.

Java code

class Solution {
    public int longestCommonSubsequence(String a, String b) { return rec(a, b, a.length(), b.length()); }
    private int rec(String a, String b, int i, int j) {
        if (i == 0 || j == 0) return 0;
        if (a.charAt(i-1) == b.charAt(j-1)) return 1 + rec(a, b, i-1, j-1);
        return Math.max(rec(a, b, i-1, j), rec(a, b, i, j-1));
    }
}
2-D DP O(n·m) O(n·m)

Bottom-up table.

Pseudo-code

dp[i][j] = a[i-1]==b[j-1] ? 1+dp[i-1][j-1] : max(dp[i-1][j], dp[i][j-1])

Complexity

Time: O(n·m) — Fill table.
Space: O(n·m) — Table.

Java code

class Solution {
    public int longestCommonSubsequence(String a, String b) {
        int n = a.length(), m = b.length();
        int[][] dp = new int[n + 1][m + 1];
        for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++)
            dp[i][j] = a.charAt(i-1) == b.charAt(j-1) ? dp[i-1][j-1] + 1 : Math.max(dp[i-1][j], dp[i][j-1]);
        return dp[n][m];
    }
}
1-D rolling DP O(n·m) O(min(n,m))

Two rows; keep diag in temp.

Pseudo-code

keep prev row, current row; equal: cur[j]=prev[j-1]+1; else max(prev[j], cur[j-1])

Complexity

Time: O(n·m) — Linear scan.
Space: O(min(n,m)) — One row.

Java code

class Solution {
    public int longestCommonSubsequence(String a, String b) {
        if (b.length() > a.length()) { String t = a; a = b; b = t; }
        int n = a.length(), m = b.length();
        int[] prev = new int[m + 1], cur = new int[m + 1];
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++)
                cur[j] = a.charAt(i-1) == b.charAt(j-1) ? prev[j-1] + 1 : Math.max(prev[j], cur[j-1]);
            int[] t = prev; prev = cur; cur = t;
        }
        return prev[m];
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^(n+m))O(n·m)
SpaceO(n+m)O(min(n,m))
Difficulty2/54/5
WhenDon'tMemory tight
PE Verdict2-D bottom-up DP.

Python Solutions

Recursion O(2^(n+m)) O(n+m)

Same.

Pseudo-code

see Java

Complexity

Time: O(2^(n+m)) — Tree.
Space: O(n+m) — Stack.

Python code

class Solution:
    def longestCommonSubsequence(self, a: str, b: str) -> int:
        def rec(i, j):
            if i == 0 or j == 0: return 0
            if a[i-1] == b[j-1]: return 1 + rec(i-1, j-1)
            return max(rec(i-1, j), rec(i, j-1))
        return rec(len(a), len(b))
2-D DP O(n·m) O(n·m)

Same.

Pseudo-code

see Java

Complexity

Time: O(n·m) — Fill.
Space: O(n·m) — Table.

Python code

class Solution:
    def longestCommonSubsequence(self, a: str, b: str) -> int:
        n, m = len(a), len(b)
        dp = [[0]*(m+1) for _ in range(n+1)]
        for i in range(1, n+1):
            for j in range(1, m+1):
                dp[i][j] = dp[i-1][j-1] + 1 if a[i-1] == b[j-1] else max(dp[i-1][j], dp[i][j-1])
        return dp[n][m]
1-D rolling O(n·m) O(min(n,m))

Same.

Pseudo-code

see Java

Complexity

Time: O(n·m) — Linear.
Space: O(min(n,m)) — Two rows.

Python code

class Solution:
    def longestCommonSubsequence(self, a: str, b: str) -> int:
        if len(b) > len(a): a, b = b, a
        n, m = len(a), len(b)
        prev = [0]*(m+1); cur = [0]*(m+1)
        for i in range(1, n+1):
            for j in range(1, m+1):
                cur[j] = prev[j-1] + 1 if a[i-1] == b[j-1] else max(prev[j], cur[j-1])
            prev, cur = cur, prev
        return prev[m]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^(n+m))O(n·m)
SpaceO(n+m)O(min(n,m))
Difficulty2/54/5
WhenDon'tTight memory
PE Verdict2-D DP.

What the interviewer is really testing

Foundational 2-D DP.

Top gotchas

  • dp dimensions are n+1 by m+1 (pad with zeros).
  • Match: +1 + diagonal. No match: max of top/left.
  • Roll to keep min of n,m on the inner axis.

Ship-it

2-D bottom-up DP.

#113 LC 309 Medium Best Time to Buy and Sell Stock With Cooldown

State machine — hold, sold, rest; transitions among the three.

Problem Statement

Max profit with unlimited transactions but 1-day cooldown after sell.

Signature: int maxProfit(int[] prices)

Examples

[1,2,3,0,2] → 3 (buy0 sell2 cool buy0 sell2)

Constraints

  • 1 <= n <= 5000

Approach Overview

Brute Force

Java: Recursion with state

Python: Recursion

O(2^n) O(n)

Optimal #1

Java: 3-state DP O(1)

Python: 3-state DP

O(n) O(1)

Optimal #2

Java: Memoized recursion

Python: Memoized

O(n) O(n)

Java Solutions

Recursion with state O(2^n) O(n)

Each day: hold or not, can buy or not.

Pseudo-code

rec(i, holding): max(skip, buy/sell)

Complexity

Time: O(2^n) — Branching.
Space: O(n) — Stack.

Java code

class Solution {
    public int maxProfit(int[] p) { return rec(p, 0, false); }
    private int rec(int[] p, int i, boolean hold) {
        if (i >= p.length) return 0;
        int skip = rec(p, i + 1, hold);
        int act = hold ? p[i] + rec(p, i + 2, false) : -p[i] + rec(p, i + 1, true);
        return Math.max(skip, act);
    }
}
3-state DP O(1) O(n) O(1)

hold, sold, rest as rolling vars.

Pseudo-code

hold = max(hold, rest - p); sold = hold + p; rest = max(rest, sold_prev)

Complexity

Time: O(n) — One pass.
Space: O(1) — Three ints.

Java code

class Solution {
    public int maxProfit(int[] p) {
        int hold = Integer.MIN_VALUE, sold = 0, rest = 0;
        for (int x : p) {
            int prevSold = sold;
            sold = hold + x;
            hold = Math.max(hold, rest - x);
            rest = Math.max(rest, prevSold);
        }
        return Math.max(sold, rest);
    }
}
Memoized recursion O(n) O(n)

Top-down with cache.

Pseudo-code

memo[(i, holding)]

Complexity

Time: O(n) — Each state once.
Space: O(n) — Memo.

Java code

class Solution {
    private Integer[][] memo;
    public int maxProfit(int[] p) {
        memo = new Integer[p.length][2];
        return rec(p, 0, 0);
    }
    private int rec(int[] p, int i, int hold) {
        if (i >= p.length) return 0;
        if (memo[i][hold] != null) return memo[i][hold];
        int skip = rec(p, i + 1, hold);
        int act = hold == 1 ? p[i] + rec(p, i + 2, 0) : -p[i] + rec(p, i + 1, 1);
        return memo[i][hold] = Math.max(skip, act);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n)
SpaceO(n)O(n)
Difficulty2/53/5
WhenDon'tTop-down
PE Verdict3-state rolling DP.

Python Solutions

Recursion O(2^n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(2^n) — Tree.
Space: O(n) — Stack.

Python code

from typing import List
class Solution:
    def maxProfit(self, p: List[int]) -> int:
        def rec(i, hold):
            if i >= len(p): return 0
            skip = rec(i+1, hold)
            act = p[i] + rec(i+2, False) if hold else -p[i] + rec(i+1, True)
            return max(skip, act)
        return rec(0, False)
3-state DP O(n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — Three ints.

Python code

from typing import List
class Solution:
    def maxProfit(self, p: List[int]) -> int:
        hold, sold, rest = float('-inf'), 0, 0
        for x in p:
            prev_sold = sold
            sold = hold + x
            hold = max(hold, rest - x)
            rest = max(rest, prev_sold)
        return max(sold, rest)
Memoized O(n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Each state once.
Space: O(n) — Memo.

Python code

from typing import List
from functools import lru_cache
class Solution:
    def maxProfit(self, p: List[int]) -> int:
        @lru_cache(None)
        def rec(i, hold):
            if i >= len(p): return 0
            skip = rec(i+1, hold)
            act = p[i] + rec(i+2, False) if hold else -p[i] + rec(i+1, True)
            return max(skip, act)
        return rec(0, False)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n)
SpaceO(n)O(n)
Difficulty2/53/5
WhenDon'tTop-down
PE Verdict3-state rolling DP.

What the interviewer is really testing

Stock series with cooldown — state machine model.

Top gotchas

  • Three states: hold (own), sold (just sold today), rest (not holding, can buy).
  • Use PREV sold to compute new rest — order matters.
  • Answer is max(sold, rest) at the end.

Ship-it

3-state rolling DP.

#114 LC 518 Medium Coin Change II

Unbounded knapsack — # ways to make amount; iterate coins OUTER (avoid permutations).

Problem Statement

Return number of combinations summing to amount (each coin unlimited).

Signature: int change(int amount, int[] coins)

Examples

amount=5, coins=[1,2,5] → 4 ([5],[2,2,1],[2,1,1,1],[1,1,1,1,1])

Constraints

  • 1 <= coins.length <= 300
  • 0 <= amount <= 5000

Approach Overview

Brute Force

Java: Recursion

Python: Recursion

O(2^(a+n)) O(a+n)

Optimal #1

Java: 1-D DP, coins outer

Python: 1-D DP

O(a·n) O(a)

Optimal #2

Java: 2-D DP

Python: 2-D DP

O(a·n) O(a·n)

Java Solutions

Recursion O(2^(a+n)) O(a+n)

Try take coin (stay) or move on.

Pseudo-code

rec(i, rem): rec(i+1, rem) + rec(i, rem - coins[i]) if rem>=coins[i]

Complexity

Time: O(2^(a+n)) — Branching.
Space: O(a+n) — Stack.

Edge cases & gotchas

  • amount=0 → 1.

Java code

class Solution {
    public int change(int amount, int[] coins) { return rec(coins, 0, amount); }
    private int rec(int[] c, int i, int rem) {
        if (rem == 0) return 1;
        if (i == c.length || rem < 0) return 0;
        return rec(c, i + 1, rem) + rec(c, i, rem - c[i]);
    }
}
1-D DP, coins outer O(a·n) O(a)

dp[rem] += dp[rem - coin] iterating coins outside.

Pseudo-code

dp[0]=1; for c in coins: for r in c..amount: dp[r] += dp[r-c]

Complexity

Time: O(a·n) — Fill array.
Space: O(a) — dp[].

Edge cases & gotchas

  • Order matters — coins outer avoids permutation overcount.

Java code

class Solution {
    public int change(int amount, int[] coins) {
        int[] dp = new int[amount + 1];
        dp[0] = 1;
        for (int c : coins) for (int r = c; r <= amount; r++) dp[r] += dp[r - c];
        return dp[amount];
    }
}
2-D DP O(a·n) O(a·n)

dp[i][r] = # ways using first i coins.

Pseudo-code

dp[i][r] = dp[i-1][r] + dp[i][r - coins[i-1]]

Complexity

Time: O(a·n) — Table.
Space: O(a·n) — Table.

Java code

class Solution {
    public int change(int amount, int[] coins) {
        int n = coins.length;
        int[][] dp = new int[n + 1][amount + 1];
        for (int i = 0; i <= n; i++) dp[i][0] = 1;
        for (int i = 1; i <= n; i++) for (int r = 0; r <= amount; r++) {
            dp[i][r] = dp[i-1][r];
            if (r >= coins[i-1]) dp[i][r] += dp[i][r - coins[i-1]];
        }
        return dp[n][amount];
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^(a+n))O(a·n)
SpaceO(a+n)O(a·n)
Difficulty2/53/5
WhenDon'tPedagogical
PE Verdict1-D DP with coins outer.

Python Solutions

Recursion O(2^(a+n)) O(a+n)

Same.

Pseudo-code

see Java

Complexity

Time: O(2^(a+n)) — Tree.
Space: O(a+n) — Stack.

Python code

from typing import List
class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        def rec(i, rem):
            if rem == 0: return 1
            if i == len(coins) or rem < 0: return 0
            return rec(i+1, rem) + rec(i, rem - coins[i])
        return rec(0, amount)
1-D DP O(a·n) O(a)

Same.

Pseudo-code

see Java

Complexity

Time: O(a·n) — Fill.
Space: O(a) — Array.

Python code

from typing import List
class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        dp = [0]*(amount + 1); dp[0] = 1
        for c in coins:
            for r in range(c, amount + 1): dp[r] += dp[r - c]
        return dp[amount]
2-D DP O(a·n) O(a·n)

Same.

Pseudo-code

see Java

Complexity

Time: O(a·n) — Table.
Space: O(a·n) — Table.

Python code

from typing import List
class Solution:
    def change(self, amount: int, coins: List[int]) -> int:
        n = len(coins)
        dp = [[0]*(amount+1) for _ in range(n+1)]
        for i in range(n+1): dp[i][0] = 1
        for i in range(1, n+1):
            for r in range(amount + 1):
                dp[i][r] = dp[i-1][r]
                if r >= coins[i-1]: dp[i][r] += dp[i][r - coins[i-1]]
        return dp[n][amount]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^(a+n))O(a·n)
SpaceO(a+n)O(a·n)
Difficulty2/53/5
WhenDon'tPedagogical
PE Verdict1-D DP, coins outer.

What the interviewer is really testing

Subtle distinction: combinations vs permutations.

Top gotchas

  • COINS LOOP MUST BE OUTSIDE the amount loop. Reversing causes permutation overcount.
  • dp[0] = 1: one way to make 0 (use no coins).
  • Unbounded knapsack: iterate amounts in forward direction.

Ship-it

1-D DP, coins outer.

#115 LC 494 Medium Target Sum

Subset sum reduction — count subsets with sum = (S+target)/2.

Problem Statement

Each nums[i] gets +/-; count expressions equaling target.

Signature: int findTargetSumWays(int[] nums, int target)

Examples

[1,1,1,1,1], target=3 → 5

Constraints

  • 1 <= n <= 20
  • 0 <= sum <= 1000

Approach Overview

Brute Force

Java: Recursion

Python: Recursion

O(2^n) O(n)

Optimal #1

Java: Subset-sum DP

Python: Subset-sum DP

O(n·S) O(S)

Optimal #2

Java: Memoized recursion (offset sum)

Python: Memoized

O(n·S) O(n·S)

Java Solutions

Recursion O(2^n) O(n)

Try +/- at each index.

Pseudo-code

rec(i, sum): rec(i+1, sum+nums[i]) + rec(i+1, sum-nums[i])

Complexity

Time: O(2^n) — Branching.
Space: O(n) — Stack.

Java code

class Solution {
    public int findTargetSumWays(int[] nums, int target) { return rec(nums, 0, 0, target); }
    private int rec(int[] n, int i, int sum, int target) {
        if (i == n.length) return sum == target ? 1 : 0;
        return rec(n, i+1, sum + n[i], target) + rec(n, i+1, sum - n[i], target);
    }
}
Subset-sum DP O(n·S) O(S)

Find subset with sum P where P-(S-P)=target → P=(S+target)/2.

Pseudo-code

if (S+target) odd or |target|>S: 0; else count subsets summing to (S+target)/2

Complexity

Time: O(n·S) — Fill array.
Space: O(S) — dp[].

Edge cases & gotchas

  • |target|>S → 0.

Java code

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        int sum = 0; for (int x : nums) sum += x;
        if (Math.abs(target) > sum || ((sum + target) & 1) != 0) return 0;
        int p = (sum + target) / 2;
        int[] dp = new int[p + 1]; dp[0] = 1;
        for (int x : nums) for (int t = p; t >= x; t--) dp[t] += dp[t - x];
        return dp[p];
    }
}
Memoized recursion (offset sum) O(n·S) O(n·S)

Top-down with (i, sum) memo.

Pseudo-code

map (i, runningSum) → count

Complexity

Time: O(n·S) — Each state once.
Space: O(n·S) — Memo.

Java code

import java.util.*;
class Solution {
    private Map<Long, Integer> memo;
    public int findTargetSumWays(int[] nums, int target) { memo = new HashMap<>(); return rec(nums, 0, 0, target); }
    private int rec(int[] n, int i, int sum, int target) {
        if (i == n.length) return sum == target ? 1 : 0;
        long key = (long) i * 10001 + sum + 5000;
        if (memo.containsKey(key)) return memo.get(key);
        int r = rec(n, i+1, sum + n[i], target) + rec(n, i+1, sum - n[i], target);
        memo.put(key, r);
        return r;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n·S)
SpaceO(n)O(n·S)
Difficulty2/53/5
WhenDon'tTop-down
PE VerdictReduce to subset-sum DP.

Python Solutions

Recursion O(2^n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(2^n) — Tree.
Space: O(n) — Stack.

Python code

from typing import List
class Solution:
    def findTargetSumWays(self, nums: List[int], target: int) -> int:
        def rec(i, s):
            if i == len(nums): return 1 if s == target else 0
            return rec(i+1, s + nums[i]) + rec(i+1, s - nums[i])
        return rec(0, 0)
Subset-sum DP O(n·S) O(S)

Same.

Pseudo-code

see Java

Complexity

Time: O(n·S) — Fill.
Space: O(S) — Array.

Python code

from typing import List
class Solution:
    def findTargetSumWays(self, nums: List[int], target: int) -> int:
        total = sum(nums)
        if abs(target) > total or (total + target) % 2: return 0
        p = (total + target) // 2
        dp = [0]*(p + 1); dp[0] = 1
        for x in nums:
            for t in range(p, x - 1, -1): dp[t] += dp[t - x]
        return dp[p]
Memoized O(n·S) O(n·S)

Same.

Pseudo-code

see Java

Complexity

Time: O(n·S) — Each state once.
Space: O(n·S) — Memo.

Python code

from typing import List
from functools import lru_cache
class Solution:
    def findTargetSumWays(self, nums: List[int], target: int) -> int:
        @lru_cache(None)
        def rec(i, s):
            if i == len(nums): return 1 if s == target else 0
            return rec(i+1, s + nums[i]) + rec(i+1, s - nums[i])
        return rec(0, 0)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n·S)
SpaceO(n)O(n·S)
Difficulty2/53/5
WhenDon'tTop-down
PE VerdictSubset-sum reduction.

What the interviewer is really testing

The math reduction P=(S+t)/2 is what makes this interesting.

Top gotchas

  • If (S+target) is odd → 0 ways.
  • |target| > S → 0 ways.
  • Standard 0/1 knapsack: iterate t DOWN.

Ship-it

Subset-sum DP.

#116 LC 97 Medium Interleaving String

2-D DP — dp[i][j] = can s3[0..i+j] be formed from s1[0..i] + s2[0..j]?

Problem Statement

Return true if s3 is an interleaving of s1 and s2.

Signature: boolean isInterleave(String s1, String s2, String s3)

Examples

s1='aabcc', s2='dbbca', s3='aadbbcbcac' → true

Constraints

  • 0 <= len(s1), len(s2) <= 100

Approach Overview

Brute Force

Java: Recursion

Python: Recursion

O(2^(n+m)) O(n+m)

Optimal #1

Java: 2-D DP

Python: 2-D DP

O(n·m) O(n·m)

Optimal #2

Java: 1-D rolling DP

Python: 1-D rolling

O(n·m) O(min(n,m))

Java Solutions

Recursion O(2^(n+m)) O(n+m)

At each step try consuming from s1 or s2.

Pseudo-code

rec(i,j): k=i+j; (s1[i]==s3[k] && rec(i+1,j)) || (s2[j]==s3[k] && rec(i,j+1))

Complexity

Time: O(2^(n+m)) — Branching.
Space: O(n+m) — Stack.

Edge cases & gotchas

  • len mismatch → false.

Java code

class Solution {
    public boolean isInterleave(String s1, String s2, String s3) {
        if (s1.length() + s2.length() != s3.length()) return false;
        return rec(s1, s2, s3, 0, 0);
    }
    private boolean rec(String a, String b, String c, int i, int j) {
        int k = i + j;
        if (k == c.length()) return true;
        boolean ok = false;
        if (i < a.length() && a.charAt(i) == c.charAt(k)) ok = rec(a, b, c, i+1, j);
        if (!ok && j < b.length() && b.charAt(j) == c.charAt(k)) ok = rec(a, b, c, i, j+1);
        return ok;
    }
}
2-D DP O(n·m) O(n·m)

dp[i][j] = can interleave prefix of s1 (i chars) and s2 (j chars).

Pseudo-code

dp[i][j] = (dp[i-1][j] && s1[i-1]==s3[i+j-1]) || (dp[i][j-1] && s2[j-1]==s3[i+j-1])

Complexity

Time: O(n·m) — Fill table.
Space: O(n·m) — Table.

Java code

class Solution {
    public boolean isInterleave(String s1, String s2, String s3) {
        int n = s1.length(), m = s2.length();
        if (n + m != s3.length()) return false;
        boolean[][] dp = new boolean[n + 1][m + 1];
        dp[0][0] = true;
        for (int i = 1; i <= n; i++) dp[i][0] = dp[i-1][0] && s1.charAt(i-1) == s3.charAt(i-1);
        for (int j = 1; j <= m; j++) dp[0][j] = dp[0][j-1] && s2.charAt(j-1) == s3.charAt(j-1);
        for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++)
            dp[i][j] = (dp[i-1][j] && s1.charAt(i-1) == s3.charAt(i+j-1))
                    || (dp[i][j-1] && s2.charAt(j-1) == s3.charAt(i+j-1));
        return dp[n][m];
    }
}
1-D rolling DP O(n·m) O(min(n,m))

Keep one row.

Pseudo-code

row[j] = (row[j] && s1[i-1]==s3[i+j-1]) || (row[j-1] && s2[j-1]==s3[i+j-1])

Complexity

Time: O(n·m) — Linear.
Space: O(min(n,m)) — Row.

Java code

class Solution {
    public boolean isInterleave(String s1, String s2, String s3) {
        int n = s1.length(), m = s2.length();
        if (n + m != s3.length()) return false;
        if (m < n) { String t = s1; s1 = s2; s2 = t; int u = n; n = m; m = u; }
        boolean[] dp = new boolean[m + 1];
        dp[0] = true;
        for (int j = 1; j <= m; j++) dp[j] = dp[j-1] && s2.charAt(j-1) == s3.charAt(j-1);
        for (int i = 1; i <= n; i++) {
            dp[0] = dp[0] && s1.charAt(i-1) == s3.charAt(i-1);
            for (int j = 1; j <= m; j++)
                dp[j] = (dp[j] && s1.charAt(i-1) == s3.charAt(i+j-1))
                     || (dp[j-1] && s2.charAt(j-1) == s3.charAt(i+j-1));
        }
        return dp[m];
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^(n+m))O(n·m)
SpaceO(n+m)O(min(n,m))
Difficulty3/54/5
WhenDon'tTight memory
PE Verdict2-D DP.

Python Solutions

Recursion O(2^(n+m)) O(n+m)

Same.

Pseudo-code

see Java

Complexity

Time: O(2^(n+m)) — Tree.
Space: O(n+m) — Stack.

Python code

class Solution:
    def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
        if len(s1) + len(s2) != len(s3): return False
        def rec(i, j):
            k = i + j
            if k == len(s3): return True
            if i < len(s1) and s1[i] == s3[k] and rec(i+1, j): return True
            if j < len(s2) and s2[j] == s3[k] and rec(i, j+1): return True
            return False
        return rec(0, 0)
2-D DP O(n·m) O(n·m)

Same.

Pseudo-code

see Java

Complexity

Time: O(n·m) — Fill.
Space: O(n·m) — Table.

Python code

class Solution:
    def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
        n, m = len(s1), len(s2)
        if n + m != len(s3): return False
        dp = [[False]*(m+1) for _ in range(n+1)]
        dp[0][0] = True
        for i in range(1, n+1): dp[i][0] = dp[i-1][0] and s1[i-1] == s3[i-1]
        for j in range(1, m+1): dp[0][j] = dp[0][j-1] and s2[j-1] == s3[j-1]
        for i in range(1, n+1):
            for j in range(1, m+1):
                dp[i][j] = (dp[i-1][j] and s1[i-1] == s3[i+j-1]) or (dp[i][j-1] and s2[j-1] == s3[i+j-1])
        return dp[n][m]
1-D rolling O(n·m) O(min(n,m))

Same.

Pseudo-code

see Java

Complexity

Time: O(n·m) — Linear.
Space: O(min(n,m)) — Row.

Python code

class Solution:
    def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
        if len(s1) + len(s2) != len(s3): return False
        if len(s2) < len(s1): s1, s2 = s2, s1
        n, m = len(s1), len(s2)
        dp = [False]*(m+1); dp[0] = True
        for j in range(1, m+1): dp[j] = dp[j-1] and s2[j-1] == s3[j-1]
        for i in range(1, n+1):
            dp[0] = dp[0] and s1[i-1] == s3[i-1]
            for j in range(1, m+1):
                dp[j] = (dp[j] and s1[i-1] == s3[i+j-1]) or (dp[j-1] and s2[j-1] == s3[i+j-1])
        return dp[m]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^(n+m))O(n·m)
SpaceO(n+m)O(min(n,m))
Difficulty3/54/5
WhenDon'tTight memory
PE Verdict2-D DP.

What the interviewer is really testing

2-D state from two strings.

Top gotchas

  • Length must match: n+m == len(s3).
  • Two choices at each cell: take from s1 OR s2 if char matches.
  • Roll to 1-D if memory matters.

Ship-it

2-D DP.

#117 LC 329 Hard Longest Increasing Path in a Matrix

DFS + memoization — implicit DAG (edges only point upward in value).

Problem Statement

Length of longest strictly increasing path in matrix.

Signature: int longestIncreasingPath(int[][] matrix)

Examples

[[9,9,4],[6,6,8],[2,1,1]] → 4 ([1,2,6,9])

Constraints

  • 1 <= m, n <= 200

Approach Overview

Brute Force

Java: DFS without memo

Python: DFS no memo

O(4^(m·n)) O(m·n)

Optimal #1

Java: DFS + memo

Python: DFS + memo

O(m·n) O(m·n)

Optimal #2

Java: Topological sort by indegree

Python: Topo sort

O(m·n) O(m·n)

Java Solutions

DFS without memo O(4^(m·n)) O(m·n)

Exponential paths.

Pseudo-code

dfs(i,j): max(1 + dfs(nb)) for nb with greater value

Complexity

Time: O(4^(m·n)) — Branching.
Space: O(m·n) — Stack.

Java code

class Solution {
    public int longestIncreasingPath(int[][] m) {
        int best = 0;
        for (int i = 0; i < m.length; i++) for (int j = 0; j < m[0].length; j++)
            best = Math.max(best, dfs(m, i, j, Integer.MIN_VALUE));
        return best;
    }
    private int dfs(int[][] m, int i, int j, int prev) {
        if (i<0||j<0||i>=m.length||j>=m[0].length||m[i][j] <= prev) return 0;
        int v = m[i][j];
        return 1 + Math.max(Math.max(dfs(m,i+1,j,v), dfs(m,i-1,j,v)), Math.max(dfs(m,i,j+1,v), dfs(m,i,j-1,v)));
    }
}
DFS + memo O(m·n) O(m·n)

Cache longest path starting at each cell.

Pseudo-code

dfs(i,j): if memo set return; else compute 1+max(neighbors strictly greater); store; return

Complexity

Time: O(m·n) — Each cell once.
Space: O(m·n) — Memo.

Java code

class Solution {
    private int[][] memo;
    public int longestIncreasingPath(int[][] m) {
        memo = new int[m.length][m[0].length];
        int best = 0;
        for (int i = 0; i < m.length; i++) for (int j = 0; j < m[0].length; j++)
            best = Math.max(best, dfs(m, i, j));
        return best;
    }
    private int dfs(int[][] m, int i, int j) {
        if (memo[i][j] != 0) return memo[i][j];
        int best = 1;
        int[][] D = {{-1,0},{1,0},{0,-1},{0,1}};
        for (int[] d : D) {
            int ni = i+d[0], nj = j+d[1];
            if (ni>=0&&nj>=0&&ni<m.length&&nj<m[0].length&&m[ni][nj]>m[i][j])
                best = Math.max(best, 1 + dfs(m, ni, nj));
        }
        return memo[i][j] = best;
    }
}
Topological sort by indegree O(m·n) O(m·n)

Edges from smaller→greater; Kahn's BFS layers = longest path.

Pseudo-code

indeg = # smaller neighbors; enqueue 0s; layer-by-layer; max layer

Complexity

Time: O(m·n) — Linear.
Space: O(m·n) — Indeg.

Java code

import java.util.*;
class Solution {
    public int longestIncreasingPath(int[][] m) {
        int M = m.length, N = m[0].length;
        int[][] indeg = new int[M][N];
        int[][] D = {{-1,0},{1,0},{0,-1},{0,1}};
        for (int i = 0; i < M; i++) for (int j = 0; j < N; j++) for (int[] d : D) {
            int ni = i+d[0], nj = j+d[1];
            if (ni>=0&&nj>=0&&ni<M&&nj<N&&m[ni][nj]<m[i][j]) indeg[i][j]++;
        }
        Deque<int[]> q = new ArrayDeque<>();
        for (int i = 0; i < M; i++) for (int j = 0; j < N; j++) if (indeg[i][j] == 0) q.offer(new int[]{i,j});
        int layers = 0;
        while (!q.isEmpty()) {
            layers++;
            int sz = q.size();
            for (int k = 0; k < sz; k++) {
                int[] p = q.poll();
                for (int[] d : D) {
                    int ni = p[0]+d[0], nj = p[1]+d[1];
                    if (ni>=0&&nj>=0&&ni<M&&nj<N&&m[ni][nj]>m[p[0]][p[1]] && --indeg[ni][nj] == 0) q.offer(new int[]{ni,nj});
                }
            }
        }
        return layers;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(4^(mn))O(m·n)
SpaceO(m·n)O(m·n)
Difficulty2/54/5
WhenDon'tIterative
PE VerdictDFS + memo.

Python Solutions

DFS no memo O(4^(mn)) O(m·n)

Same.

Pseudo-code

see Java

Complexity

Time: O(4^(mn)) — Branching.
Space: O(m·n) — Stack.

Python code

from typing import List
class Solution:
    def longestIncreasingPath(self, m: List[List[int]]) -> int:
        M, N = len(m), len(m[0])
        def dfs(i, j, prev):
            if not (0 <= i < M and 0 <= j < N) or m[i][j] <= prev: return 0
            v = m[i][j]
            return 1 + max(dfs(i+1,j,v), dfs(i-1,j,v), dfs(i,j+1,v), dfs(i,j-1,v))
        return max(dfs(i,j,float('-inf')) for i in range(M) for j in range(N))
DFS + memo O(m·n) O(m·n)

Same.

Pseudo-code

see Java

Complexity

Time: O(m·n) — Each cell once.
Space: O(m·n) — Memo.

Python code

from typing import List
from functools import lru_cache
class Solution:
    def longestIncreasingPath(self, m: List[List[int]]) -> int:
        M, N = len(m), len(m[0])
        @lru_cache(None)
        def dfs(i, j):
            best = 1
            for di, dj in [(-1,0),(1,0),(0,-1),(0,1)]:
                ni, nj = i+di, j+dj
                if 0<=ni<M and 0<=nj<N and m[ni][nj] > m[i][j]:
                    best = max(best, 1 + dfs(ni, nj))
            return best
        return max(dfs(i, j) for i in range(M) for j in range(N))
Topo sort O(m·n) O(m·n)

Same.

Pseudo-code

see Java

Complexity

Time: O(m·n) — Linear.
Space: O(m·n) — Indeg.

Python code

from typing import List
from collections import deque
class Solution:
    def longestIncreasingPath(self, m: List[List[int]]) -> int:
        M, N = len(m), len(m[0])
        indeg = [[0]*N for _ in range(M)]
        D = [(-1,0),(1,0),(0,-1),(0,1)]
        for i in range(M):
            for j in range(N):
                for di, dj in D:
                    ni, nj = i+di, j+dj
                    if 0<=ni<M and 0<=nj<N and m[ni][nj] < m[i][j]: indeg[i][j] += 1
        q = deque((i,j) for i in range(M) for j in range(N) if indeg[i][j] == 0)
        layers = 0
        while q:
            layers += 1
            for _ in range(len(q)):
                i, j = q.popleft()
                for di, dj in D:
                    ni, nj = i+di, j+dj
                    if 0<=ni<M and 0<=nj<N and m[ni][nj] > m[i][j]:
                        indeg[ni][nj] -= 1
                        if indeg[ni][nj] == 0: q.append((ni, nj))
        return layers

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(4^(mn))O(m·n)
SpaceO(m·n)O(m·n)
Difficulty2/54/5
WhenDon'tIterative
PE VerdictDFS + memo.

What the interviewer is really testing

Grid as implicit DAG (no cycles since strictly greater).

Top gotchas

  • No visited set needed — strict monotonicity prevents cycles.
  • Memoize STARTING longest path per cell.
  • Topo variant counts layers = longest path length.

Ship-it

DFS + memo.

#118 LC 115 Hard Distinct Subsequences

2-D DP — match: take + skip; mismatch: skip only.

Problem Statement

Count distinct subsequences of s that equal t.

Signature: int numDistinct(String s, String t)

Examples

s='rabbbit', t='rabbit' → 3

Constraints

  • 1 <= len(s), len(t) <= 1000

Approach Overview

Brute Force

Java: Recursion

Python: Recursion

O(2^n) O(n)

Optimal #1

Java: 2-D DP

Python: 2-D DP

O(n·m) O(n·m)

Optimal #2

Java: 1-D rolling DP

Python: 1-D rolling

O(n·m) O(m)

Java Solutions

Recursion O(2^n) O(n)

Try match or skip s[i].

Pseudo-code

rec(i,j): if j==len(t) return 1; if i==len(s) return 0; res = rec(i+1,j); if equal: res += rec(i+1,j+1)

Complexity

Time: O(2^n) — Tree.
Space: O(n) — Stack.

Edge cases & gotchas

  • t empty → 1.

Java code

class Solution {
    public int numDistinct(String s, String t) { return rec(s, t, 0, 0); }
    private int rec(String s, String t, int i, int j) {
        if (j == t.length()) return 1;
        if (i == s.length()) return 0;
        int r = rec(s, t, i+1, j);
        if (s.charAt(i) == t.charAt(j)) r += rec(s, t, i+1, j+1);
        return r;
    }
}
2-D DP O(n·m) O(n·m)

dp[i][j] = # ways to form t[j..] from s[i..].

Pseudo-code

fill bottom-up; equal: dp[i][j]=dp[i+1][j+1]+dp[i+1][j]; else dp[i+1][j]

Complexity

Time: O(n·m) — Fill table.
Space: O(n·m) — Table.

Java code

class Solution {
    public int numDistinct(String s, String t) {
        int n = s.length(), m = t.length();
        int[][] dp = new int[n + 1][m + 1];
        for (int i = 0; i <= n; i++) dp[i][m] = 1;
        for (int i = n - 1; i >= 0; i--) for (int j = m - 1; j >= 0; j--) {
            dp[i][j] = dp[i+1][j];
            if (s.charAt(i) == t.charAt(j)) dp[i][j] += dp[i+1][j+1];
        }
        return dp[0][0];
    }
}
1-D rolling DP O(n·m) O(m)

Iterate t right-to-left to avoid overwrite.

Pseudo-code

dp[m]=1; for each s[i]: for j=0..m-1: if s[i]==t[j]: dp[j] += dp[j+1]

Complexity

Time: O(n·m) — Fill.
Space: O(m) — Row.

Java code

class Solution {
    public int numDistinct(String s, String t) {
        int n = s.length(), m = t.length();
        int[] dp = new int[m + 1]; dp[m] = 1;
        for (int i = n - 1; i >= 0; i--)
            for (int j = 0; j < m; j++)
                if (s.charAt(i) == t.charAt(j)) dp[j] += dp[j+1];
        return dp[0];
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n·m)
SpaceO(n)O(m)
Difficulty2/54/5
WhenDon'tMemory tight
PE Verdict2-D DP bottom-up.

Python Solutions

Recursion O(2^n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(2^n) — Tree.
Space: O(n) — Stack.

Python code

class Solution:
    def numDistinct(self, s: str, t: str) -> int:
        def rec(i, j):
            if j == len(t): return 1
            if i == len(s): return 0
            r = rec(i+1, j)
            if s[i] == t[j]: r += rec(i+1, j+1)
            return r
        return rec(0, 0)
2-D DP O(n·m) O(n·m)

Same.

Pseudo-code

see Java

Complexity

Time: O(n·m) — Fill.
Space: O(n·m) — Table.

Python code

class Solution:
    def numDistinct(self, s: str, t: str) -> int:
        n, m = len(s), len(t)
        dp = [[0]*(m+1) for _ in range(n+1)]
        for i in range(n+1): dp[i][m] = 1
        for i in range(n-1, -1, -1):
            for j in range(m-1, -1, -1):
                dp[i][j] = dp[i+1][j]
                if s[i] == t[j]: dp[i][j] += dp[i+1][j+1]
        return dp[0][0]
1-D rolling O(n·m) O(m)

Same.

Pseudo-code

see Java

Complexity

Time: O(n·m) — Fill.
Space: O(m) — Row.

Python code

class Solution:
    def numDistinct(self, s: str, t: str) -> int:
        n, m = len(s), len(t)
        dp = [0]*(m+1); dp[m] = 1
        for i in range(n-1, -1, -1):
            for j in range(m):
                if s[i] == t[j]: dp[j] += dp[j+1]
        return dp[0]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n·m)
SpaceO(n)O(m)
Difficulty2/54/5
WhenDon'tTight
PE Verdict2-D DP.

What the interviewer is really testing

Subsequence DP — careful with 'take' decision.

Top gotchas

  • Mismatch: only the skip branch counts. Match: skip + take.
  • Base: dp[*][m] = 1 — empty t is a subsequence.
  • Roll right-to-left over j to keep 1-D correct.

Ship-it

2-D DP.

#119 LC 72 Medium Edit Distance

Levenshtein DP — match: diag; else 1 + min(insert, delete, replace).

Problem Statement

Min operations (insert/delete/replace) to transform word1 to word2.

Signature: int minDistance(String word1, String word2)

Examples

'horse'→'ros' → 3
'intention'→'execution' → 5

Constraints

  • 0 <= n, m <= 500

Approach Overview

Brute Force

Java: Recursion

Python: Recursion

O(3^(n+m)) O(n+m)

Optimal #1

Java: 2-D DP

Python: 2-D DP

O(n·m) O(n·m)

Optimal #2

Java: 1-D rolling

Python: 1-D rolling

O(n·m) O(min(n,m))

Java Solutions

Recursion O(3^(n+m)) O(n+m)

Try each op.

Pseudo-code

rec(i,j): if equal: rec(i+1,j+1) else 1+min(insert,delete,replace)

Complexity

Time: O(3^(n+m)) — Branching.
Space: O(n+m) — Stack.

Edge cases & gotchas

  • Either empty → length of other.

Java code

class Solution {
    public int minDistance(String a, String b) { return rec(a, b, 0, 0); }
    private int rec(String a, String b, int i, int j) {
        if (i == a.length()) return b.length() - j;
        if (j == b.length()) return a.length() - i;
        if (a.charAt(i) == b.charAt(j)) return rec(a, b, i+1, j+1);
        int ins = rec(a, b, i, j+1);
        int del = rec(a, b, i+1, j);
        int rep = rec(a, b, i+1, j+1);
        return 1 + Math.min(ins, Math.min(del, rep));
    }
}
2-D DP O(n·m) O(n·m)

Bottom-up table.

Pseudo-code

dp[i][j] = min cost transforming a[0..i] to b[0..j]

Complexity

Time: O(n·m) — Fill.
Space: O(n·m) — Table.

Java code

class Solution {
    public int minDistance(String a, String b) {
        int n = a.length(), m = b.length();
        int[][] dp = new int[n + 1][m + 1];
        for (int i = 0; i <= n; i++) dp[i][0] = i;
        for (int j = 0; j <= m; j++) dp[0][j] = j;
        for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) {
            if (a.charAt(i-1) == b.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[n][m];
    }
}
1-D rolling O(n·m) O(min(n,m))

Keep one row.

Pseudo-code

keep prev row; track diag in temp

Complexity

Time: O(n·m) — Fill.
Space: O(min(n,m)) — Row.

Java code

class Solution {
    public int minDistance(String a, String b) {
        if (a.length() < b.length()) { String t = a; a = b; b = t; }
        int n = a.length(), m = b.length();
        int[] dp = new int[m + 1];
        for (int j = 0; j <= m; j++) dp[j] = j;
        for (int i = 1; i <= n; i++) {
            int prev = dp[0]; dp[0] = i;
            for (int j = 1; j <= m; j++) {
                int temp = dp[j];
                if (a.charAt(i-1) == b.charAt(j-1)) dp[j] = prev;
                else dp[j] = 1 + Math.min(prev, Math.min(dp[j], dp[j-1]));
                prev = temp;
            }
        }
        return dp[m];
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(3^(n+m))O(n·m)
SpaceO(n+m)O(min(n,m))
Difficulty2/54/5
WhenDon'tTight memory
PE Verdict2-D DP.

Python Solutions

Recursion O(3^(n+m)) O(n+m)

Same.

Pseudo-code

see Java

Complexity

Time: O(3^(n+m)) — Tree.
Space: O(n+m) — Stack.

Python code

class Solution:
    def minDistance(self, a: str, b: str) -> int:
        def rec(i, j):
            if i == len(a): return len(b) - j
            if j == len(b): return len(a) - i
            if a[i] == b[j]: return rec(i+1, j+1)
            return 1 + min(rec(i, j+1), rec(i+1, j), rec(i+1, j+1))
        return rec(0, 0)
2-D DP O(n·m) O(n·m)

Same.

Pseudo-code

see Java

Complexity

Time: O(n·m) — Fill.
Space: O(n·m) — Table.

Python code

class Solution:
    def minDistance(self, a: str, b: str) -> int:
        n, m = len(a), len(b)
        dp = [[0]*(m+1) for _ in range(n+1)]
        for i in range(n+1): dp[i][0] = i
        for j in range(m+1): dp[0][j] = j
        for i in range(1, n+1):
            for j in range(1, m+1):
                if a[i-1] == b[j-1]: dp[i][j] = dp[i-1][j-1]
                else: dp[i][j] = 1 + min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1])
        return dp[n][m]
1-D rolling O(n·m) O(min(n,m))

Same.

Pseudo-code

see Java

Complexity

Time: O(n·m) — Fill.
Space: O(min(n,m)) — Row.

Python code

class Solution:
    def minDistance(self, a: str, b: str) -> int:
        if len(a) < len(b): a, b = b, a
        n, m = len(a), len(b)
        dp = list(range(m+1))
        for i in range(1, n+1):
            prev = dp[0]; dp[0] = i
            for j in range(1, m+1):
                temp = dp[j]
                if a[i-1] == b[j-1]: dp[j] = prev
                else: dp[j] = 1 + min(prev, dp[j], dp[j-1])
                prev = temp
        return dp[m]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(3^(n+m))O(n·m)
SpaceO(n+m)O(min(n,m))
Difficulty2/54/5
WhenDon'tTight
PE Verdict2-D DP.

What the interviewer is really testing

Classic Levenshtein DP.

Top gotchas

  • 3 operations → 3 transitions: dp[i-1][j-1] (replace), dp[i-1][j] (delete), dp[i][j-1] (insert).
  • Base: empty string → length of other.
  • Equal chars → free diagonal.

Ship-it

2-D DP.

#120 LC 312 Hard Burst Balloons

Interval DP — pick LAST balloon to burst in each subinterval.

Problem Statement

Burst balloon i gets nums[l]·nums[i]·nums[r] coins (l,r = nearest unburst neighbors). Max total coins.

Signature: int maxCoins(int[] nums)

Examples

[3,1,5,8] → 167

Constraints

  • 1 <= n <= 300

Approach Overview

Brute Force

Java: Recursion try each next

Python: Recursion

O(n!) O(n)

Optimal #1

Java: Interval DP — last burst

Python: Interval DP

O(n³) O(n²)

Optimal #2

Java: Memoized recursion

Python: Memoized

O(n³) O(n²)

Java Solutions

Recursion try each next O(n!) O(n)

Pick which to burst next; recurse.

Pseudo-code

for each balloon: burst, recurse on remaining

Complexity

Time: O(n!) — Permutations.
Space: O(n) — Stack.

Java code

import java.util.*;
class Solution {
    public int maxCoins(int[] nums) {
        List<Integer> arr = new ArrayList<>();
        arr.add(1);
        for (int x : nums) arr.add(x);
        arr.add(1);
        return rec(arr);
    }
    private int rec(List<Integer> arr) {
        if (arr.size() == 2) return 0;
        int best = 0;
        for (int i = 1; i < arr.size() - 1; i++) {
            int coins = arr.get(i-1) * arr.get(i) * arr.get(i+1);
            int v = arr.remove(i);
            best = Math.max(best, coins + rec(arr));
            arr.add(i, v);
        }
        return best;
    }
}
Interval DP — last burst O(n³) O(n²)

dp[l][r] = pick last k in (l,r); coins = nums[l]·nums[k]·nums[r] + dp[l][k] + dp[k][r].

Pseudo-code

pad with 1s; for length=2..n+1: for l: r=l+len; for k=l+1..r-1: dp[l][r]=max(dp[l][r], nums[l]*nums[k]*nums[r] + dp[l][k] + dp[k][r])

Complexity

Time: O(n³) — Three nested.
Space: O(n²) — Table.

Edge cases & gotchas

  • n=1 → nums[0].

Java code

class Solution {
    public int maxCoins(int[] nums) {
        int n = nums.length;
        int[] arr = new int[n + 2];
        arr[0] = arr[n + 1] = 1;
        for (int i = 0; i < n; i++) arr[i + 1] = nums[i];
        int[][] dp = new int[n + 2][n + 2];
        for (int len = 2; len <= n + 1; len++) {
            for (int l = 0; l + len <= n + 1; l++) {
                int r = l + len;
                for (int k = l + 1; k < r; k++)
                    dp[l][r] = Math.max(dp[l][r], arr[l] * arr[k] * arr[r] + dp[l][k] + dp[k][r]);
            }
        }
        return dp[0][n + 1];
    }
}
Memoized recursion O(n³) O(n²)

Top-down same idea.

Pseudo-code

rec(l, r): for k in (l, r): max(arr[l]*arr[k]*arr[r] + rec(l,k) + rec(k,r))

Complexity

Time: O(n³) — Each interval once.
Space: O(n²) — Memo.

Java code

class Solution {
    private int[] arr;
    private int[][] memo;
    public int maxCoins(int[] nums) {
        int n = nums.length;
        arr = new int[n + 2]; arr[0] = arr[n + 1] = 1;
        for (int i = 0; i < n; i++) arr[i + 1] = nums[i];
        memo = new int[n + 2][n + 2];
        return rec(0, n + 1);
    }
    private int rec(int l, int r) {
        if (r - l < 2) return 0;
        if (memo[l][r] != 0) return memo[l][r];
        int best = 0;
        for (int k = l + 1; k < r; k++)
            best = Math.max(best, arr[l] * arr[k] * arr[r] + rec(l, k) + rec(k, r));
        return memo[l][r] = best;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n!)O(n³)
SpaceO(n)O(n²)
Difficulty3/54/5
WhenDon'tTop-down clarity
PE VerdictInterval DP, last-burst trick.

Python Solutions

Recursion O(n!) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n!) — Tree.
Space: O(n) — Stack.

Python code

from typing import List
class Solution:
    def maxCoins(self, nums: List[int]) -> int:
        arr = [1] + nums + [1]
        def rec(a):
            if len(a) == 2: return 0
            best = 0
            for i in range(1, len(a) - 1):
                coins = a[i-1] * a[i] * a[i+1]
                best = max(best, coins + rec(a[:i] + a[i+1:]))
            return best
        return rec(arr)
Interval DP O(n³) O(n²)

Same.

Pseudo-code

see Java

Complexity

Time: O(n³) — Triple loop.
Space: O(n²) — Table.

Python code

from typing import List
class Solution:
    def maxCoins(self, nums: List[int]) -> int:
        arr = [1] + nums + [1]
        n = len(arr)
        dp = [[0]*n for _ in range(n)]
        for length in range(2, n):
            for l in range(n - length):
                r = l + length
                for k in range(l + 1, r):
                    dp[l][r] = max(dp[l][r], arr[l]*arr[k]*arr[r] + dp[l][k] + dp[k][r])
        return dp[0][n-1]
Memoized O(n³) O(n²)

Same.

Pseudo-code

see Java

Complexity

Time: O(n³) — Each interval once.
Space: O(n²) — Memo.

Python code

from typing import List
from functools import lru_cache
class Solution:
    def maxCoins(self, nums: List[int]) -> int:
        arr = [1] + nums + [1]
        @lru_cache(None)
        def rec(l, r):
            if r - l < 2: return 0
            return max(arr[l]*arr[k]*arr[r] + rec(l, k) + rec(k, r) for k in range(l+1, r))
        return rec(0, len(arr) - 1)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n!)O(n³)
SpaceO(n)O(n²)
Difficulty3/54/5
WhenDon'tTop-down
PE VerdictInterval DP.

What the interviewer is really testing

Classic interval DP — counterintuitive 'last' framing.

Top gotchas

  • DON'T think 'first to burst' — pick which is LAST burst in interval.
  • Pad with 1s on both sides (virtual boundary balloons).
  • Inner k iterates strictly between l and r.

Ship-it

Interval DP.

#121 LC 10 Hard Regular Expression Matching

2-D DP — '*' = zero or more of preceding char; recurse on both.

Problem Statement

Match string s with pattern p containing '.' (any) and '*' (zero or more of prev).

Signature: boolean isMatch(String s, String p)

Examples

s='aa', p='a*' → true
s='ab', p='.*' → true
s='mississippi', p='mis*is*p*.' → false

Constraints

  • 1 <= n, m <= 20

Approach Overview

Brute Force

Java: Recursion

Python: Recursion

O(2^(n+m)) O(n+m)

Optimal #1

Java: 2-D DP

Python: 2-D DP

O(n·m) O(n·m)

Optimal #2

Java: Memoized recursion

Python: Memoized

O(n·m) O(n·m)

Java Solutions

Recursion O(2^(n+m)) O(n+m)

Try '*' = 0 (skip) or 1+ (consume).

Pseudo-code

if p[j+1]=='*': skip OR (match && rec(i+1,j)); else single match && rec(i+1,j+1)

Complexity

Time: O(2^(n+m)) — Tree.
Space: O(n+m) — Stack.

Edge cases & gotchas

  • Empty s, p must collapse via '*'.

Java code

class Solution {
    public boolean isMatch(String s, String p) { return rec(s, p, 0, 0); }
    private boolean rec(String s, String p, int i, int j) {
        if (j == p.length()) return i == s.length();
        boolean first = i < s.length() && (s.charAt(i) == p.charAt(j) || p.charAt(j) == '.');
        if (j + 1 < p.length() && p.charAt(j+1) == '*')
            return rec(s, p, i, j+2) || (first && rec(s, p, i+1, j));
        return first && rec(s, p, i+1, j+1);
    }
}
2-D DP O(n·m) O(n·m)

Bottom-up.

Pseudo-code

dp[i][j] = can match s[i..] with p[j..]; same logic

Complexity

Time: O(n·m) — Fill table.
Space: O(n·m) — Table.

Java code

class Solution {
    public boolean isMatch(String s, String p) {
        int n = s.length(), m = p.length();
        boolean[][] dp = new boolean[n + 1][m + 1];
        dp[n][m] = true;
        for (int i = n; i >= 0; i--) for (int j = m - 1; j >= 0; j--) {
            boolean first = i < n && (s.charAt(i) == p.charAt(j) || p.charAt(j) == '.');
            if (j + 1 < m && p.charAt(j+1) == '*') dp[i][j] = dp[i][j+2] || (first && dp[i+1][j]);
            else dp[i][j] = first && dp[i+1][j+1];
        }
        return dp[0][0];
    }
}
Memoized recursion O(n·m) O(n·m)

Top-down cached.

Pseudo-code

memo[(i,j)]

Complexity

Time: O(n·m) — Each state once.
Space: O(n·m) — Memo.

Java code

class Solution {
    private Boolean[][] memo;
    public boolean isMatch(String s, String p) { memo = new Boolean[s.length() + 1][p.length() + 1]; return rec(s, p, 0, 0); }
    private boolean rec(String s, String p, int i, int j) {
        if (memo[i][j] != null) return memo[i][j];
        if (j == p.length()) return memo[i][j] = (i == s.length());
        boolean first = i < s.length() && (s.charAt(i) == p.charAt(j) || p.charAt(j) == '.');
        boolean res;
        if (j + 1 < p.length() && p.charAt(j+1) == '*') res = rec(s, p, i, j+2) || (first && rec(s, p, i+1, j));
        else res = first && rec(s, p, i+1, j+1);
        return memo[i][j] = res;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^(n+m))O(n·m)
SpaceO(n+m)O(n·m)
Difficulty3/54/5
WhenDon'tTop-down clarity
PE Verdict2-D DP bottom-up.

Python Solutions

Recursion O(2^(n+m)) O(n+m)

Same.

Pseudo-code

see Java

Complexity

Time: O(2^(n+m)) — Tree.
Space: O(n+m) — Stack.

Python code

class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        def rec(i, j):
            if j == len(p): return i == len(s)
            first = i < len(s) and (s[i] == p[j] or p[j] == '.')
            if j+1 < len(p) and p[j+1] == '*':
                return rec(i, j+2) or (first and rec(i+1, j))
            return first and rec(i+1, j+1)
        return rec(0, 0)
2-D DP O(n·m) O(n·m)

Same.

Pseudo-code

see Java

Complexity

Time: O(n·m) — Fill.
Space: O(n·m) — Table.

Python code

class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        n, m = len(s), len(p)
        dp = [[False]*(m+1) for _ in range(n+1)]
        dp[n][m] = True
        for i in range(n, -1, -1):
            for j in range(m-1, -1, -1):
                first = i < n and (s[i] == p[j] or p[j] == '.')
                if j+1 < m and p[j+1] == '*': dp[i][j] = dp[i][j+2] or (first and dp[i+1][j])
                else: dp[i][j] = first and dp[i+1][j+1]
        return dp[0][0]
Memoized O(n·m) O(n·m)

Same.

Pseudo-code

see Java

Complexity

Time: O(n·m) — Each once.
Space: O(n·m) — Memo.

Python code

from functools import lru_cache
class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        @lru_cache(None)
        def rec(i, j):
            if j == len(p): return i == len(s)
            first = i < len(s) and (s[i] == p[j] or p[j] == '.')
            if j+1 < len(p) and p[j+1] == '*':
                return rec(i, j+2) or (first and rec(i+1, j))
            return first and rec(i+1, j+1)
        return rec(0, 0)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^(n+m))O(n·m)
SpaceO(n+m)O(n·m)
Difficulty3/54/5
WhenDon'tTop-down
PE Verdict2-D DP.

What the interviewer is really testing

Classic regex DP — '*' has two interpretations.

Top gotchas

  • Look AHEAD to p[j+1] for '*'.
  • '*' consume requires first char match AND recurse keeping j (don't advance).
  • Base: empty s matches empty p; empty s matches some patterns via '*' collapsing pairs.

Ship-it

2-D DP.

Greedy

#122 LC 53 Medium Maximum Subarray

Kadane's algorithm — track running sum; reset when it dips negative.

Problem Statement

Max sum of any contiguous subarray.

Signature: int maxSubArray(int[] nums)

Examples

[-2,1,-3,4,-1,2,1,-5,4] → 6

Constraints

  • 1 <= n <= 10^5

Approach Overview

Brute Force

Java: All subarrays

Python: All subarrays

O(n²) O(1)

Optimal #1

Java: Kadane's

Python: Kadane's

O(n) O(1)

Optimal #2

Java: Divide & conquer

Python: D&C

O(n log n) O(log n)

Java Solutions

All subarrays O(n²) O(1)

O(n²) pairs.

Pseudo-code

for each l: sum=0; for r=l..: sum+=nums[r]; best=max

Complexity

Time: O(n²) — Nested.
Space: O(1) — None.

Edge cases & gotchas

  • All negative — best is max single element.

Java code

class Solution {
    public int maxSubArray(int[] nums) {
        int best = Integer.MIN_VALUE;
        for (int l = 0; l < nums.length; l++) {
            int s = 0;
            for (int r = l; r < nums.length; r++) { s += nums[r]; best = Math.max(best, s); }
        }
        return best;
    }
}
Kadane's O(n) O(1)

Running sum; reset if it drops below current x.

Pseudo-code

cur = max(x, cur+x); best = max(best, cur)

Complexity

Time: O(n) — Single pass.
Space: O(1) — Two ints.

Java code

class Solution {
    public int maxSubArray(int[] nums) {
        int cur = nums[0], best = nums[0];
        for (int i = 1; i < nums.length; i++) {
            cur = Math.max(nums[i], cur + nums[i]);
            best = Math.max(best, cur);
        }
        return best;
    }
}
Divide & conquer O(n log n) O(log n)

Mid-crossing + left + right.

Pseudo-code

max(leftMax, rightMax, crossingMax)

Complexity

Time: O(n log n) — Recursive halves.
Space: O(log n) — Stack.

Java code

class Solution {
    public int maxSubArray(int[] nums) { return rec(nums, 0, nums.length - 1); }
    private int rec(int[] n, int l, int r) {
        if (l == r) return n[l];
        int m = (l + r) >>> 1;
        int lm = rec(n, l, m), rm = rec(n, m+1, r);
        int s = 0, lc = Integer.MIN_VALUE;
        for (int i = m; i >= l; i--) { s += n[i]; lc = Math.max(lc, s); }
        s = 0; int rc = Integer.MIN_VALUE;
        for (int i = m+1; i <= r; i++) { s += n[i]; rc = Math.max(rc, s); }
        return Math.max(Math.max(lm, rm), lc + rc);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n log n)
SpaceO(1)O(log n)
Difficulty1/54/5
WhenDon'tPedagogical / parallel
PE VerdictKadane's.

Python Solutions

All subarrays O(n²) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²) — Nested.
Space: O(1) — None.

Python code

from typing import List
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        best = nums[0]
        for l in range(len(nums)):
            s = 0
            for r in range(l, len(nums)):
                s += nums[r]; best = max(best, s)
        return best
Kadane's O(n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — Two ints.

Python code

from typing import List
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        cur = best = nums[0]
        for x in nums[1:]:
            cur = max(x, cur + x); best = max(best, cur)
        return best
D&C O(n log n) O(log n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n log n) — Halves.
Space: O(log n) — Stack.

Python code

from typing import List
class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        def rec(l, r):
            if l == r: return nums[l]
            m = (l + r) // 2
            lm, rm = rec(l, m), rec(m+1, r)
            s = 0; lc = float('-inf')
            for i in range(m, l-1, -1): s += nums[i]; lc = max(lc, s)
            s = 0; rc = float('-inf')
            for i in range(m+1, r+1): s += nums[i]; rc = max(rc, s)
            return max(lm, rm, lc + rc)
        return rec(0, len(nums) - 1)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n log n)
SpaceO(1)O(log n)
Difficulty1/54/5
WhenDon'tPedagogical
PE VerdictKadane's.

What the interviewer is really testing

Kadane's is a must-know.

Top gotchas

  • Compare cur+x to x alone (handles all-negative arrays).
  • Initialize best with nums[0], not 0.
  • D&C variant useful for parallel/distributed.

Ship-it

Kadane's running sum.

#123 LC 55 Medium Jump Game

Greedy — track farthest reachable index; fail if i exceeds it.

Problem Statement

Each nums[i] is max jump length from i. Can you reach index n-1?

Signature: boolean canJump(int[] nums)

Examples

[2,3,1,1,4] → true
[3,2,1,0,4] → false

Constraints

  • 1 <= n <= 10^4

Approach Overview

Brute Force

Java: DFS try every jump

Python: DFS

O(2^n) O(n)

Optimal #1

Java: Greedy reachable

Python: Greedy

O(n) O(1)

Optimal #2

Java: Backward DP

Python: Backward DP

O(n) O(1)

Java Solutions

DFS try every jump O(2^n) O(n)

Backtrack jump lengths.

Pseudo-code

rec(i): if i>=n-1 true; for step=1..nums[i]: if rec(i+step): true

Complexity

Time: O(2^n) — Branching.
Space: O(n) — Stack.

Java code

class Solution {
    public boolean canJump(int[] nums) { return rec(nums, 0); }
    private boolean rec(int[] n, int i) {
        if (i >= n.length - 1) return true;
        int maxStep = Math.min(n[i], n.length - 1 - i);
        for (int step = maxStep; step >= 1; step--) if (rec(n, i + step)) return true;
        return false;
    }
}
Greedy reachable O(n) O(1)

Track farthest index reachable so far.

Pseudo-code

far=0; for i: if i>far: false; far=max(far, i+nums[i]); return true

Complexity

Time: O(n) — Single pass.
Space: O(1) — One int.

Java code

class Solution {
    public boolean canJump(int[] nums) {
        int far = 0;
        for (int i = 0; i < nums.length; i++) {
            if (i > far) return false;
            far = Math.max(far, i + nums[i]);
            if (far >= nums.length - 1) return true;
        }
        return true;
    }
}
Backward DP O(n) O(1)

Walk right→left; track last good.

Pseudo-code

good=n-1; for i=n-2..0: if i+nums[i]>=good: good=i; return good==0

Complexity

Time: O(n) — Single pass.
Space: O(1) — One int.

Java code

class Solution {
    public boolean canJump(int[] nums) {
        int good = nums.length - 1;
        for (int i = nums.length - 2; i >= 0; i--)
            if (i + nums[i] >= good) good = i;
        return good == 0;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n)
SpaceO(n)O(1)
Difficulty2/52/5
WhenDon'tEquivalent
PE VerdictGreedy reachable index.

Python Solutions

DFS O(2^n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(2^n) — Tree.
Space: O(n) — Stack.

Python code

from typing import List
class Solution:
    def canJump(self, nums: List[int]) -> bool:
        def rec(i):
            if i >= len(nums) - 1: return True
            for step in range(min(nums[i], len(nums)-1-i), 0, -1):
                if rec(i + step): return True
            return False
        return rec(0)
Greedy O(n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — One int.

Python code

from typing import List
class Solution:
    def canJump(self, nums: List[int]) -> bool:
        far = 0
        for i, x in enumerate(nums):
            if i > far: return False
            far = max(far, i + x)
        return True
Backward DP O(n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — One int.

Python code

from typing import List
class Solution:
    def canJump(self, nums: List[int]) -> bool:
        good = len(nums) - 1
        for i in range(len(nums) - 2, -1, -1):
            if i + nums[i] >= good: good = i
        return good == 0

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n)
SpaceO(n)O(1)
Difficulty2/52/5
WhenDon'tEquivalent
PE VerdictGreedy reachable.

What the interviewer is really testing

Classic greedy.

Top gotchas

  • Track FARTHEST index reachable — not just whether i is reachable.
  • Early-exit when far >= n-1.
  • Backward DP equally valid; same complexity.

Ship-it

Greedy forward sweep.

#124 LC 45 Medium Jump Game II

BFS-on-array — current 'level' ends, jump from best in window.

Problem Statement

Min jumps to reach last index (guaranteed reachable).

Signature: int jump(int[] nums)

Examples

[2,3,1,1,4] → 2

Constraints

  • 1 <= n <= 10^4

Approach Overview

Brute Force

Java: DP

Python: DP

O(n²) O(n)

Optimal #1

Java: BFS-style greedy

Python: BFS greedy

O(n) O(1)

Optimal #2

Java: BFS explicit

Python: BFS explicit

O(n) O(n)

Java Solutions

DP O(n²) O(n)

dp[i] = min jumps to reach i.

Pseudo-code

dp[0]=0; for i: for j>i within reach: dp[j]=min(dp[j], dp[i]+1)

Complexity

Time: O(n²) — Nested.
Space: O(n) — dp[].

Java code

class Solution {
    public int jump(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];
        java.util.Arrays.fill(dp, Integer.MAX_VALUE);
        dp[0] = 0;
        for (int i = 0; i < n; i++)
            for (int j = i + 1; j <= Math.min(n - 1, i + nums[i]); j++)
                if (dp[i] != Integer.MAX_VALUE) dp[j] = Math.min(dp[j], dp[i] + 1);
        return dp[n-1];
    }
}
BFS-style greedy O(n) O(1)

Track current end-of-level and farthest reachable.

Pseudo-code

jumps=0; far=0; end=0; for i<n-1: far=max(far, i+nums[i]); if i==end: jumps++; end=far

Complexity

Time: O(n) — Single pass.
Space: O(1) — Three ints.

Edge cases & gotchas

  • n=1 → 0.

Java code

class Solution {
    public int jump(int[] nums) {
        int jumps = 0, far = 0, end = 0;
        for (int i = 0; i < nums.length - 1; i++) {
            far = Math.max(far, i + nums[i]);
            if (i == end) { jumps++; end = far; }
        }
        return jumps;
    }
}
BFS explicit O(n) O(n)

Queue of levels.

Pseudo-code

BFS by level; first level reaching n-1

Complexity

Time: O(n) — Each visited once.
Space: O(n) — Queue.

Java code

import java.util.*;
class Solution {
    public int jump(int[] nums) {
        if (nums.length == 1) return 0;
        Deque<Integer> q = new ArrayDeque<>(); q.offer(0);
        boolean[] v = new boolean[nums.length]; v[0] = true;
        int level = 0;
        while (!q.isEmpty()) {
            level++;
            int sz = q.size();
            for (int k = 0; k < sz; k++) {
                int i = q.poll();
                for (int s = 1; s <= nums[i] && i + s < nums.length; s++) {
                    if (i + s == nums.length - 1) return level;
                    if (!v[i+s]) { v[i+s] = true; q.offer(i+s); }
                }
            }
        }
        return -1;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(n)O(n)
Difficulty2/53/5
WhenDon'tConceptual
PE VerdictBFS-style greedy.

Python Solutions

DP O(n²) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²) — Nested.
Space: O(n) — dp[].

Python code

from typing import List
class Solution:
    def jump(self, nums: List[int]) -> int:
        n = len(nums)
        dp = [float('inf')]*n; dp[0] = 0
        for i in range(n):
            for j in range(i+1, min(n, i + nums[i] + 1)):
                dp[j] = min(dp[j], dp[i] + 1)
        return dp[-1]
BFS greedy O(n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — Const.

Python code

from typing import List
class Solution:
    def jump(self, nums: List[int]) -> int:
        jumps = far = end = 0
        for i in range(len(nums) - 1):
            far = max(far, i + nums[i])
            if i == end: jumps += 1; end = far
        return jumps
BFS explicit O(n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(n) — Queue.

Python code

from typing import List
from collections import deque
class Solution:
    def jump(self, nums: List[int]) -> int:
        if len(nums) == 1: return 0
        q = deque([0]); seen = {0}; level = 0
        while q:
            level += 1
            for _ in range(len(q)):
                i = q.popleft()
                for s in range(1, nums[i] + 1):
                    nxt = i + s
                    if nxt >= len(nums) - 1: return level
                    if nxt not in seen: seen.add(nxt); q.append(nxt)
        return -1

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(n)O(n)
Difficulty2/53/5
WhenDon'tConceptual
PE VerdictBFS-style greedy.

What the interviewer is really testing

Greedy levels — equivalent to BFS over jumps.

Top gotchas

  • Stop at i = n-2 (don't jump from last).
  • Bump jumps when you HIT the end of current window — set new window to farthest seen.
  • All inputs guarantee reachable; no -1 path needed.

Ship-it

BFS-style greedy.

#125 LC 134 Medium Gas Station

Greedy — if total gas ≥ total cost, the station after each 'tank empty' is the start.

Problem Statement

Circular route. gas[i] fuel at i, cost[i] to drive to i+1. Find starting station to complete the loop, else -1.

Signature: int canCompleteCircuit(int[] gas, int[] cost)

Examples

gas=[1,2,3,4,5], cost=[3,4,5,1,2] → 3

Constraints

  • 1 <= n <= 10^5

Approach Overview

Brute Force

Java: Try each start

Python: Each start

O(n²) O(1)

Optimal #1

Java: Greedy one pass

Python: Greedy

O(n) O(1)

Optimal #2

Java: Two-pointer simulation

Python: Two-pointer

O(n) O(1)

Java Solutions

Try each start O(n²) O(1)

Simulate from each.

Pseudo-code

for start: tank=0; for i in 0..n: idx=(start+i)%n; tank+=gas-cost; if tank<0 break; if i==n return start

Complexity

Time: O(n²) — Nested.
Space: O(1) — None.

Edge cases & gotchas

  • Single station with gas>=cost → 0.

Java code

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int n = gas.length;
        for (int s = 0; s < n; s++) {
            int tank = 0; boolean ok = true;
            for (int i = 0; i < n; i++) {
                int idx = (s + i) % n;
                tank += gas[idx] - cost[idx];
                if (tank < 0) { ok = false; break; }
            }
            if (ok) return s;
        }
        return -1;
    }
}
Greedy one pass O(n) O(1)

If running tank dips negative, reset start to next station.

Pseudo-code

if sum(gas) < sum(cost): -1; tank=0; start=0; for i: tank+=gas-cost; if tank<0: start=i+1; tank=0; return start

Complexity

Time: O(n) — Single pass.
Space: O(1) — Three ints.

Java code

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int total = 0, tank = 0, start = 0;
        for (int i = 0; i < gas.length; i++) {
            int d = gas[i] - cost[i];
            total += d; tank += d;
            if (tank < 0) { start = i + 1; tank = 0; }
        }
        return total < 0 ? -1 : start;
    }
}
Two-pointer simulation O(n) O(1)

Use circular pointer with both ends.

Pseudo-code

end at n-1, start at 0; if tank<0 move start back; until covered

Complexity

Time: O(n) — Two passes.
Space: O(1) — Const.

Java code

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int n = gas.length, start = n - 1, end = 0, tank = gas[start] - cost[start];
        while (start > end) {
            if (tank < 0) { start--; tank += gas[start] - cost[start]; }
            else { tank += gas[end] - cost[end]; end++; }
        }
        return tank >= 0 ? start : -1;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(1)O(1)
Difficulty2/54/5
WhenDon'tEquivalent
PE VerdictGreedy one-pass.

Python Solutions

Each start O(n²) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²) — Nested.
Space: O(1) — None.

Python code

from typing import List
class Solution:
    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
        n = len(gas)
        for s in range(n):
            tank = 0; ok = True
            for i in range(n):
                tank += gas[(s+i)%n] - cost[(s+i)%n]
                if tank < 0: ok = False; break
            if ok: return s
        return -1
Greedy O(n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — Const.

Python code

from typing import List
class Solution:
    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
        total = tank = start = 0
        for i, (g, c) in enumerate(zip(gas, cost)):
            d = g - c; total += d; tank += d
            if tank < 0: start = i + 1; tank = 0
        return -1 if total < 0 else start
Two-pointer O(n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — Const.

Python code

from typing import List
class Solution:
    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
        n = len(gas); start = n - 1; end = 0; tank = gas[start] - cost[start]
        while start > end:
            if tank < 0: start -= 1; tank += gas[start] - cost[start]
            else: tank += gas[end] - cost[end]; end += 1
        return start if tank >= 0 else -1

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(1)O(1)
Difficulty2/54/5
WhenDon'tEquivalent
PE VerdictGreedy.

What the interviewer is really testing

Beautiful greedy with subtle proof.

Top gotchas

  • Total gas < total cost → impossible.
  • If you run out between i and j, NO station in [i,j] can be a valid start. Skip ahead to j+1.
  • Only ONE valid start exists (if any).

Ship-it

Greedy one-pass.

#126 LC 846 Medium Hand of Straights

Count map + greedy — repeatedly form a group starting at smallest unused.

Problem Statement

Can hand be split into groups of k consecutive cards?

Signature: boolean isNStraightHand(int[] hand, int groupSize)

Examples

[1,2,3,6,2,3,4,7,8], k=3 → true

Constraints

  • 1 <= hand.length <= 10^4

Approach Overview

Brute Force

Java: Sort + naive scan

Python: Sort + naive

O(n²) O(n)

Optimal #1

Java: Count map + TreeMap

Python: Counter + sorted

O(n log n) O(n)

Optimal #2

Java: Sort + queue of group starts

Python: Iterate sorted h

O(n log n) O(n)

Java Solutions

Sort + naive scan O(n²) O(n)

Sort; pull k consecutive starting at smallest available.

Pseudo-code

sort; for each: if used skip; pull next k; if missing → false

Complexity

Time: O(n²) — Linear scan per group.
Space: O(n) — Used flags.

Edge cases & gotchas

  • n % k != 0 → false.

Java code

import java.util.*;
class Solution {
    public boolean isNStraightHand(int[] h, int k) {
        if (h.length % k != 0) return false;
        Arrays.sort(h);
        boolean[] used = new boolean[h.length];
        for (int i = 0; i < h.length; i++) {
            if (used[i]) continue;
            int cur = h[i]; used[i] = true;
            for (int g = 1; g < k; g++) {
                boolean found = false;
                for (int j = i + 1; j < h.length; j++) if (!used[j] && h[j] == cur + 1) { used[j] = true; cur++; found = true; break; }
                if (!found) return false;
            }
        }
        return true;
    }
}
Count map + TreeMap O(n log n) O(n)

Smallest key; consume k consecutive counts.

Pseudo-code

counts; while not empty: smallest s; for s..s+k-1: decrement, return false if missing

Complexity

Time: O(n log n) — TreeMap ops.
Space: O(n) — Map.

Java code

import java.util.*;
class Solution {
    public boolean isNStraightHand(int[] h, int k) {
        if (h.length % k != 0) return false;
        TreeMap<Integer, Integer> cnt = new TreeMap<>();
        for (int x : h) cnt.merge(x, 1, Integer::sum);
        while (!cnt.isEmpty()) {
            int s = cnt.firstKey();
            for (int i = 0; i < k; i++) {
                Integer c = cnt.get(s + i);
                if (c == null) return false;
                if (c == 1) cnt.remove(s + i); else cnt.put(s + i, c - 1);
            }
        }
        return true;
    }
}
Sort + queue of group starts O(n log n) O(n)

Iterate sorted; track open groups expiring at value+k.

Pseudo-code

sort; queue tracks (start, remaining); for each card: extend existing or start new

Complexity

Time: O(n log n) — Sort + linear.
Space: O(n) — Queue.

Java code

import java.util.*;
class Solution {
    public boolean isNStraightHand(int[] h, int k) {
        if (h.length % k != 0) return false;
        Map<Integer, Integer> cnt = new HashMap<>();
        for (int x : h) cnt.merge(x, 1, Integer::sum);
        Arrays.sort(h);
        for (int x : h) {
            if (cnt.get(x) == 0) continue;
            for (int g = 0; g < k; g++) {
                if (cnt.getOrDefault(x + g, 0) == 0) return false;
                cnt.put(x + g, cnt.get(x + g) - 1);
            }
        }
        return true;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n log n)
SpaceO(n)O(n)
Difficulty2/53/5
WhenDon'tSorted scan
PE VerdictTreeMap + greedy.

Python Solutions

Sort + naive O(n²) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²) — Naive.
Space: O(n) — Used.

Python code

from typing import List
class Solution:
    def isNStraightHand(self, h: List[int], k: int) -> bool:
        if len(h) % k: return False
        h.sort(); used = [False]*len(h)
        for i in range(len(h)):
            if used[i]: continue
            cur = h[i]; used[i] = True
            for _ in range(k - 1):
                found = False
                for j in range(i+1, len(h)):
                    if not used[j] and h[j] == cur + 1:
                        used[j] = True; cur += 1; found = True; break
                if not found: return False
        return True
Counter + sorted O(n log n) O(n)

Same idea, heap of smallest.

Pseudo-code

see Java

Complexity

Time: O(n log n) — Sort + linear.
Space: O(n) — Counter.

Python code

from typing import List
from collections import Counter
class Solution:
    def isNStraightHand(self, h: List[int], k: int) -> bool:
        if len(h) % k: return False
        cnt = Counter(h)
        for x in sorted(cnt):
            c = cnt[x]
            if c == 0: continue
            for g in range(k):
                if cnt[x + g] < c: return False
                cnt[x + g] -= c
        return True
Iterate sorted h O(n log n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n log n) — Sort + linear.
Space: O(n) — Counter.

Python code

from typing import List
from collections import Counter
class Solution:
    def isNStraightHand(self, h: List[int], k: int) -> bool:
        if len(h) % k: return False
        cnt = Counter(h)
        for x in sorted(h):
            if cnt[x] == 0: continue
            for g in range(k):
                if cnt[x + g] == 0: return False
                cnt[x + g] -= 1
        return True

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n log n)
SpaceO(n)O(n)
Difficulty2/53/5
WhenDon'tEquivalent
PE VerdictCounter + sorted keys.

What the interviewer is really testing

Greedy + frequency map combo.

Top gotchas

  • First short-circuit: n % k != 0 → impossible.
  • Always start a new group from the SMALLEST remaining card.
  • Consume k consecutive in the counter; bail if any is missing.

Ship-it

TreeMap/Counter greedy.

#127 LC 1899 Medium Merge Triplets to Form Target Triplet

Filter usable triplets (no value exceeds target) — check OR-merge covers target.

Problem Statement

merge(a,b) takes max-by-component. Can we produce target from any subset?

Signature: boolean mergeTriplets(int[][] triplets, int[] target)

Examples

triplets=[[2,5,3],[1,8,4],[1,7,5]], target=[2,7,5] → true

Constraints

  • 1 <= n <= 10^5

Approach Overview

Brute Force

Java: Bitmask try-all subsets

Python: Bitmask

O(2^n · 3) O(1)

Optimal #1

Java: Greedy filter + max

Python: Greedy filter

O(n) O(1)

Optimal #2

Java: Same logic; bitmask of found components

Python: Bitmask of found

O(n) O(1)

Java Solutions

Bitmask try-all subsets O(2^n · 3) O(1)

Try every subset, merge, check.

Pseudo-code

for each subset: merge; if == target return true

Complexity

Time: O(2^n · 3) — Exp.
Space: O(1) — None.

Java code

class Solution {
    public boolean mergeTriplets(int[][] t, int[] target) {
        int n = t.length;
        for (int mask = 1; mask < (1 << n); mask++) {
            int a = 0, b = 0, c = 0;
            for (int i = 0; i < n; i++) if ((mask & (1 << i)) != 0) {
                a = Math.max(a, t[i][0]); b = Math.max(b, t[i][1]); c = Math.max(c, t[i][2]);
            }
            if (a == target[0] && b == target[1] && c == target[2]) return true;
        }
        return false;
    }
}
Greedy filter + max O(n) O(1)

Drop triplets exceeding target; check if max of remaining equals target component-wise.

Pseudo-code

for t: if any t[k]>target[k]: skip; else update found[k] if t[k]==target[k]; return all found

Complexity

Time: O(n) — Single pass.
Space: O(1) — None.

Java code

class Solution {
    public boolean mergeTriplets(int[][] t, int[] target) {
        boolean[] found = new boolean[3];
        for (int[] x : t) {
            if (x[0] > target[0] || x[1] > target[1] || x[2] > target[2]) continue;
            for (int k = 0; k < 3; k++) if (x[k] == target[k]) found[k] = true;
        }
        return found[0] && found[1] && found[2];
    }
}
Same logic; bitmask of found components O(n) O(1)

Bitmask 3-bit.

Pseudo-code

int f=0; for t: skip if exceeds; for k: if t[k]==target[k] set bit

Complexity

Time: O(n) — Linear.
Space: O(1) — Int.

Java code

class Solution {
    public boolean mergeTriplets(int[][] t, int[] target) {
        int f = 0;
        for (int[] x : t) {
            if (x[0] > target[0] || x[1] > target[1] || x[2] > target[2]) continue;
            for (int k = 0; k < 3; k++) if (x[k] == target[k]) f |= 1 << k;
        }
        return f == 7;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n)
SpaceO(1)O(1)
Difficulty2/53/5
WhenDon'tEquivalent
PE VerdictGreedy filter + per-component cover.

Python Solutions

Bitmask O(2^n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(2^n) — Exp.
Space: O(1) — None.

Python code

from typing import List
class Solution:
    def mergeTriplets(self, t: List[List[int]], target: List[int]) -> bool:
        n = len(t)
        for mask in range(1, 1 << n):
            a = b = c = 0
            for i in range(n):
                if mask & (1 << i):
                    a = max(a, t[i][0]); b = max(b, t[i][1]); c = max(c, t[i][2])
            if [a,b,c] == target: return True
        return False
Greedy filter O(n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — None.

Python code

from typing import List
class Solution:
    def mergeTriplets(self, t: List[List[int]], target: List[int]) -> bool:
        found = [False, False, False]
        for x in t:
            if any(x[k] > target[k] for k in range(3)): continue
            for k in range(3):
                if x[k] == target[k]: found[k] = True
        return all(found)
Bitmask of found O(n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — Int.

Python code

from typing import List
class Solution:
    def mergeTriplets(self, t: List[List[int]], target: List[int]) -> bool:
        f = 0
        for x in t:
            if any(x[k] > target[k] for k in range(3)): continue
            for k in range(3):
                if x[k] == target[k]: f |= 1 << k
        return f == 7

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(2^n)O(n)
SpaceO(1)O(1)
Difficulty2/53/5
WhenDon'tEquivalent
PE VerdictGreedy filter.

What the interviewer is really testing

Insight: any triplet with a component > target is unusable.

Top gotchas

  • Skip ENTIRELY any triplet that exceeds target in any component.
  • Need to find triplets that contribute each of the 3 target values exactly.
  • Merging takes maxes — so non-exceeding partners are safe.

Ship-it

Greedy filter.

#128 LC 763 Medium Partition Labels

Last-index map + greedy — expand current window to cover farthest last-index.

Problem Statement

Partition s so each letter appears in only one part. Return sizes.

Signature: List<Integer> partitionLabels(String s)

Examples

'ababcbacadefegdehijhklij' → [9,7,8]

Constraints

  • 1 <= n <= 500

Approach Overview

Brute Force

Java: Try every cut

Python: Brute cuts

O(n²) O(n)

Optimal #1

Java: Last-index map + greedy

Python: Last-index greedy

O(n) O(1)

Optimal #2

Java: Interval merge

Python: Interval merge

O(n + 26 log 26) O(1)

Java Solutions

Try every cut O(n²) O(n)

Verify each prefix is closed wrt remaining suffix.

Pseudo-code

for each cut: chars in prefix ∩ chars in suffix == ∅ → cut

Complexity

Time: O(n²) — Nested.
Space: O(n) — Sets.

Java code

import java.util.*;
class Solution {
    public List<Integer> partitionLabels(String s) {
        List<Integer> out = new ArrayList<>();
        int start = 0;
        while (start < s.length()) {
            for (int end = start; end < s.length(); end++) {
                Set<Character> a = new HashSet<>(), b = new HashSet<>();
                for (int i = start; i <= end; i++) a.add(s.charAt(i));
                for (int i = end + 1; i < s.length(); i++) b.add(s.charAt(i));
                a.retainAll(b);
                if (a.isEmpty()) { out.add(end - start + 1); start = end + 1; break; }
            }
        }
        return out;
    }
}
Last-index map + greedy O(n) O(1)

Track last occurrence; when i reaches farthest last, close partition.

Pseudo-code

compute last[c]; far=0; start=0; for i: far=max(far, last[s[i]]); if i==far: add(i-start+1); start=i+1

Complexity

Time: O(n) — Two passes.
Space: O(1) — 26 entries.

Java code

import java.util.*;
class Solution {
    public List<Integer> partitionLabels(String s) {
        int[] last = new int[26];
        for (int i = 0; i < s.length(); i++) last[s.charAt(i) - 'a'] = i;
        List<Integer> out = new ArrayList<>();
        int far = 0, start = 0;
        for (int i = 0; i < s.length(); i++) {
            far = Math.max(far, last[s.charAt(i) - 'a']);
            if (i == far) { out.add(i - start + 1); start = i + 1; }
        }
        return out;
    }
}
Interval merge O(n + 26 log 26) O(1)

Build [first, last] per char; merge intervals.

Pseudo-code

first/last per char; merge overlapping; emit lengths

Complexity

Time: O(n + 26 log 26) — Linear + small sort.
Space: O(1) — 26 entries.

Java code

import java.util.*;
class Solution {
    public List<Integer> partitionLabels(String s) {
        int[] first = new int[26], last = new int[26];
        Arrays.fill(first, -1);
        for (int i = 0; i < s.length(); i++) {
            int c = s.charAt(i) - 'a';
            if (first[c] == -1) first[c] = i;
            last[c] = i;
        }
        List<int[]> iv = new ArrayList<>();
        for (int c = 0; c < 26; c++) if (first[c] != -1) iv.add(new int[]{first[c], last[c]});
        iv.sort((a, b) -> a[0] - b[0]);
        List<Integer> out = new ArrayList<>();
        int l = iv.get(0)[0], r = iv.get(0)[1];
        for (int i = 1; i < iv.size(); i++) {
            if (iv.get(i)[0] <= r) r = Math.max(r, iv.get(i)[1]);
            else { out.add(r - l + 1); l = iv.get(i)[0]; r = iv.get(i)[1]; }
        }
        out.add(r - l + 1);
        return out;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(n)O(1)
Difficulty2/54/5
WhenDon'tGeneralizes to k>26
PE VerdictLast-index map + greedy.

Python Solutions

Brute cuts O(n²) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²) — Naive.
Space: O(n) — Sets.

Python code

from typing import List
class Solution:
    def partitionLabels(self, s: str) -> List[int]:
        out = []; start = 0
        while start < len(s):
            for end in range(start, len(s)):
                if set(s[start:end+1]).isdisjoint(set(s[end+1:])):
                    out.append(end - start + 1); start = end + 1; break
        return out
Last-index greedy O(n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Two passes.
Space: O(1) — Map.

Python code

from typing import List
class Solution:
    def partitionLabels(self, s: str) -> List[int]:
        last = {c: i for i, c in enumerate(s)}
        out = []; far = start = 0
        for i, c in enumerate(s):
            far = max(far, last[c])
            if i == far: out.append(i - start + 1); start = i + 1
        return out
Interval merge O(n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — Map.

Python code

from typing import List
class Solution:
    def partitionLabels(self, s: str) -> List[int]:
        first, last = {}, {}
        for i, c in enumerate(s):
            if c not in first: first[c] = i
            last[c] = i
        iv = sorted([(first[c], last[c]) for c in first])
        out = []; l, r = iv[0]
        for a, b in iv[1:]:
            if a <= r: r = max(r, b)
            else: out.append(r - l + 1); l, r = a, b
        out.append(r - l + 1)
        return out

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n)
SpaceO(n)O(1)
Difficulty2/54/5
WhenDon'tGeneralizes
PE VerdictLast-index + greedy.

What the interviewer is really testing

Beautiful single-pass greedy.

Top gotchas

  • Build last[c] in a separate sweep.
  • Track farthest required end; close when i reaches it.
  • Reset partition start to i+1.

Ship-it

Last-index map + greedy.

#129 LC 678 Medium Valid Parenthesis String

Track open-count range [lo, hi] — '*' widens the range.

Problem Statement

'*' acts as '(', ')', or empty. Is s a valid parenthesis sequence?

Signature: boolean checkValidString(String s)

Examples

'(*))' → true
'(*)' → true
'(((*)' → false

Constraints

  • 1 <= n <= 100

Approach Overview

Brute Force

Java: Try each '*' as 3 chars

Python: Recursion

O(3^n) O(n)

Optimal #1

Java: Range [lo, hi]

Python: Range [lo, hi]

O(n) O(1)

Optimal #2

Java: Two stacks

Python: Two stacks

O(n) O(n)

Java Solutions

Try each '*' as 3 chars O(3^n) O(n)

Recursion 3-way.

Pseudo-code

rec(i, bal): if c=='(' bal++; if ')' bal--; if '*' try all three

Complexity

Time: O(3^n) — Branching.
Space: O(n) — Stack.

Edge cases & gotchas

  • bal must stay >=0.

Java code

class Solution {
    public boolean checkValidString(String s) { return rec(s, 0, 0); }
    private boolean rec(String s, int i, int bal) {
        if (bal < 0) return false;
        if (i == s.length()) return bal == 0;
        char c = s.charAt(i);
        if (c == '(') return rec(s, i+1, bal+1);
        if (c == ')') return rec(s, i+1, bal-1);
        return rec(s, i+1, bal+1) || rec(s, i+1, bal) || rec(s, i+1, bal-1);
    }
}
Range [lo, hi] O(n) O(1)

lo = min possible open count, hi = max. '*' shifts both ways.

Pseudo-code

lo=hi=0; for c: if '(' lo++,hi++; if ')' lo--,hi--; if '*' lo--,hi++; if hi<0: false; lo=max(0,lo); return lo==0

Complexity

Time: O(n) — Single pass.
Space: O(1) — Two ints.

Java code

class Solution {
    public boolean checkValidString(String s) {
        int lo = 0, hi = 0;
        for (char c : s.toCharArray()) {
            if (c == '(') { lo++; hi++; }
            else if (c == ')') { lo--; hi--; }
            else { lo--; hi++; }
            if (hi < 0) return false;
            lo = Math.max(lo, 0);
        }
        return lo == 0;
    }
}
Two stacks O(n) O(n)

Stack of '(' indices, stack of '*' indices; reconcile.

Pseudo-code

push '(' and '*' indices; ')' pops '(' first else '*'; final: leftover '(' must pair with '*' to the right

Complexity

Time: O(n) — Single pass.
Space: O(n) — Two stacks.

Java code

import java.util.*;
class Solution {
    public boolean checkValidString(String s) {
        Deque<Integer> open = new ArrayDeque<>(), star = new ArrayDeque<>();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '(') open.push(i);
            else if (c == '*') star.push(i);
            else { if (!open.isEmpty()) open.pop(); else if (!star.isEmpty()) star.pop(); else return false; }
        }
        while (!open.isEmpty() && !star.isEmpty()) {
            if (open.pop() > star.pop()) return false;
        }
        return open.isEmpty();
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(3^n)O(n)
SpaceO(n)O(n)
Difficulty2/53/5
WhenDon'tIndex-aware
PE VerdictRange [lo, hi].

Python Solutions

Recursion O(3^n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(3^n) — Tree.
Space: O(n) — Stack.

Python code

class Solution:
    def checkValidString(self, s: str) -> bool:
        def rec(i, bal):
            if bal < 0: return False
            if i == len(s): return bal == 0
            c = s[i]
            if c == '(': return rec(i+1, bal+1)
            if c == ')': return rec(i+1, bal-1)
            return rec(i+1, bal+1) or rec(i+1, bal) or rec(i+1, bal-1)
        return rec(0, 0)
Range [lo, hi] O(n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — Two ints.

Python code

class Solution:
    def checkValidString(self, s: str) -> bool:
        lo = hi = 0
        for c in s:
            if c == '(': lo += 1; hi += 1
            elif c == ')': lo -= 1; hi -= 1
            else: lo -= 1; hi += 1
            if hi < 0: return False
            lo = max(lo, 0)
        return lo == 0
Two stacks O(n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(n) — Stacks.

Python code

class Solution:
    def checkValidString(self, s: str) -> bool:
        open_, star = [], []
        for i, c in enumerate(s):
            if c == '(': open_.append(i)
            elif c == '*': star.append(i)
            else:
                if open_: open_.pop()
                elif star: star.pop()
                else: return False
        while open_ and star:
            if open_.pop() > star.pop(): return False
        return not open_

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(3^n)O(n)
SpaceO(n)O(n)
Difficulty2/53/5
WhenDon'tEquivalent
PE VerdictRange [lo, hi].

What the interviewer is really testing

The [lo,hi] trick is elegant.

Top gotchas

  • Clamp lo to 0 (can't have negative unmatched-open count).
  • Fail if hi < 0 — impossible to recover.
  • Final accept: lo == 0 (some interpretation yields exact balance).

Ship-it

Range [lo, hi].

Intervals

#130 LC 57 Medium Insert Interval

Single pass — emit before, merge overlapping, emit after.

Problem Statement

Insert newInterval into sorted non-overlapping intervals; merge if needed.

Signature: int[][] insert(int[][] intervals, int[] newInterval)

Examples

[[1,3],[6,9]] + [2,5] → [[1,5],[6,9]]

Constraints

  • 0 <= n <= 10^4

Approach Overview

Brute Force

Java: Append + sort + merge

Python: Append + sort + merge

O(n log n) O(n)

Optimal #1

Java: Single-pass insert

Python: Single-pass

O(n) O(n)

Optimal #2

Java: Binary search for insertion point

Python: Binary search

O(log n + k) O(n)

Java Solutions

Append + sort + merge O(n log n) O(n)

Reuse merge-intervals.

Pseudo-code

append; sort by start; standard merge

Complexity

Time: O(n log n) — Sort.
Space: O(n) — Output.

Edge cases & gotchas

  • Empty intervals → just newInterval.

Java code

import java.util.*;
class Solution {
    public int[][] insert(int[][] iv, int[] x) {
        int[][] all = new int[iv.length + 1][];
        System.arraycopy(iv, 0, all, 0, iv.length);
        all[iv.length] = x;
        Arrays.sort(all, (a, b) -> a[0] - b[0]);
        List<int[]> out = new ArrayList<>();
        for (int[] r : all) {
            if (!out.isEmpty() && out.get(out.size()-1)[1] >= r[0])
                out.get(out.size()-1)[1] = Math.max(out.get(out.size()-1)[1], r[1]);
            else out.add(r);
        }
        return out.toArray(new int[0][]);
    }
}
Single-pass insert O(n) O(n)

Three phases: before, merge, after.

Pseudo-code

i=0; copy strictly-before; merge overlapping into x; copy remaining

Complexity

Time: O(n) — Single pass.
Space: O(n) — Output.

Java code

import java.util.*;
class Solution {
    public int[][] insert(int[][] iv, int[] x) {
        List<int[]> out = new ArrayList<>();
        int i = 0, n = iv.length;
        while (i < n && iv[i][1] < x[0]) out.add(iv[i++]);
        while (i < n && iv[i][0] <= x[1]) {
            x[0] = Math.min(x[0], iv[i][0]);
            x[1] = Math.max(x[1], iv[i][1]);
            i++;
        }
        out.add(x);
        while (i < n) out.add(iv[i++]);
        return out.toArray(new int[0][]);
    }
}
Binary search for insertion point O(log n + k) O(n)

Find first overlap, merge forward.

Pseudo-code

binsearch leftmost interval with end>=x[0]; merge forward

Complexity

Time: O(log n + k) — Faster lookup.
Space: O(n) — Output.

Java code

import java.util.*;
class Solution {
    public int[][] insert(int[][] iv, int[] x) {
        int n = iv.length;
        int lo = 0, hi = n;
        while (lo < hi) { int m = (lo + hi) >>> 1; if (iv[m][1] < x[0]) lo = m + 1; else hi = m; }
        int idx = lo;
        List<int[]> out = new ArrayList<>();
        for (int k = 0; k < idx; k++) out.add(iv[k]);
        int i = idx;
        while (i < n && iv[i][0] <= x[1]) {
            x[0] = Math.min(x[0], iv[i][0]);
            x[1] = Math.max(x[1], iv[i][1]);
            i++;
        }
        out.add(x);
        while (i < n) out.add(iv[i++]);
        return out.toArray(new int[0][]);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n log n)O(log n + k)
SpaceO(n)O(n)
Difficulty2/54/5
WhenDon'tHuge n
PE VerdictSingle-pass insert.

Python Solutions

Append + sort + merge O(n log n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n log n) — Sort.
Space: O(n) — Output.

Python code

from typing import List
class Solution:
    def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
        all_ = sorted(intervals + [newInterval])
        out = []
        for r in all_:
            if out and out[-1][1] >= r[0]: out[-1][1] = max(out[-1][1], r[1])
            else: out.append(r)
        return out
Single-pass O(n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(n) — Output.

Python code

from typing import List
class Solution:
    def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
        out, i, n = [], 0, len(intervals)
        while i < n and intervals[i][1] < newInterval[0]:
            out.append(intervals[i]); i += 1
        while i < n and intervals[i][0] <= newInterval[1]:
            newInterval = [min(newInterval[0], intervals[i][0]), max(newInterval[1], intervals[i][1])]
            i += 1
        out.append(newInterval)
        while i < n:
            out.append(intervals[i]); i += 1
        return out
Binary search O(log n + k) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(log n + k) — Faster.
Space: O(n) — Output.

Python code

from typing import List
from bisect import bisect_left
class Solution:
    def insert(self, intervals: List[List[int]], newInterval: List[int]) -> List[List[int]]:
        ends = [iv[1] for iv in intervals]
        idx = bisect_left(ends, newInterval[0])
        out = intervals[:idx]; i = idx
        while i < len(intervals) and intervals[i][0] <= newInterval[1]:
            newInterval = [min(newInterval[0], intervals[i][0]), max(newInterval[1], intervals[i][1])]
            i += 1
        out.append(newInterval)
        out.extend(intervals[i:])
        return out

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n log n)O(log n + k)
SpaceO(n)O(n)
Difficulty2/54/5
WhenDon'tHuge n
PE VerdictSingle-pass.

What the interviewer is really testing

Three-phase walk.

Top gotchas

  • Phase 1: intervals ending BEFORE start of new — copy.
  • Phase 2: overlapping — merge into running x.
  • Phase 3: rest — copy.

Ship-it

Single-pass three-phase.

#131 LC 56 Medium Merge Intervals

Sort by start; merge if current overlaps last result.

Problem Statement

Merge all overlapping intervals.

Signature: int[][] merge(int[][] intervals)

Examples

[[1,3],[2,6],[8,10],[15,18]] → [[1,6],[8,10],[15,18]]

Constraints

  • 1 <= n <= 10^4

Approach Overview

Brute Force

Java: Repeated pass until stable

Python: Repeated pass

O(n²) O(n)

Optimal #1

Java: Sort + single pass

Python: Sort + single pass

O(n log n) O(n)

Optimal #2

Java: Sweep line

Python: Sweep line

O(n log n) O(n)

Java Solutions

Repeated pass until stable O(n²) O(n)

Each pass merges adjacent overlap; repeat until no change.

Pseudo-code

do: scan, merge adjacent overlapping pair; while changed

Complexity

Time: O(n²) — Up to n passes.
Space: O(n) — List.

Java code

import java.util.*;
class Solution {
    public int[][] merge(int[][] iv) {
        List<int[]> list = new ArrayList<>();
        for (int[] x : iv) list.add(x);
        list.sort((a, b) -> a[0] - b[0]);
        boolean changed = true;
        while (changed) {
            changed = false;
            for (int i = 0; i + 1 < list.size(); i++) {
                if (list.get(i)[1] >= list.get(i+1)[0]) {
                    list.get(i)[1] = Math.max(list.get(i)[1], list.get(i+1)[1]);
                    list.remove(i+1); changed = true; break;
                }
            }
        }
        return list.toArray(new int[0][]);
    }
}
Sort + single pass O(n log n) O(n)

Standard merge-intervals.

Pseudo-code

sort by start; for each: if last.end >= cur.start: extend last.end; else append

Complexity

Time: O(n log n) — Sort dominates.
Space: O(n) — Output.

Java code

import java.util.*;
class Solution {
    public int[][] merge(int[][] iv) {
        Arrays.sort(iv, (a, b) -> a[0] - b[0]);
        List<int[]> out = new ArrayList<>();
        for (int[] x : iv) {
            if (!out.isEmpty() && out.get(out.size()-1)[1] >= x[0])
                out.get(out.size()-1)[1] = Math.max(out.get(out.size()-1)[1], x[1]);
            else out.add(x);
        }
        return out.toArray(new int[0][]);
    }
}
Sweep line O(n log n) O(n)

Endpoints as events.

Pseudo-code

events: (start,+1),(end+1,-1); sweep; balance crosses 0 → close interval

Complexity

Time: O(n log n) — Sort events.
Space: O(n) — Events.

Edge cases & gotchas

  • Closed intervals — use end+1 with care.

Java code

import java.util.*;
class Solution {
    public int[][] merge(int[][] iv) {
        int n = iv.length;
        int[][] e = new int[2*n][2];
        for (int i = 0; i < n; i++) { e[2*i] = new int[]{iv[i][0], 1}; e[2*i+1] = new int[]{iv[i][1], -1}; }
        Arrays.sort(e, (a, b) -> a[0] != b[0] ? a[0] - b[0] : b[1] - a[1]);
        List<int[]> out = new ArrayList<>();
        int bal = 0, start = -1;
        for (int[] ev : e) {
            if (bal == 0 && ev[1] == 1) start = ev[0];
            bal += ev[1];
            if (bal == 0) out.add(new int[]{start, ev[0]});
        }
        return out.toArray(new int[0][]);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n log n)
SpaceO(n)O(n)
Difficulty1/54/5
WhenDon'tSweep-line generalizes
PE VerdictSort + single pass.

Python Solutions

Repeated pass O(n²) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²) — Naive.
Space: O(n) — List.

Python code

from typing import List
class Solution:
    def merge(self, iv: List[List[int]]) -> List[List[int]]:
        iv = sorted(iv)
        changed = True
        while changed:
            changed = False
            for i in range(len(iv) - 1):
                if iv[i][1] >= iv[i+1][0]:
                    iv[i][1] = max(iv[i][1], iv[i+1][1])
                    iv.pop(i+1); changed = True; break
        return iv
Sort + single pass O(n log n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n log n) — Sort.
Space: O(n) — Output.

Python code

from typing import List
class Solution:
    def merge(self, iv: List[List[int]]) -> List[List[int]]:
        iv.sort()
        out = []
        for x in iv:
            if out and out[-1][1] >= x[0]: out[-1][1] = max(out[-1][1], x[1])
            else: out.append(x)
        return out
Sweep line O(n log n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n log n) — Sort events.
Space: O(n) — Events.

Python code

from typing import List
class Solution:
    def merge(self, iv: List[List[int]]) -> List[List[int]]:
        events = []
        for s, e in iv:
            events.append((s, 1)); events.append((e, -1))
        events.sort(key=lambda x: (x[0], -x[1]))
        out = []; bal = 0; start = -1
        for t, d in events:
            if bal == 0 and d == 1: start = t
            bal += d
            if bal == 0: out.append([start, t])
        return out

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n log n)
SpaceO(n)O(n)
Difficulty1/54/5
WhenDon'tSweep generalizes
PE VerdictSort + single pass.

What the interviewer is really testing

Foundational intervals.

Top gotchas

  • Sort by START.
  • Overlap if last.end >= current.start (inclusive endpoints).
  • Always extend last.end with max — don't overwrite blindly.

Ship-it

Sort + single pass.

#132 LC 435 Medium Non-overlapping Intervals

Sort by end — greedy keep earliest end; count removals.

Problem Statement

Min intervals to remove so the rest don't overlap.

Signature: int eraseOverlapIntervals(int[][] intervals)

Examples

[[1,2],[2,3],[3,4],[1,3]] → 1

Constraints

  • 1 <= n <= 10^5

Approach Overview

Brute Force

Java: Sort by start + DP

Python: DP

O(n²) O(n)

Optimal #1

Java: Sort by end + greedy

Python: Sort by end + greedy

O(n log n) O(1)

Optimal #2

Java: Sort by start + remove later end

Python: Sort by start + drop later end

O(n log n) O(1)

Java Solutions

Sort by start + DP O(n²) O(n)

LIS-style: longest non-overlap chain.

Pseudo-code

sort by start; dp[i] = 1 + max(dp[j] for j<i if iv[j].end <= iv[i].start); answer = n - max(dp)

Complexity

Time: O(n²) — Nested.
Space: O(n) — dp[].

Java code

import java.util.*;
class Solution {
    public int eraseOverlapIntervals(int[][] iv) {
        Arrays.sort(iv, (a, b) -> a[0] - b[0]);
        int n = iv.length, best = 1;
        int[] dp = new int[n];
        Arrays.fill(dp, 1);
        for (int i = 1; i < n; i++) {
            for (int j = 0; j < i; j++) if (iv[j][1] <= iv[i][0]) dp[i] = Math.max(dp[i], dp[j] + 1);
            best = Math.max(best, dp[i]);
        }
        return n - best;
    }
}
Sort by end + greedy O(n log n) O(1)

Activity selection — pick earliest-ending non-overlap.

Pseudo-code

sort by end; prev = iv[0].end; count=0; for each: if start<prev: count++; else prev = end

Complexity

Time: O(n log n) — Sort.
Space: O(1) — None.

Java code

import java.util.*;
class Solution {
    public int eraseOverlapIntervals(int[][] iv) {
        Arrays.sort(iv, (a, b) -> a[1] - b[1]);
        int prev = iv[0][1], removed = 0;
        for (int i = 1; i < iv.length; i++) {
            if (iv[i][0] < prev) removed++;
            else prev = iv[i][1];
        }
        return removed;
    }
}
Sort by start + remove later end O(n log n) O(1)

On overlap, remove the one ending later.

Pseudo-code

sort by start; if overlap, remove the one with larger end

Complexity

Time: O(n log n) — Sort.
Space: O(1) — None.

Java code

import java.util.*;
class Solution {
    public int eraseOverlapIntervals(int[][] iv) {
        Arrays.sort(iv, (a, b) -> a[0] - b[0]);
        int prevEnd = iv[0][1], removed = 0;
        for (int i = 1; i < iv.length; i++) {
            if (iv[i][0] < prevEnd) { removed++; prevEnd = Math.min(prevEnd, iv[i][1]); }
            else prevEnd = iv[i][1];
        }
        return removed;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n log n)
SpaceO(n)O(1)
Difficulty3/53/5
WhenDon'tEquivalent
PE VerdictSort by end + greedy.

Python Solutions

DP O(n²) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²) — Nested.
Space: O(n) — dp[].

Python code

from typing import List
class Solution:
    def eraseOverlapIntervals(self, iv: List[List[int]]) -> int:
        iv.sort(); n = len(iv); dp = [1]*n
        for i in range(1, n):
            for j in range(i):
                if iv[j][1] <= iv[i][0]: dp[i] = max(dp[i], dp[j] + 1)
        return n - max(dp)
Sort by end + greedy O(n log n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n log n) — Sort.
Space: O(1) — None.

Python code

from typing import List
class Solution:
    def eraseOverlapIntervals(self, iv: List[List[int]]) -> int:
        iv.sort(key=lambda x: x[1])
        prev = iv[0][1]; removed = 0
        for s, e in iv[1:]:
            if s < prev: removed += 1
            else: prev = e
        return removed
Sort by start + drop later end O(n log n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n log n) — Sort.
Space: O(1) — None.

Python code

from typing import List
class Solution:
    def eraseOverlapIntervals(self, iv: List[List[int]]) -> int:
        iv.sort()
        prev_end = iv[0][1]; removed = 0
        for s, e in iv[1:]:
            if s < prev_end: removed += 1; prev_end = min(prev_end, e)
            else: prev_end = e
        return removed

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n log n)
SpaceO(n)O(1)
Difficulty3/53/5
WhenDon'tEquivalent
PE VerdictSort by end + greedy.

What the interviewer is really testing

Activity selection in disguise.

Top gotchas

  • Sort by END, not start.
  • Strict comparison: '<' (touching at the boundary is NOT overlap here).
  • Equivalent to: max non-overlapping subset; answer = n - that count.

Ship-it

Sort by end + greedy.

#133 LC 252 Easy Meeting Rooms

Sort by start — check every adjacent pair for overlap.

Problem Statement

Can a person attend all meetings? (no overlaps)

Signature: boolean canAttendMeetings(int[][] intervals)

Examples

[[0,30],[5,10],[15,20]] → false
[[7,10],[2,4]] → true

Constraints

  • 0 <= n <= 10^4

Approach Overview

Brute Force

Java: All pairs

Python: All pairs

O(n²) O(1)

Optimal #1

Java: Sort by start

Python: Sort by start

O(n log n) O(1)

Optimal #2

Java: Sweep line

Python: Sweep line

O(n log n) O(n)

Java Solutions

All pairs O(n²) O(1)

O(n²) overlap check.

Pseudo-code

for each i<j: if iv[i] overlaps iv[j]: false

Complexity

Time: O(n²) — Nested.
Space: O(1) — None.

Edge cases & gotchas

  • Empty → true.

Java code

class Solution {
    public boolean canAttendMeetings(int[][] iv) {
        for (int i = 0; i < iv.length; i++) for (int j = i + 1; j < iv.length; j++)
            if (iv[i][0] < iv[j][1] && iv[j][0] < iv[i][1]) return false;
        return true;
    }
}
Sort by start O(n log n) O(1)

Adjacent overlap check.

Pseudo-code

sort by start; for i: if iv[i].start < iv[i-1].end: false

Complexity

Time: O(n log n) — Sort.
Space: O(1) — None.

Java code

import java.util.*;
class Solution {
    public boolean canAttendMeetings(int[][] iv) {
        Arrays.sort(iv, (a, b) -> a[0] - b[0]);
        for (int i = 1; i < iv.length; i++) if (iv[i][0] < iv[i-1][1]) return false;
        return true;
    }
}
Sweep line O(n log n) O(n)

Sort events; bal never > 1.

Pseudo-code

events; sweep; if bal > 1: false

Complexity

Time: O(n log n) — Sort events.
Space: O(n) — Events.

Java code

import java.util.*;
class Solution {
    public boolean canAttendMeetings(int[][] iv) {
        int n = iv.length;
        int[][] e = new int[2*n][2];
        for (int i = 0; i < n; i++) { e[2*i] = new int[]{iv[i][0], 1}; e[2*i+1] = new int[]{iv[i][1], -1}; }
        Arrays.sort(e, (a, b) -> a[0] != b[0] ? a[0] - b[0] : a[1] - b[1]);
        int bal = 0;
        for (int[] x : e) { bal += x[1]; if (bal > 1) return false; }
        return true;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n log n)
SpaceO(1)O(n)
Difficulty1/53/5
WhenDon'tGeneralizes
PE VerdictSort by start + adjacent overlap.

Python Solutions

All pairs O(n²) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²) — Nested.
Space: O(1) — None.

Python code

from typing import List
class Solution:
    def canAttendMeetings(self, iv: List[List[int]]) -> bool:
        for i in range(len(iv)):
            for j in range(i+1, len(iv)):
                if iv[i][0] < iv[j][1] and iv[j][0] < iv[i][1]: return False
        return True
Sort by start O(n log n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n log n) — Sort.
Space: O(1) — None.

Python code

from typing import List
class Solution:
    def canAttendMeetings(self, iv: List[List[int]]) -> bool:
        iv.sort()
        for i in range(1, len(iv)):
            if iv[i][0] < iv[i-1][1]: return False
        return True
Sweep line O(n log n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n log n) — Sort events.
Space: O(n) — Events.

Python code

from typing import List
class Solution:
    def canAttendMeetings(self, iv: List[List[int]]) -> bool:
        events = []
        for s, e in iv: events.append((s, 1)); events.append((e, -1))
        events.sort()
        bal = 0
        for _, d in events:
            bal += d
            if bal > 1: return False
        return True

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n log n)
SpaceO(1)O(n)
Difficulty1/53/5
WhenDon'tGeneralizes
PE VerdictSort + adjacent overlap.

What the interviewer is really testing

Intro to interval overlap.

Top gotchas

  • Touching at boundary (end == start) is NOT a conflict here.
  • Sort by start, then linear adjacent check.
  • Sweep-line generalizes to Meeting Rooms II.

Ship-it

Sort + adjacent check.

#134 LC 253 Medium Meeting Rooms II

Min-heap of end times — peak concurrency = rooms needed.

Problem Statement

Min rooms required to host all meetings.

Signature: int minMeetingRooms(int[][] intervals)

Examples

[[0,30],[5,10],[15,20]] → 2

Constraints

  • 1 <= n <= 10^4

Approach Overview

Brute Force

Java: Timeline counting

Python: Timeline

O(n·T) O(T)

Optimal #1

Java: Sort + min-heap of ends

Python: Sort + min-heap

O(n log n) O(n)

Optimal #2

Java: Sweep line

Python: Sweep line

O(n log n) O(n)

Java Solutions

Timeline counting O(n·T) O(T)

Increment per minute; max.

Pseudo-code

buckets = max end; for each iv: bucket[s]++; bucket[e]--; running max prefix

Complexity

Time: O(n·T) — T = time range.
Space: O(T) — Buckets.

Java code

class Solution {
    public int minMeetingRooms(int[][] iv) {
        int maxT = 0;
        for (int[] x : iv) maxT = Math.max(maxT, x[1]);
        int[] diff = new int[maxT + 2];
        for (int[] x : iv) { diff[x[0]]++; diff[x[1]]--; }
        int best = 0, run = 0;
        for (int v : diff) { run += v; best = Math.max(best, run); }
        return best;
    }
}
Sort + min-heap of ends O(n log n) O(n)

Pop ended meetings; heap size = rooms.

Pseudo-code

sort by start; heap of ends; for each: if heap.top<=start: pop; push end; max(size)

Complexity

Time: O(n log n) — Heap.
Space: O(n) — Heap.

Java code

import java.util.*;
class Solution {
    public int minMeetingRooms(int[][] iv) {
        Arrays.sort(iv, (a, b) -> a[0] - b[0]);
        PriorityQueue<Integer> pq = new PriorityQueue<>();
        int best = 0;
        for (int[] x : iv) {
            if (!pq.isEmpty() && pq.peek() <= x[0]) pq.poll();
            pq.offer(x[1]);
            best = Math.max(best, pq.size());
        }
        return best;
    }
}
Sweep line O(n log n) O(n)

Sort events; max concurrent.

Pseudo-code

events; sort; for each: bal += delta; max(bal)

Complexity

Time: O(n log n) — Sort.
Space: O(n) — Events.

Edge cases & gotchas

  • Equal time: end (-1) before start (+1).

Java code

import java.util.*;
class Solution {
    public int minMeetingRooms(int[][] iv) {
        int n = iv.length;
        int[][] e = new int[2*n][2];
        for (int i = 0; i < n; i++) { e[2*i] = new int[]{iv[i][0], 1}; e[2*i+1] = new int[]{iv[i][1], -1}; }
        Arrays.sort(e, (a, b) -> a[0] != b[0] ? a[0] - b[0] : a[1] - b[1]);
        int bal = 0, best = 0;
        for (int[] x : e) { bal += x[1]; best = Math.max(best, bal); }
        return best;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n·T)O(n log n)
SpaceO(T)O(n)
Difficulty2/53/5
WhenTiny TEquivalent
PE VerdictSort + min-heap.

Python Solutions

Timeline O(n·T) O(T)

Same.

Pseudo-code

see Java

Complexity

Time: O(n·T) — Linear.
Space: O(T) — Buckets.

Python code

from typing import List
class Solution:
    def minMeetingRooms(self, iv: List[List[int]]) -> int:
        maxT = max(e for _, e in iv)
        diff = [0]*(maxT + 2)
        for s, e in iv: diff[s] += 1; diff[e] -= 1
        best = run = 0
        for v in diff: run += v; best = max(best, run)
        return best
Sort + min-heap O(n log n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n log n) — Heap.
Space: O(n) — Heap.

Python code

from typing import List
import heapq
class Solution:
    def minMeetingRooms(self, iv: List[List[int]]) -> int:
        iv.sort()
        h = []; best = 0
        for s, e in iv:
            if h and h[0] <= s: heapq.heappop(h)
            heapq.heappush(h, e)
            best = max(best, len(h))
        return best
Sweep line O(n log n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n log n) — Sort.
Space: O(n) — Events.

Python code

from typing import List
class Solution:
    def minMeetingRooms(self, iv: List[List[int]]) -> int:
        events = []
        for s, e in iv: events.append((s, 1)); events.append((e, -1))
        events.sort(key=lambda x: (x[0], x[1]))
        bal = best = 0
        for _, d in events:
            bal += d; best = max(best, bal)
        return best

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n·T)O(n log n)
SpaceO(T)O(n)
Difficulty2/53/5
WhenTiny TEquivalent
PE VerdictSort + min-heap.

What the interviewer is really testing

Heap of end times = active rooms.

Top gotchas

  • Sort by START.
  • Pop heap if earliest END <= current start (release room).
  • Touching at boundary (end == start) means the room can be reused — use <=, not <.

Ship-it

Sort + min-heap.

#135 LC 1851 Hard Minimum Interval to Include Each Query

Offline: sort intervals & queries; min-heap by size, prune by end.

Problem Statement

For each query q, return size of smallest interval [l,r] s.t. l<=q<=r (or -1).

Signature: int[] minInterval(int[][] intervals, int[] queries)

Examples

intervals=[[1,4],[2,4],[3,6],[4,4]], queries=[2,3,4,5] → [3,3,1,4]

Constraints

  • 1 <= n, q <= 10^5

Approach Overview

Brute Force

Java: For each query scan intervals

Python: Per-query scan

O(n·q) O(q)

Optimal #1

Java: Offline sort + min-heap

Python: Offline + min-heap

O((n+q) log (n+q)) O(n+q)

Optimal #2

Java: Sweep with sorted intervals + TreeSet

Python: SortedList

O((n+q) log n) O(n)

Java Solutions

For each query scan intervals O(n·q) O(q)

O(n·q).

Pseudo-code

for each q: best=inf; for each iv: if l<=q<=r: best=min(best, r-l+1); record

Complexity

Time: O(n·q) — Nested.
Space: O(q) — Output.

Java code

class Solution {
    public int[] minInterval(int[][] iv, int[] q) {
        int[] out = new int[q.length];
        for (int i = 0; i < q.length; i++) {
            int best = Integer.MAX_VALUE;
            for (int[] x : iv) if (x[0] <= q[i] && q[i] <= x[1]) best = Math.min(best, x[1] - x[0] + 1);
            out[i] = best == Integer.MAX_VALUE ? -1 : best;
        }
        return out;
    }
}
Offline sort + min-heap O((n+q) log (n+q)) O(n+q)

Sort intervals by start, queries asc; add eligible to heap (size), pop expired.

Pseudo-code

sort intervals by start, query indices by query value; for each q: push intervals with start<=q (size, end); pop heap top with end<q; answer = heap.top.size

Complexity

Time: O((n+q) log (n+q)) — Heap + sort.
Space: O(n+q) — Heap + index.

Edge cases & gotchas

  • No covering interval → -1.

Java code

import java.util.*;
class Solution {
    public int[] minInterval(int[][] iv, int[] q) {
        Arrays.sort(iv, (a, b) -> a[0] - b[0]);
        Integer[] idx = new Integer[q.length];
        for (int i = 0; i < q.length; i++) idx[i] = i;
        Arrays.sort(idx, (a, b) -> q[a] - q[b]);
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);
        int[] out = new int[q.length];
        int j = 0;
        for (int i : idx) {
            int qi = q[i];
            while (j < iv.length && iv[j][0] <= qi) { pq.offer(new int[]{iv[j][1] - iv[j][0] + 1, iv[j][1]}); j++; }
            while (!pq.isEmpty() && pq.peek()[1] < qi) pq.poll();
            out[i] = pq.isEmpty() ? -1 : pq.peek()[0];
        }
        return out;
    }
}
Sweep with sorted intervals + TreeSet O((n+q) log n) O(n)

Insert intervals as queries advance; track active by size.

Pseudo-code

sort intervals by start; sort queries; for each: add intervals with start<=q to BST keyed by (size, end); remove expired; answer = first.size

Complexity

Time: O((n+q) log n) — BST ops.
Space: O(n) — BST.

Java code

import java.util.*;
class Solution {
    public int[] minInterval(int[][] iv, int[] q) {
        Arrays.sort(iv, (a, b) -> a[0] - b[0]);
        Integer[] idx = new Integer[q.length];
        for (int i = 0; i < q.length; i++) idx[i] = i;
        Arrays.sort(idx, (a, b) -> q[a] - q[b]);
        // size, end
        TreeSet<long[]> active = new TreeSet<>((a, b) -> a[0] != b[0] ? Long.compare(a[0], b[0]) : Long.compare(a[1], b[1]));
        int[] out = new int[q.length]; int j = 0;
        for (int i : idx) {
            int qi = q[i];
            while (j < iv.length && iv[j][0] <= qi) { active.add(new long[]{iv[j][1] - iv[j][0] + 1L, iv[j][1]}); j++; }
            // remove expired by linear scan (rare in practice; could index differently)
            while (!active.isEmpty()) {
                long[] f = active.first();
                if (f[1] < qi) active.remove(f); else break;
            }
            out[i] = active.isEmpty() ? -1 : (int) active.first()[0];
        }
        return out;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n·q)O((n+q)log n)
SpaceO(q)O(n)
Difficulty1/54/5
WhenTinyVariant
PE VerdictOffline sort + min-heap of (size, end).

Python Solutions

Per-query scan O(n·q) O(q)

Same.

Pseudo-code

see Java

Complexity

Time: O(n·q) — Nested.
Space: O(q) — Output.

Python code

from typing import List
class Solution:
    def minInterval(self, iv: List[List[int]], q: List[int]) -> List[int]:
        out = []
        for qi in q:
            best = float('inf')
            for l, r in iv:
                if l <= qi <= r: best = min(best, r - l + 1)
            out.append(-1 if best == float('inf') else best)
        return out
Offline + min-heap O((n+q) log) O(n+q)

Same.

Pseudo-code

see Java

Complexity

Time: O((n+q) log) — Heap.
Space: O(n+q) — Heap.

Python code

from typing import List
import heapq
class Solution:
    def minInterval(self, iv: List[List[int]], q: List[int]) -> List[int]:
        iv.sort()
        idx = sorted(range(len(q)), key=lambda i: q[i])
        out = [0]*len(q); h = []; j = 0
        for i in idx:
            qi = q[i]
            while j < len(iv) and iv[j][0] <= qi:
                heapq.heappush(h, (iv[j][1] - iv[j][0] + 1, iv[j][1])); j += 1
            while h and h[0][1] < qi: heapq.heappop(h)
            out[i] = h[0][0] if h else -1
        return out
SortedList O((n+q)log n) O(n)

Same idea.

Pseudo-code

see Java

Complexity

Time: O((n+q)log n) — BST.
Space: O(n) — BST.

Python code

from typing import List
from sortedcontainers import SortedList
class Solution:
    def minInterval(self, iv: List[List[int]], q: List[int]) -> List[int]:
        iv.sort()
        idx = sorted(range(len(q)), key=lambda i: q[i])
        out = [0]*len(q); sl = SortedList(); j = 0
        for i in idx:
            qi = q[i]
            while j < len(iv) and iv[j][0] <= qi:
                sl.add((iv[j][1] - iv[j][0] + 1, iv[j][1])); j += 1
            while sl and sl[0][1] < qi: sl.pop(0)
            out[i] = sl[0][0] if sl else -1
        return out

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n·q)O((n+q)log n)
SpaceO(q)O(n)
Difficulty1/54/5
WhenTinyVariant
PE VerdictOffline sort + min-heap.

What the interviewer is really testing

Offline processing — answer queries in convenient order.

Top gotchas

  • Sort intervals by start; queries by value.
  • Heap key = (size, end); pop top while top.end < q.
  • Write to out[i] in ORIGINAL order — track index permutation.

Ship-it

Offline sort + min-heap.

Math & Geometry

#136 LC 48 Medium Rotate Image

Transpose then reverse each row — in-place 90° CW.

Problem Statement

Rotate n×n matrix 90° clockwise in place.

Signature: void rotate(int[][] matrix)

Examples

[[1,2,3],[4,5,6],[7,8,9]] → [[7,4,1],[8,5,2],[9,6,3]]

Constraints

  • 1 <= n <= 20

Approach Overview

Brute Force

Java: New matrix

Python: New matrix

O(n²) O(n²)

Optimal #1

Java: Transpose + reverse rows

Python: Transpose + reverse

O(n²) O(1)

Optimal #2

Java: Four-cycle in place

Python: Four-cycle

O(n²) O(1)

Java Solutions

New matrix O(n²) O(n²)

out[j][n-1-i] = in[i][j].

Pseudo-code

create new; copy with new[j][n-1-i]=old[i][j]

Complexity

Time: O(n²) — Copy.
Space: O(n²) — New.

Java code

class Solution {
    public void rotate(int[][] m) {
        int n = m.length;
        int[][] r = new int[n][n];
        for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) r[j][n-1-i] = m[i][j];
        for (int i = 0; i < n; i++) System.arraycopy(r[i], 0, m[i], 0, n);
    }
}
Transpose + reverse rows O(n²) O(1)

Two passes, both in place.

Pseudo-code

transpose: swap m[i][j] and m[j][i] for j>i; then reverse each row

Complexity

Time: O(n²) — Two passes.
Space: O(1) — None.

Java code

class Solution {
    public void rotate(int[][] m) {
        int n = m.length;
        for (int i = 0; i < n; i++) for (int j = i + 1; j < n; j++) { int t = m[i][j]; m[i][j] = m[j][i]; m[j][i] = t; }
        for (int i = 0; i < n; i++) for (int l = 0, r = n - 1; l < r; l++, r--) { int t = m[i][l]; m[i][l] = m[i][r]; m[i][r] = t; }
    }
}
Four-cycle in place O(n²) O(1)

Rotate 4 corners at a time.

Pseudo-code

for layer 0..n/2: for i in layer..n-1-layer-1: swap 4 cells

Complexity

Time: O(n²) — Single pass.
Space: O(1) — None.

Java code

class Solution {
    public void rotate(int[][] m) {
        int n = m.length;
        for (int l = 0; l < n / 2; l++) {
            for (int i = l; i < n - 1 - l; i++) {
                int t = m[l][i];
                m[l][i] = m[n-1-i][l];
                m[n-1-i][l] = m[n-1-l][n-1-i];
                m[n-1-l][n-1-i] = m[i][n-1-l];
                m[i][n-1-l] = t;
            }
        }
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n²)
SpaceO(n²)O(1)
Difficulty1/54/5
WhenDon'tSingle-pass elegance
PE VerdictTranspose + reverse rows.

Python Solutions

New matrix O(n²) O(n²)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²) — Copy.
Space: O(n²) — New.

Python code

from typing import List
class Solution:
    def rotate(self, m: List[List[int]]) -> None:
        n = len(m)
        r = [[0]*n for _ in range(n)]
        for i in range(n):
            for j in range(n): r[j][n-1-i] = m[i][j]
        for i in range(n): m[i] = r[i]
Transpose + reverse O(n²) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²) — Two passes.
Space: O(1) — None.

Python code

from typing import List
class Solution:
    def rotate(self, m: List[List[int]]) -> None:
        n = len(m)
        for i in range(n):
            for j in range(i+1, n): m[i][j], m[j][i] = m[j][i], m[i][j]
        for row in m: row.reverse()
Four-cycle O(n²) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n²) — Single pass.
Space: O(1) — None.

Python code

from typing import List
class Solution:
    def rotate(self, m: List[List[int]]) -> None:
        n = len(m)
        for l in range(n // 2):
            for i in range(l, n - 1 - l):
                t = m[l][i]
                m[l][i] = m[n-1-i][l]
                m[n-1-i][l] = m[n-1-l][n-1-i]
                m[n-1-l][n-1-i] = m[i][n-1-l]
                m[i][n-1-l] = t

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n²)O(n²)
SpaceO(n²)O(1)
Difficulty1/54/5
WhenDon'tElegant
PE VerdictTranspose + reverse rows.

What the interviewer is really testing

Matrix manipulation classic.

Top gotchas

  • Transpose: swap with j > i (only upper triangle).
  • Then reverse each row for CW rotation.
  • CCW would be transpose + reverse COLUMNS.

Ship-it

Transpose + reverse rows.

#137 LC 54 Medium Spiral Matrix

Walk in 4 directions; shrink boundaries each layer.

Problem Statement

Return all matrix elements in spiral order.

Signature: List<Integer> spiralOrder(int[][] matrix)

Examples

[[1,2,3],[4,5,6],[7,8,9]] → [1,2,3,6,9,8,7,4,5]

Constraints

  • 1 <= m, n <= 10

Approach Overview

Brute Force

Java: Visited grid

Python: Visited

O(m·n) O(m·n)

Optimal #1

Java: Shrink boundaries

Python: Boundaries

O(m·n) O(1)

Optimal #2

Java: Recursive peel

Python: Recursive peel

O(m·n) O(min(m,n))

Java Solutions

Visited grid O(m·n) O(m·n)

Walk with 4-dir cycle.

Pseudo-code

dirs=[(0,1),(1,0),(0,-1),(-1,0)]; walk; turn when out/visited

Complexity

Time: O(m·n) — Each cell.
Space: O(m·n) — Visited.

Java code

import java.util.*;
class Solution {
    public List<Integer> spiralOrder(int[][] m) {
        int M = m.length, N = m[0].length;
        boolean[][] v = new boolean[M][N];
        int[][] D = {{0,1},{1,0},{0,-1},{-1,0}};
        int d = 0, r = 0, c = 0;
        List<Integer> out = new ArrayList<>();
        for (int k = 0; k < M * N; k++) {
            out.add(m[r][c]); v[r][c] = true;
            int nr = r + D[d][0], nc = c + D[d][1];
            if (nr < 0 || nc < 0 || nr >= M || nc >= N || v[nr][nc]) { d = (d + 1) % 4; nr = r + D[d][0]; nc = c + D[d][1]; }
            r = nr; c = nc;
        }
        return out;
    }
}
Shrink boundaries O(m·n) O(1)

Walk top→right→bottom→left; shrink.

Pseudo-code

top,bot,left,right; while top<=bot and left<=right: 4 walks; shrink

Complexity

Time: O(m·n) — Each cell once.
Space: O(1) — Output.

Edge cases & gotchas

  • Single row/col — guard both bot>=top and right>=left.

Java code

import java.util.*;
class Solution {
    public List<Integer> spiralOrder(int[][] m) {
        List<Integer> out = new ArrayList<>();
        int top = 0, bot = m.length - 1, left = 0, right = m[0].length - 1;
        while (top <= bot && left <= right) {
            for (int c = left; c <= right; c++) out.add(m[top][c]);
            top++;
            for (int r = top; r <= bot; r++) out.add(m[r][right]);
            right--;
            if (top <= bot) for (int c = right; c >= left; c--) out.add(m[bot][c]);
            bot--;
            if (left <= right) for (int r = bot; r >= top; r--) out.add(m[r][left]);
            left++;
        }
        return out;
    }
}
Recursive peel O(m·n) O(min(m,n))

Strip outermost ring, recurse on inner.

Pseudo-code

emit ring; recurse on submatrix

Complexity

Time: O(m·n) — Each cell once.
Space: O(min(m,n)) — Recursion.

Java code

import java.util.*;
class Solution {
    public List<Integer> spiralOrder(int[][] m) {
        List<Integer> out = new ArrayList<>();
        peel(m, 0, 0, m.length - 1, m[0].length - 1, out);
        return out;
    }
    private void peel(int[][] m, int top, int left, int bot, int right, List<Integer> out) {
        if (top > bot || left > right) return;
        for (int c = left; c <= right; c++) out.add(m[top][c]);
        for (int r = top + 1; r <= bot; r++) out.add(m[r][right]);
        if (top < bot) for (int c = right - 1; c >= left; c--) out.add(m[bot][c]);
        if (left < right) for (int r = bot - 1; r > top; r--) out.add(m[r][left]);
        peel(m, top + 1, left + 1, bot - 1, right - 1, out);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(m·n)O(m·n)
SpaceO(m·n)O(min(m,n))
Difficulty2/53/5
WhenDon'tPedagogical
PE VerdictShrink boundaries.

Python Solutions

Visited O(m·n) O(m·n)

Same.

Pseudo-code

see Java

Complexity

Time: O(m·n) — Linear.
Space: O(m·n) — Visited.

Python code

from typing import List
class Solution:
    def spiralOrder(self, m: List[List[int]]) -> List[int]:
        M, N = len(m), len(m[0]); v = [[False]*N for _ in range(M)]
        D = [(0,1),(1,0),(0,-1),(-1,0)]
        d = r = c = 0; out = []
        for _ in range(M*N):
            out.append(m[r][c]); v[r][c] = True
            nr, nc = r + D[d][0], c + D[d][1]
            if not (0 <= nr < M and 0 <= nc < N) or v[nr][nc]:
                d = (d + 1) % 4; nr, nc = r + D[d][0], c + D[d][1]
            r, c = nr, nc
        return out
Boundaries O(m·n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(m·n) — Linear.
Space: O(1) — Output.

Python code

from typing import List
class Solution:
    def spiralOrder(self, m: List[List[int]]) -> List[int]:
        out = []; top, bot, left, right = 0, len(m)-1, 0, len(m[0])-1
        while top <= bot and left <= right:
            for c in range(left, right+1): out.append(m[top][c])
            top += 1
            for r in range(top, bot+1): out.append(m[r][right])
            right -= 1
            if top <= bot:
                for c in range(right, left-1, -1): out.append(m[bot][c])
                bot -= 1
            if left <= right:
                for r in range(bot, top-1, -1): out.append(m[r][left])
                left += 1
        return out
Recursive peel O(m·n) O(min(m,n))

Same.

Pseudo-code

see Java

Complexity

Time: O(m·n) — Linear.
Space: O(min(m,n)) — Recursion.

Python code

from typing import List
class Solution:
    def spiralOrder(self, m: List[List[int]]) -> List[int]:
        out = []
        def peel(top, left, bot, right):
            if top > bot or left > right: return
            for c in range(left, right+1): out.append(m[top][c])
            for r in range(top+1, bot+1): out.append(m[r][right])
            if top < bot:
                for c in range(right-1, left-1, -1): out.append(m[bot][c])
            if left < right:
                for r in range(bot-1, top, -1): out.append(m[r][left])
            peel(top+1, left+1, bot-1, right-1)
        peel(0, 0, len(m)-1, len(m[0])-1)
        return out

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(m·n)O(m·n)
SpaceO(m·n)O(min(m,n))
Difficulty2/53/5
WhenDon'tPedagogical
PE VerdictShrink boundaries.

What the interviewer is really testing

Boundary shrink pattern.

Top gotchas

  • Guard bottom/left walks with top<=bot and left<=right after shrinking.
  • Edge case: single row or col — skipping prevents duplicates.
  • Verify increment/decrement order to avoid off-by-one.

Ship-it

Shrink boundaries.

#138 LC 73 Medium Set Matrix Zeroes

Use first row & col as in-place markers; track them separately.

Problem Statement

If element is 0, zero out its row and column. In place.

Signature: void setZeroes(int[][] matrix)

Examples

[[1,1,1],[1,0,1],[1,1,1]] → [[1,0,1],[0,0,0],[1,0,1]]

Constraints

  • 1 <= m, n <= 200

Approach Overview

Brute Force

Java: Copy matrix

Python: Copy matrix

O(m·n) O(m·n)

Optimal #1

Java: O(1) markers

Python: O(1) markers

O(m·n) O(1)

Optimal #2

Java: O(m+n) row/col sets

Python: O(m+n) sets

O(m·n) O(m+n)

Java Solutions

Copy matrix O(m·n) O(m·n)

Scan; zero rows/cols in a copy.

Pseudo-code

copy m; scan original; if 0 → zero row+col in copy; copy back

Complexity

Time: O(m·n) — Linear.
Space: O(m·n) — Copy.

Java code

class Solution {
    public void setZeroes(int[][] m) {
        int M = m.length, N = m[0].length;
        int[][] c = new int[M][N];
        for (int i = 0; i < M; i++) c[i] = m[i].clone();
        for (int i = 0; i < M; i++) for (int j = 0; j < N; j++) if (m[i][j] == 0) {
            for (int k = 0; k < N; k++) c[i][k] = 0;
            for (int k = 0; k < M; k++) c[k][j] = 0;
        }
        for (int i = 0; i < M; i++) m[i] = c[i];
    }
}
O(1) markers O(m·n) O(1)

First row & col as markers; track them via two booleans.

Pseudo-code

firstRowZ, firstColZ flags; mark m[i][0] and m[0][j]; second pass zero from markers; finally zero first row/col if needed

Complexity

Time: O(m·n) — Constant passes.
Space: O(1) — Two booleans.

Java code

class Solution {
    public void setZeroes(int[][] m) {
        int M = m.length, N = m[0].length;
        boolean firstRowZ = false, firstColZ = false;
        for (int j = 0; j < N; j++) if (m[0][j] == 0) { firstRowZ = true; break; }
        for (int i = 0; i < M; i++) if (m[i][0] == 0) { firstColZ = true; break; }
        for (int i = 1; i < M; i++) for (int j = 1; j < N; j++) if (m[i][j] == 0) { m[i][0] = 0; m[0][j] = 0; }
        for (int i = 1; i < M; i++) for (int j = 1; j < N; j++) if (m[i][0] == 0 || m[0][j] == 0) m[i][j] = 0;
        if (firstRowZ) for (int j = 0; j < N; j++) m[0][j] = 0;
        if (firstColZ) for (int i = 0; i < M; i++) m[i][0] = 0;
    }
}
O(m+n) row/col sets O(m·n) O(m+n)

Track zero rows and cols in sets.

Pseudo-code

first pass find zero rows/cols; second pass zero matching

Complexity

Time: O(m·n) — Two passes.
Space: O(m+n) — Sets.

Java code

import java.util.*;
class Solution {
    public void setZeroes(int[][] m) {
        Set<Integer> rows = new HashSet<>(), cols = new HashSet<>();
        for (int i = 0; i < m.length; i++) for (int j = 0; j < m[0].length; j++)
            if (m[i][j] == 0) { rows.add(i); cols.add(j); }
        for (int i = 0; i < m.length; i++) for (int j = 0; j < m[0].length; j++)
            if (rows.contains(i) || cols.contains(j)) m[i][j] = 0;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(m·n)O(m·n)
SpaceO(m·n)O(m+n)
Difficulty1/52/5
WhenDon'tEasy variant
PE VerdictFirst row/col as markers + 2 bools.

Python Solutions

Copy matrix O(m·n) O(m·n)

Same.

Pseudo-code

see Java

Complexity

Time: O(m·n) — Linear.
Space: O(m·n) — Copy.

Python code

from typing import List
import copy
class Solution:
    def setZeroes(self, m: List[List[int]]) -> None:
        M, N = len(m), len(m[0])
        c = copy.deepcopy(m)
        for i in range(M):
            for j in range(N):
                if m[i][j] == 0:
                    for k in range(N): c[i][k] = 0
                    for k in range(M): c[k][j] = 0
        for i in range(M): m[i] = c[i]
O(1) markers O(m·n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(m·n) — Linear.
Space: O(1) — Two bools.

Python code

from typing import List
class Solution:
    def setZeroes(self, m: List[List[int]]) -> None:
        M, N = len(m), len(m[0])
        firstRowZ = any(m[0][j] == 0 for j in range(N))
        firstColZ = any(m[i][0] == 0 for i in range(M))
        for i in range(1, M):
            for j in range(1, N):
                if m[i][j] == 0: m[i][0] = 0; m[0][j] = 0
        for i in range(1, M):
            for j in range(1, N):
                if m[i][0] == 0 or m[0][j] == 0: m[i][j] = 0
        if firstRowZ:
            for j in range(N): m[0][j] = 0
        if firstColZ:
            for i in range(M): m[i][0] = 0
O(m+n) sets O(m·n) O(m+n)

Same.

Pseudo-code

see Java

Complexity

Time: O(m·n) — Linear.
Space: O(m+n) — Sets.

Python code

from typing import List
class Solution:
    def setZeroes(self, m: List[List[int]]) -> None:
        rows, cols = set(), set()
        for i in range(len(m)):
            for j in range(len(m[0])):
                if m[i][j] == 0: rows.add(i); cols.add(j)
        for i in range(len(m)):
            for j in range(len(m[0])):
                if i in rows or j in cols: m[i][j] = 0

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(m·n)O(m·n)
SpaceO(m·n)O(m+n)
Difficulty1/52/5
WhenDon'tEasy alt
PE VerdictFirst row/col as markers.

What the interviewer is really testing

In-place trick uses existing memory.

Top gotchas

  • First row/col DOUBLES as marker and target — track separately with bools.
  • Apply markers AFTER you've finished marking.
  • Finally zero first row/col if their bool is set.

Ship-it

O(1) markers.

#139 LC 202 Easy Happy Number

Floyd's tortoise & hare on the digit-square function — detect cycle.

Problem Statement

Sum of squares of digits, repeat. Happy if reaches 1; else loops.

Signature: boolean isHappy(int n)

Examples

19 → true (1²+9²=82→64+4=68→36+64=100→1)

Constraints

  • 1 <= n <= 2^31 - 1

Approach Overview

Brute Force

Java: HashSet of seen

Python: Seen set

O(log n)* O(log n)*

Optimal #1

Java: Floyd's cycle detection

Python: Floyd's

O(log n)* O(1)

Optimal #2

Java: Known unhappy cycle includes 4

Python: Hits 4 shortcut

O(log n)* O(1)

Java Solutions

HashSet of seen O(log n)* O(log n)*

Track visited; if revisit → false.

Pseudo-code

set; while n != 1: if n in set: false; add; n = squareSum(n)

Complexity

Time: O(log n)* — Bounded by cycle.
Space: O(log n)* — Set.

Edge cases & gotchas

  • n=1 → true.

Java code

import java.util.*;
class Solution {
    public boolean isHappy(int n) {
        Set<Integer> seen = new HashSet<>();
        while (n != 1) {
            if (!seen.add(n)) return false;
            n = sq(n);
        }
        return true;
    }
    private int sq(int n) { int s = 0; while (n > 0) { int d = n % 10; s += d*d; n /= 10; } return s; }
}
Floyd's cycle detection O(log n)* O(1)

Slow/fast pointers; meet implies cycle.

Pseudo-code

slow=n; fast=sq(n); while fast!=1 and slow!=fast: slow=sq(slow); fast=sq(sq(fast)); return fast==1

Complexity

Time: O(log n)* — Bounded.
Space: O(1) — None.

Java code

class Solution {
    public boolean isHappy(int n) {
        int slow = n, fast = sq(n);
        while (fast != 1 && slow != fast) {
            slow = sq(slow);
            fast = sq(sq(fast));
        }
        return fast == 1;
    }
    private int sq(int n) { int s = 0; while (n > 0) { int d = n % 10; s += d*d; n /= 10; } return s; }
}
Known unhappy cycle includes 4 O(log n)* O(1)

Every unhappy number eventually hits 4.

Pseudo-code

while n != 1 and n != 4: n = sq(n); return n == 1

Complexity

Time: O(log n)* — Bounded.
Space: O(1) — None.

Java code

class Solution {
    public boolean isHappy(int n) {
        while (n != 1 && n != 4) n = sq(n);
        return n == 1;
    }
    private int sq(int n) { int s = 0; while (n > 0) { int d = n % 10; s += d*d; n /= 10; } return s; }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(log n)*O(log n)*
SpaceO(log n)*O(1)
Difficulty1/52/5
WhenDon'tMathy shortcut
PE VerdictFloyd's cycle detection.

Python Solutions

Seen set O(log n)* O(log n)*

Same.

Pseudo-code

see Java

Complexity

Time: O(log n)* — Bounded.
Space: O(log n)* — Set.

Python code

class Solution:
    def isHappy(self, n: int) -> bool:
        seen = set()
        def sq(x):
            s = 0
            while x: x, d = divmod(x, 10); s += d*d
            return s
        while n != 1:
            if n in seen: return False
            seen.add(n); n = sq(n)
        return True
Floyd's O(log n)* O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(log n)* — Bounded.
Space: O(1) — None.

Python code

class Solution:
    def isHappy(self, n: int) -> bool:
        def sq(x):
            s = 0
            while x: x, d = divmod(x, 10); s += d*d
            return s
        slow, fast = n, sq(n)
        while fast != 1 and slow != fast:
            slow = sq(slow); fast = sq(sq(fast))
        return fast == 1
Hits 4 shortcut O(log n)* O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(log n)* — Bounded.
Space: O(1) — None.

Python code

class Solution:
    def isHappy(self, n: int) -> bool:
        def sq(x):
            s = 0
            while x: x, d = divmod(x, 10); s += d*d
            return s
        while n != 1 and n != 4: n = sq(n)
        return n == 1

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(log n)*O(log n)*
SpaceO(log n)*O(1)
Difficulty1/52/5
WhenDon'tMathy
PE VerdictFloyd's cycle.

What the interviewer is really testing

Cycle detection on implicit sequence.

Top gotchas

  • Sequence either reaches 1 or enters a small cycle (4→16→37→58→89→145→42→20→4).
  • Floyd's two-pointer avoids a hash set.
  • Numbers bounded — for big input, max ~3 digits eventually.

Ship-it

Floyd's cycle detection.

#140 LC 66 Easy Plus One

Walk right-to-left; carry; if leftmost overflows, prepend 1.

Problem Statement

Increment large integer represented as digit array.

Signature: int[] plusOne(int[] digits)

Examples

[1,2,3] → [1,2,4]
[9] → [1,0]

Constraints

  • 1 <= n <= 100

Approach Overview

Brute Force

Java: BigInt convert

Python: Int convert

O(n) O(n)

Optimal #1

Java: Carry walk

Python: Carry walk

O(n) O(1)

Optimal #2

Java: Single-pass with carry

Python: Explicit carry

O(n) O(1)

Java Solutions

BigInt convert O(n) O(n)

Form number, add 1, split back.

Pseudo-code

BigInt.valueOf(digits) + 1; split

Complexity

Time: O(n) — Stringify.
Space: O(n) — BigInt.

Java code

import java.math.BigInteger;
class Solution {
    public int[] plusOne(int[] d) {
        StringBuilder sb = new StringBuilder();
        for (int x : d) sb.append(x);
        BigInteger n = new BigInteger(sb.toString()).add(BigInteger.ONE);
        String s = n.toString();
        int[] out = new int[s.length()];
        for (int i = 0; i < s.length(); i++) out[i] = s.charAt(i) - '0';
        return out;
    }
}
Carry walk O(n) O(1)

Right-to-left; if digit<9, ++ and return.

Pseudo-code

for i=n-1..0: if d[i]<9: d[i]++; return d; d[i]=0; return [1]+d

Complexity

Time: O(n) — Single pass.
Space: O(1) — Output.

Edge cases & gotchas

  • All 9s → length grows by 1.

Java code

class Solution {
    public int[] plusOne(int[] d) {
        for (int i = d.length - 1; i >= 0; i--) {
            if (d[i] < 9) { d[i]++; return d; }
            d[i] = 0;
        }
        int[] out = new int[d.length + 1];
        out[0] = 1;
        return out;
    }
}
Single-pass with carry O(n) O(1)

Explicit carry variable.

Pseudo-code

carry=1; for i=n-1..0: t=d[i]+carry; d[i]=t%10; carry=t/10; if carry: prepend

Complexity

Time: O(n) — Single pass.
Space: O(1) — Output.

Java code

class Solution {
    public int[] plusOne(int[] d) {
        int carry = 1;
        for (int i = d.length - 1; i >= 0; i--) {
            int t = d[i] + carry;
            d[i] = t % 10;
            carry = t / 10;
            if (carry == 0) return d;
        }
        int[] out = new int[d.length + 1];
        out[0] = 1;
        return out;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(n)O(1)
Difficulty1/52/5
WhenDon'tExplicit carry
PE VerdictCarry walk.

Python Solutions

Int convert O(n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(n) — Int.

Python code

from typing import List
class Solution:
    def plusOne(self, d: List[int]) -> List[int]:
        n = int(''.join(map(str, d))) + 1
        return [int(c) for c in str(n)]
Carry walk O(n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Single pass.
Space: O(1) — Output.

Python code

from typing import List
class Solution:
    def plusOne(self, d: List[int]) -> List[int]:
        for i in range(len(d) - 1, -1, -1):
            if d[i] < 9: d[i] += 1; return d
            d[i] = 0
        return [1] + d
Explicit carry O(n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Single pass.
Space: O(1) — Output.

Python code

from typing import List
class Solution:
    def plusOne(self, d: List[int]) -> List[int]:
        carry = 1
        for i in range(len(d) - 1, -1, -1):
            t = d[i] + carry
            d[i] = t % 10; carry = t // 10
            if not carry: return d
        return [1] + d

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n)
SpaceO(n)O(1)
Difficulty1/52/5
WhenDon'tEquivalent
PE VerdictCarry walk.

What the interviewer is really testing

Toy problem, watch overflow.

Top gotchas

  • All-9 case grows array length by 1.
  • Don't convert via int for huge inputs (use opt1 in interview).
  • Walk right-to-left, return as soon as no carry.

Ship-it

Carry walk.

#141 LC 50 Medium Pow(x, n)

Fast exponentiation — square the base, halve the exponent.

Problem Statement

Compute x raised to the integer power n.

Signature: double myPow(double x, int n)

Examples

x=2.0, n=10 → 1024.0
x=2.0, n=-2 → 0.25

Constraints

  • -100.0 < x < 100.0
  • -2^31 <= n <= 2^31 - 1

Approach Overview

Brute Force

Java: Linear multiply

Python: Linear

O(n) O(1)

Optimal #1

Java: Iterative fast pow

Python: Iterative fast pow

O(log n) O(1)

Optimal #2

Java: Recursive divide & conquer

Python: Recursive D&C

O(log n) O(log n)

Java Solutions

Linear multiply O(n) O(1)

Multiply x by itself |n| times.

Pseudo-code

r=1; for i in 1..|n|: r*=x; if n<0: r=1/r

Complexity

Time: O(n) — Linear.
Space: O(1) — None.

Edge cases & gotchas

  • n=0 → 1.
  • negative n.

Java code

class Solution {
    public double myPow(double x, int n) {
        long N = n;
        boolean neg = N < 0; if (neg) N = -N;
        double r = 1;
        for (long i = 0; i < N; i++) r *= x;
        return neg ? 1 / r : r;
    }
}
Iterative fast pow O(log n) O(1)

Square x, halve n; multiply when bit set.

Pseudo-code

r=1; while n: if n&1: r*=x; x*=x; n>>=1; handle negative

Complexity

Time: O(log n) — Halving.
Space: O(1) — None.

Edge cases & gotchas

  • n = Integer.MIN_VALUE — use long.

Java code

class Solution {
    public double myPow(double x, int n) {
        long N = n;
        if (N < 0) { x = 1 / x; N = -N; }
        double r = 1;
        while (N > 0) {
            if ((N & 1) == 1) r *= x;
            x *= x;
            N >>= 1;
        }
        return r;
    }
}
Recursive divide & conquer O(log n) O(log n)

pow(x, n) = pow(x, n/2)².

Pseudo-code

pow(x,n): if n==0 return 1; half = pow(x, n/2); return n%2==0 ? half*half : half*half*x

Complexity

Time: O(log n) — Halving.
Space: O(log n) — Stack.

Java code

class Solution {
    public double myPow(double x, int n) {
        long N = n;
        if (N < 0) { x = 1 / x; N = -N; }
        return helper(x, N);
    }
    private double helper(double x, long n) {
        if (n == 0) return 1;
        double half = helper(x, n / 2);
        return n % 2 == 0 ? half * half : half * half * x;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(log n)
SpaceO(1)O(log n)
Difficulty1/53/5
WhenDon'tRecursive clarity
PE VerdictIterative fast pow.

Python Solutions

Linear O(n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — None.

Python code

class Solution:
    def myPow(self, x: float, n: int) -> float:
        neg = n < 0; n = abs(n); r = 1
        for _ in range(n): r *= x
        return 1 / r if neg else r
Iterative fast pow O(log n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(log n) — Halving.
Space: O(1) — None.

Python code

class Solution:
    def myPow(self, x: float, n: int) -> float:
        if n < 0: x = 1 / x; n = -n
        r = 1.0
        while n:
            if n & 1: r *= x
            x *= x; n >>= 1
        return r
Recursive D&C O(log n) O(log n)

Same.

Pseudo-code

see Java

Complexity

Time: O(log n) — Halving.
Space: O(log n) — Stack.

Python code

class Solution:
    def myPow(self, x: float, n: int) -> float:
        if n < 0: x = 1 / x; n = -n
        def rec(b, e):
            if e == 0: return 1
            half = rec(b, e // 2)
            return half * half if e % 2 == 0 else half * half * b
        return rec(x, n)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(log n)
SpaceO(1)O(log n)
Difficulty1/53/5
WhenDon'tRecursive
PE VerdictIterative fast pow.

What the interviewer is really testing

Exponentiation by squaring.

Top gotchas

  • n = Integer.MIN_VALUE → must widen to long before negating.
  • Negative exponent: invert base, negate exponent.
  • Each iteration: square base, halve exponent, accumulate if bit set.

Ship-it

Iterative fast pow.

#142 LC 43 Medium Multiply Strings

Grade-school multiply — digit at position (i+j+1) ← num1[i]·num2[j].

Problem Statement

Multiply two non-negative integers represented as strings; return product string.

Signature: String multiply(String num1, String num2)

Examples

'123', '456' → '56088'

Constraints

  • 1 <= len <= 200

Approach Overview

Brute Force

Java: BigInteger

Python: int()

O(n·m) O(n+m)

Optimal #1

Java: Grade-school multiply

Python: Grade-school

O(n·m) O(n+m)

Optimal #2

Java: Add partial products

Python: Add partials

O(n·m) O(n+m)

Java Solutions

BigInteger O(n·m) O(n+m)

Use java.math.BigInteger.

Pseudo-code

new BigInteger(a).multiply(new BigInteger(b)).toString()

Complexity

Time: O(n·m) — Library.
Space: O(n+m) — Output.

Edge cases & gotchas

  • Either '0' → '0'.

Java code

import java.math.BigInteger;
class Solution {
    public String multiply(String a, String b) {
        return new BigInteger(a).multiply(new BigInteger(b)).toString();
    }
}
Grade-school multiply O(n·m) O(n+m)

Result[i+j+1] += a[i]·b[j]; carry up.

Pseudo-code

r = new int[n+m]; for i: for j: r[i+j+1] += a[i]*b[j]; pass: handle carry; trim leading zeros

Complexity

Time: O(n·m) — Pairwise.
Space: O(n+m) — Output.

Java code

class Solution {
    public String multiply(String a, String b) {
        if (a.equals("0") || b.equals("0")) return "0";
        int n = a.length(), m = b.length();
        int[] r = new int[n + m];
        for (int i = n - 1; i >= 0; i--) for (int j = m - 1; j >= 0; j--) {
            int mul = (a.charAt(i) - '0') * (b.charAt(j) - '0');
            int sum = r[i + j + 1] + mul;
            r[i + j + 1] = sum % 10;
            r[i + j] += sum / 10;
        }
        StringBuilder sb = new StringBuilder();
        for (int d : r) if (!(sb.length() == 0 && d == 0)) sb.append(d);
        return sb.length() == 0 ? "0" : sb.toString();
    }
}
Add partial products O(n·m) O(n+m)

For each digit of b, multiply by a; shift; sum.

Pseudo-code

for each digit of b right-to-left: multiply by a; shift left; addStrings

Complexity

Time: O(n·m) — Pairwise.
Space: O(n+m) — Strings.

Java code

class Solution {
    public String multiply(String a, String b) {
        if (a.equals("0") || b.equals("0")) return "0";
        String r = "0";
        for (int i = b.length() - 1; i >= 0; i--) {
            int digit = b.charAt(i) - '0';
            StringBuilder partial = new StringBuilder();
            int carry = 0;
            for (int j = a.length() - 1; j >= 0; j--) {
                int p = (a.charAt(j) - '0') * digit + carry;
                partial.append(p % 10);
                carry = p / 10;
            }
            if (carry > 0) partial.append(carry);
            partial.reverse();
            for (int k = 0; k < b.length() - 1 - i; k++) partial.append('0');
            r = add(r, partial.toString());
        }
        return r;
    }
    private String add(String a, String b) {
        StringBuilder sb = new StringBuilder();
        int i = a.length() - 1, j = b.length() - 1, carry = 0;
        while (i >= 0 || j >= 0 || carry > 0) {
            int s = carry;
            if (i >= 0) s += a.charAt(i--) - '0';
            if (j >= 0) s += b.charAt(j--) - '0';
            sb.append(s % 10); carry = s / 10;
        }
        return sb.reverse().toString();
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n·m)O(n·m + (n+m)·m)
SpaceO(n+m)O(n+m)
Difficulty1/54/5
WhenAllowedShow partials
PE VerdictGrade-school positional multiply.

Python Solutions

int() O(n·m) O(n+m)

Cast strings to ints.

Pseudo-code

return str(int(a)*int(b))

Complexity

Time: O(n·m) — BigInt.
Space: O(n+m) — Output.

Python code

class Solution:
    def multiply(self, a: str, b: str) -> str:
        return str(int(a) * int(b))
Grade-school O(n·m) O(n+m)

Same.

Pseudo-code

see Java

Complexity

Time: O(n·m) — Pairwise.
Space: O(n+m) — Output.

Python code

class Solution:
    def multiply(self, a: str, b: str) -> str:
        if a == '0' or b == '0': return '0'
        n, m = len(a), len(b)
        r = [0]*(n+m)
        for i in range(n-1, -1, -1):
            for j in range(m-1, -1, -1):
                mul = (ord(a[i])-48) * (ord(b[j])-48)
                s = r[i+j+1] + mul
                r[i+j+1] = s % 10
                r[i+j] += s // 10
        s = ''.join(str(d) for d in r).lstrip('0')
        return s or '0'
Add partials O(n·m) O(n+m)

Same.

Pseudo-code

see Java

Complexity

Time: O(n·m) — Pairwise.
Space: O(n+m) — Strings.

Python code

class Solution:
    def multiply(self, a: str, b: str) -> str:
        if a == '0' or b == '0': return '0'
        def add(x, y):
            i, j, carry = len(x)-1, len(y)-1, 0
            out = []
            while i >= 0 or j >= 0 or carry:
                s = carry
                if i >= 0: s += ord(x[i])-48; i -= 1
                if j >= 0: s += ord(y[j])-48; j -= 1
                out.append(str(s % 10)); carry = s // 10
            return ''.join(reversed(out))
        r = '0'
        for i in range(len(b)-1, -1, -1):
            d = ord(b[i]) - 48
            partial = []
            carry = 0
            for j in range(len(a)-1, -1, -1):
                p = (ord(a[j])-48)*d + carry
                partial.append(str(p % 10)); carry = p // 10
            if carry: partial.append(str(carry))
            partial = ''.join(reversed(partial)) + '0' * (len(b)-1-i)
            r = add(r, partial)
        return r

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n·m)O(n·m)
SpaceO(n+m)O(n+m)
Difficulty1/54/5
WhenAllowedPartials
PE VerdictGrade-school positional.

What the interviewer is really testing

Big-integer multiplication without bignum.

Top gotchas

  • Result length is at most n+m digits.
  • Position formula: a[i]*b[j] lands at r[i+j+1] with carry to r[i+j].
  • Strip leading zeros at the end; handle '0' input specially.

Ship-it

Grade-school positional multiply.

#143 LC 2013 Medium Detect Squares

Map point counts; for each diagonal pair, multiply the other two corners' counts.

Problem Statement

Design DS: add points; count axis-aligned squares with a query point as one corner.

Signature: class DetectSquares { void add(int[] point); int count(int[] point); }

Examples

add [3,10],[11,2],[3,2]; count [11,10] → 1

Constraints

  • 0 <= x, y <= 1000

Approach Overview

Brute Force

Java: Scan all 3-tuples

Python: Triple loop

O(n³) O(n)

Optimal #1

Java: Count map + diagonal pivot

Python: Map + diagonal pivot

O(k) per query O(k)

Optimal #2

Java: Same idea, iterate over map entries

Python: Iterate distinct entries

O(k) O(k)

Java Solutions

Scan all 3-tuples O(n³) O(n)

For each pair of distinct points, check square.

Pseudo-code

for each pair (a,b): determine third+fourth; if present count

Complexity

Time: O(n³) — Triple.
Space: O(n) — Points.

Edge cases & gotchas

  • Zero-area squares not counted.

Java code

import java.util.*;
class DetectSquares {
    private List<int[]> pts = new ArrayList<>();
    public void add(int[] p) { pts.add(p); }
    public int count(int[] p) {
        int total = 0;
        for (int[] a : pts) for (int[] b : pts) for (int[] c : pts) {
            // a,b,c + p form a square?
            int[][] q = {a, b, c, p};
            if (isSquare(q)) total++;
        }
        return total / 8; // permutations
    }
    private boolean isSquare(int[][] q) { return false; /* simplistic placeholder */ }
}
Count map + diagonal pivot O(k) per query O(k)

For each existing point with same |dx|=|dy| from query, multiply by counts of the other two corners.

Pseudo-code

map (x,y) → count; query (qx,qy): for each (x,y): if |qx-x|==|qy-y|>0: total += cnt[(x,y)]*cnt[(x,qy)]*cnt[(qx,y)]

Complexity

Time: O(k) per query — k = #distinct points.
Space: O(k) — Map.

Edge cases & gotchas

  • Zero-length sides skipped (qx-x>0).

Java code

import java.util.*;
class DetectSquares {
    private Map<Long, Integer> cnt = new HashMap<>();
    private List<int[]> pts = new ArrayList<>();
    private long key(int x, int y) { return (long) x * 2001 + y; }
    public void add(int[] p) {
        cnt.merge(key(p[0], p[1]), 1, Integer::sum);
        pts.add(p);
    }
    public int count(int[] p) {
        int qx = p[0], qy = p[1], total = 0;
        for (int[] o : pts) {
            int x = o[0], y = o[1];
            if (Math.abs(qx - x) != Math.abs(qy - y) || qx == x) continue;
            total += cnt.getOrDefault(key(x, qy), 0) * cnt.getOrDefault(key(qx, y), 0);
        }
        return total;
    }
}
Same idea, iterate over map entries O(k) O(k)

Iterate cnt.entrySet() instead of duplicates.

Pseudo-code

for each (x,y) in cnt: same logic; multiply by cnt of that key

Complexity

Time: O(k) — Distinct points.
Space: O(k) — Map.

Java code

import java.util.*;
class DetectSquares {
    private Map<Long, Integer> cnt = new HashMap<>();
    private long key(int x, int y) { return (long) x * 2001 + y; }
    public void add(int[] p) { cnt.merge(key(p[0], p[1]), 1, Integer::sum); }
    public int count(int[] p) {
        int qx = p[0], qy = p[1], total = 0;
        for (Map.Entry<Long, Integer> e : cnt.entrySet()) {
            long k = e.getKey();
            int x = (int) (k / 2001), y = (int) (k % 2001);
            if (Math.abs(qx - x) != Math.abs(qy - y) || qx == x) continue;
            total += e.getValue() * cnt.getOrDefault(key(x, qy), 0) * cnt.getOrDefault(key(qx, y), 0);
        }
        return total;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n³)O(k) per query
SpaceO(n)O(k)
Difficulty1/54/5
WhenDon'tMany duplicates
PE VerdictPivot through diagonal partner.

Python Solutions

Triple loop O(n³) O(n)

Same idea (rough sketch).

Pseudo-code

see Java

Complexity

Time: O(n³) — Naive.
Space: O(n) — Points.

Python code

from typing import List
from collections import Counter
class DetectSquares:
    def __init__(self):
        self.pts = []; self.cnt = Counter()
    def add(self, p: List[int]) -> None:
        self.pts.append(tuple(p)); self.cnt[tuple(p)] += 1
    def count(self, p: List[int]) -> int:
        qx, qy = p; total = 0
        # naive O(n^3) skeleton; opt1 is what you'd ship
        for a in self.pts:
            for b in self.pts:
                if a == b: continue
                # too slow in practice; included for illustration
                pass
        return self.fast_count(p)
    def fast_count(self, p):
        qx, qy = p; total = 0
        for (x, y), c in list(self.cnt.items()):
            if abs(qx-x) != abs(qy-y) or qx == x: continue
            total += c * self.cnt.get((x, qy), 0) * self.cnt.get((qx, y), 0)
        return total
Map + diagonal pivot O(n) per query O(k+n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) per query — Each prior point checked.
Space: O(k+n) — Map.

Python code

from typing import List
from collections import Counter
class DetectSquares:
    def __init__(self):
        self.pts = []
        self.cnt = Counter()
    def add(self, p: List[int]) -> None:
        self.pts.append(tuple(p))
        self.cnt[tuple(p)] += 1
    def count(self, p: List[int]) -> int:
        qx, qy = p; total = 0
        for (x, y) in self.pts:
            if abs(qx-x) != abs(qy-y) or qx == x: continue
            total += self.cnt.get((x, qy), 0) * self.cnt.get((qx, y), 0)
        return total
Iterate distinct entries O(k) O(k)

Iterate cnt instead of pts.

Pseudo-code

see Java

Complexity

Time: O(k) — Distinct.
Space: O(k) — Map.

Python code

from typing import List
from collections import Counter
class DetectSquares:
    def __init__(self):
        self.cnt = Counter()
    def add(self, p: List[int]) -> None:
        self.cnt[tuple(p)] += 1
    def count(self, p: List[int]) -> int:
        qx, qy = p; total = 0
        for (x, y), c in self.cnt.items():
            if abs(qx-x) != abs(qy-y) or qx == x: continue
            total += c * self.cnt.get((x, qy), 0) * self.cnt.get((qx, y), 0)
        return total

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n³)O(k) per query
SpaceO(n)O(k)
Difficulty1/54/5
WhenDon'tDuplicates
PE VerdictPivot through diagonal partner.

What the interviewer is really testing

Use the diagonal opposite as pivot, then look up the two side corners.

Top gotchas

  • Skip zero-area squares (qx == x).
  • Multiply counts of (x, qy) and (qx, y) — duplicates contribute correctly.
  • Map by (x, y) with COUNT, not just presence.

Ship-it

Pivot through diagonal partner.

Bit Manipulation

#144 LC 136 Easy Single Number

XOR everything — pairs cancel, singleton remains.

Problem Statement

Every element appears twice except one. Find it. O(n) time, O(1) space.

Signature: int singleNumber(int[] nums)

Examples

[2,2,1] → 1
[4,1,2,1,2] → 4

Constraints

  • 1 <= n <= 3·10^4

Approach Overview

Brute Force

Java: HashSet

Python: Set

O(n) O(n)

Optimal #1

Java: XOR fold

Python: XOR fold

O(n) O(1)

Optimal #2

Java: Sort + scan pairs

Python: Sort + pair scan

O(n log n) O(1)

Java Solutions

HashSet O(n) O(n)

Add/remove to find the odd one out.

Pseudo-code

set; for x: if in set remove else add; final set has the answer

Complexity

Time: O(n) — Hash ops.
Space: O(n) — Set.

Edge cases & gotchas

  • n=1 → nums[0].

Java code

import java.util.*;
class Solution {
    public int singleNumber(int[] nums) {
        Set<Integer> s = new HashSet<>();
        for (int x : nums) if (!s.add(x)) s.remove(x);
        return s.iterator().next();
    }
}
XOR fold O(n) O(1)

Each duplicate XORs to 0; singleton remains.

Pseudo-code

r=0; for x: r ^= x; return r

Complexity

Time: O(n) — Single pass.
Space: O(1) — None.

Java code

class Solution {
    public int singleNumber(int[] nums) {
        int r = 0;
        for (int x : nums) r ^= x;
        return r;
    }
}
Sort + scan pairs O(n log n) O(1)

After sort, the odd one out breaks pairing.

Pseudo-code

sort; for i in 0..n by 2: if nums[i]!=nums[i+1] return nums[i]; return nums[n-1]

Complexity

Time: O(n log n) — Sort.
Space: O(1) — None.

Java code

import java.util.*;
class Solution {
    public int singleNumber(int[] nums) {
        Arrays.sort(nums);
        for (int i = 0; i + 1 < nums.length; i += 2) if (nums[i] != nums[i+1]) return nums[i];
        return nums[nums.length - 1];
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n log n)
SpaceO(n)O(1)
Difficulty1/52/5
WhenDon'tNo XOR allowed
PE VerdictXOR fold.

Python Solutions

Set O(n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Hash ops.
Space: O(n) — Set.

Python code

from typing import List
class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        s = set()
        for x in nums:
            if x in s: s.remove(x)
            else: s.add(x)
        return s.pop()
XOR fold O(n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — None.

Python code

from typing import List
from functools import reduce
import operator
class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        return reduce(operator.xor, nums)
Sort + pair scan O(n log n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n log n) — Sort.
Space: O(1) — None.

Python code

from typing import List
class Solution:
    def singleNumber(self, nums: List[int]) -> int:
        nums.sort()
        for i in range(0, len(nums) - 1, 2):
            if nums[i] != nums[i+1]: return nums[i]
        return nums[-1]

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n)O(n log n)
SpaceO(n)O(1)
Difficulty1/52/5
WhenDon'tNo XOR
PE VerdictXOR fold.

What the interviewer is really testing

Canonical XOR property: a^a=0, a^0=a, XOR is associative+commutative.

Top gotchas

  • Init accumulator to 0.
  • Works on negatives too — XOR is bitwise.
  • Generalizes to Single Number II/III with bitwise counts.

Ship-it

XOR fold.

#145 LC 191 Easy Number of 1 Bits

n & (n-1) clears the lowest set bit — count iterations.

Problem Statement

Return number of 1-bits (Hamming weight).

Signature: int hammingWeight(int n)

Examples

11 (1011) → 3
128 (10000000) → 1

Constraints

  • 32-bit unsigned

Approach Overview

Brute Force

Java: Bit-by-bit shift

Python: Shift loop

O(32) O(1)

Optimal #1

Java: Kernighan trick

Python: Kernighan

O(popcount) O(1)

Optimal #2

Java: Builtin bitcount

Python: bin().count

O(1) O(1)

Java Solutions

Bit-by-bit shift O(32) O(1)

Check LSB; shift right.

Pseudo-code

cnt=0; for i in 0..31: cnt += (n>>i)&1; return cnt

Complexity

Time: O(32) — 32 iters.
Space: O(1) — None.

Edge cases & gotchas

  • Negative ints — use logical right shift.

Java code

class Solution {
    public int hammingWeight(int n) {
        int cnt = 0;
        for (int i = 0; i < 32; i++) if (((n >>> i) & 1) == 1) cnt++;
        return cnt;
    }
}
Kernighan trick O(popcount) O(1)

n &= n-1 clears lowest 1; iterate until zero.

Pseudo-code

cnt=0; while n: n &= n-1; cnt++; return cnt

Complexity

Time: O(popcount) — k iters.
Space: O(1) — None.

Java code

class Solution {
    public int hammingWeight(int n) {
        int cnt = 0;
        while (n != 0) { n &= n - 1; cnt++; }
        return cnt;
    }
}
Builtin bitcount O(1) O(1)

Integer.bitCount.

Pseudo-code

return Integer.bitCount(n)

Complexity

Time: O(1) — Library.
Space: O(1) — None.

Java code

class Solution {
    public int hammingWeight(int n) { return Integer.bitCount(n); }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(32)O(1)
SpaceO(1)O(1)
Difficulty1/51/5
WhenDon'tAllowed builtin
PE VerdictKernighan trick.

Python Solutions

Shift loop O(32) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(32) — 32 iters.
Space: O(1) — None.

Python code

class Solution:
    def hammingWeight(self, n: int) -> int:
        cnt = 0
        for i in range(32):
            if (n >> i) & 1: cnt += 1
        return cnt
Kernighan O(popcount) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(popcount) — k iters.
Space: O(1) — None.

Python code

class Solution:
    def hammingWeight(self, n: int) -> int:
        cnt = 0
        while n:
            n &= n - 1; cnt += 1
        return cnt
bin().count O(1) O(1)

Python builtin.

Pseudo-code

return bin(n).count('1')

Complexity

Time: O(1) — Library.
Space: O(1) — None.

Python code

class Solution:
    def hammingWeight(self, n: int) -> int:
        return bin(n).count('1')

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(32)O(1)
SpaceO(1)O(1)
Difficulty1/51/5
WhenDon'tAllowed builtin
PE VerdictKernighan trick.

What the interviewer is really testing

Bit-trick warmup.

Top gotchas

  • n & (n-1) clears the LOWEST set bit — fewer iterations than 32-shift.
  • Use unsigned right shift (>>>) in Java for negative ints.
  • Builtin is fine but interviewer wants Kernighan.

Ship-it

Kernighan trick.

#146 LC 338 Easy Counting Bits

DP — bits(i) = bits(i >> 1) + (i & 1).

Problem Statement

Return array ans where ans[i] = number of 1-bits in i, for 0..n.

Signature: int[] countBits(int n)

Examples

n=5 → [0,1,1,2,1,2]

Constraints

  • 0 <= n <= 10^5

Approach Overview

Brute Force

Java: Per-i bit count

Python: Per-i count

O(n log n) O(n)

Optimal #1

Java: DP via i>>1

Python: DP via i>>1

O(n) O(n)

Optimal #2

Java: DP via i & (i-1)

Python: DP via i & i-1

O(n) O(n)

Java Solutions

Per-i bit count O(n log n) O(n)

Kernighan per element.

Pseudo-code

for i in 0..n: cnt = popcount(i)

Complexity

Time: O(n log n) — log per i.
Space: O(n) — Output.

Java code

class Solution {
    public int[] countBits(int n) {
        int[] r = new int[n + 1];
        for (int i = 0; i <= n; i++) {
            int x = i, c = 0;
            while (x != 0) { x &= x - 1; c++; }
            r[i] = c;
        }
        return r;
    }
}
DP via i>>1 O(n) O(n)

bits(i) = bits(i/2) + (i & 1).

Pseudo-code

r[0]=0; for i=1..n: r[i] = r[i>>1] + (i&1)

Complexity

Time: O(n) — Linear.
Space: O(n) — Output.

Edge cases & gotchas

  • n=0 → [0].

Java code

class Solution {
    public int[] countBits(int n) {
        int[] r = new int[n + 1];
        for (int i = 1; i <= n; i++) r[i] = r[i >> 1] + (i & 1);
        return r;
    }
}
DP via i & (i-1) O(n) O(n)

bits(i) = bits(i & i-1) + 1.

Pseudo-code

r[0]=0; for i=1..n: r[i] = r[i & (i-1)] + 1

Complexity

Time: O(n) — Linear.
Space: O(n) — Output.

Java code

class Solution {
    public int[] countBits(int n) {
        int[] r = new int[n + 1];
        for (int i = 1; i <= n; i++) r[i] = r[i & (i - 1)] + 1;
        return r;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n log n)O(n)
SpaceO(n)O(n)
Difficulty1/53/5
WhenDon'tEquivalent
PE VerdictDP via i>>1.

Python Solutions

Per-i count O(n log n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n log n) — Log per i.
Space: O(n) — Output.

Python code

from typing import List
class Solution:
    def countBits(self, n: int) -> List[int]:
        return [bin(i).count('1') for i in range(n+1)]
DP via i>>1 O(n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(n) — Output.

Python code

from typing import List
class Solution:
    def countBits(self, n: int) -> List[int]:
        r = [0]*(n+1)
        for i in range(1, n+1): r[i] = r[i >> 1] + (i & 1)
        return r
DP via i & i-1 O(n) O(n)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(n) — Output.

Python code

from typing import List
class Solution:
    def countBits(self, n: int) -> List[int]:
        r = [0]*(n+1)
        for i in range(1, n+1): r[i] = r[i & (i - 1)] + 1
        return r

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n log n)O(n)
SpaceO(n)O(n)
Difficulty1/53/5
WhenDon'tEquivalent
PE VerdictDP via i>>1.

What the interviewer is really testing

DP with bit-trick recurrence.

Top gotchas

  • bits(i) = bits(i/2) + (i & 1) — the last bit dropped is the +1.
  • Or bits(i) = bits(i & (i-1)) + 1 — Kernighan trick recurrence.
  • Output length is n+1, not n.

Ship-it

DP via i>>1.

#147 LC 190 Easy Reverse Bits

Pop LSB; shift result left and OR.

Problem Statement

Reverse the bits of a 32-bit unsigned integer.

Signature: int reverseBits(int n)

Examples

00000010100101000001111010011100 → 00111001011110000010100101000000

Constraints

  • 32-bit

Approach Overview

Brute Force

Java: Bit-by-bit shift

Python: Bit-by-bit

O(32) O(1)

Optimal #1

Java: Pop-and-shift

Python: Pop-and-shift

O(32) O(1)

Optimal #2

Java: Byte-swap with mask

Python: Byte-swap masks

O(1) O(1)

Java Solutions

Bit-by-bit shift O(32) O(1)

Iterate 32 bits.

Pseudo-code

r=0; for i=0..31: r |= ((n>>i)&1) << (31-i)

Complexity

Time: O(32) — 32 iters.
Space: O(1) — None.

Java code

class Solution {
    public int reverseBits(int n) {
        int r = 0;
        for (int i = 0; i < 32; i++) r |= ((n >>> i) & 1) << (31 - i);
        return r;
    }
}
Pop-and-shift O(32) O(1)

Build r by pushing LSB; shift n right.

Pseudo-code

r=0; for 32: r = (r<<1) | (n & 1); n >>>= 1

Complexity

Time: O(32) — 32 iters.
Space: O(1) — None.

Java code

class Solution {
    public int reverseBits(int n) {
        int r = 0;
        for (int i = 0; i < 32; i++) { r = (r << 1) | (n & 1); n >>>= 1; }
        return r;
    }
}
Byte-swap with mask O(1) O(1)

Pairs / nibbles / bytes — O(log 32).

Pseudo-code

swap adjacent bits, then pairs, then nibbles, then bytes, then halves

Complexity

Time: O(1) — 5 swaps.
Space: O(1) — None.

Java code

class Solution {
    public int reverseBits(int n) {
        n = ((n & 0xFFFF0000) >>> 16) | ((n & 0x0000FFFF) << 16);
        n = ((n & 0xFF00FF00) >>> 8)  | ((n & 0x00FF00FF) << 8);
        n = ((n & 0xF0F0F0F0) >>> 4)  | ((n & 0x0F0F0F0F) << 4);
        n = ((n & 0xCCCCCCCC) >>> 2)  | ((n & 0x33333333) << 2);
        n = ((n & 0xAAAAAAAA) >>> 1)  | ((n & 0x55555555) << 1);
        return n;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(32)O(1)
SpaceO(1)O(1)
Difficulty1/54/5
WhenDon'tHot path
PE VerdictPop-and-shift.

Python Solutions

Bit-by-bit O(32) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(32) — 32 iters.
Space: O(1) — None.

Python code

class Solution:
    def reverseBits(self, n: int) -> int:
        r = 0
        for i in range(32): r |= ((n >> i) & 1) << (31 - i)
        return r
Pop-and-shift O(32) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(32) — 32 iters.
Space: O(1) — None.

Python code

class Solution:
    def reverseBits(self, n: int) -> int:
        r = 0
        for _ in range(32):
            r = (r << 1) | (n & 1); n >>= 1
        return r
Byte-swap masks O(1) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(1) — 5 swaps.
Space: O(1) — None.

Python code

class Solution:
    def reverseBits(self, n: int) -> int:
        n = ((n & 0xFFFF0000) >> 16) | ((n & 0x0000FFFF) << 16)
        n = ((n & 0xFF00FF00) >> 8)  | ((n & 0x00FF00FF) << 8)
        n = ((n & 0xF0F0F0F0) >> 4)  | ((n & 0x0F0F0F0F) << 4)
        n = ((n & 0xCCCCCCCC) >> 2)  | ((n & 0x33333333) << 2)
        n = ((n & 0xAAAAAAAA) >> 1)  | ((n & 0x55555555) << 1)
        return n & 0xFFFFFFFF

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(32)O(1)
SpaceO(1)O(1)
Difficulty1/54/5
WhenDon'tHot path
PE VerdictPop-and-shift.

What the interviewer is really testing

Two natural patterns: bit-by-bit and mask swaps.

Top gotchas

  • Use unsigned right shift (>>>) in Java; Python ints are arbitrary precision so mask with 0xFFFFFFFF.
  • Bit-swap version is constant-time, useful in performance-critical contexts.
  • Build the result by pushing LSB then shifting left — order matters.

Ship-it

Pop-and-shift.

#148 LC 268 Easy Missing Number

XOR indices and values — only the missing index survives. Or Gauss sum.

Problem Statement

Array contains n distinct numbers from [0..n]. Find the missing one.

Signature: int missingNumber(int[] nums)

Examples

[3,0,1] → 2
[9,6,4,2,3,5,7,0,1] → 8

Constraints

  • 1 <= n <= 10^4

Approach Overview

Brute Force

Java: Sort + scan

Python: Sort + scan

O(n log n) O(1)

Optimal #1

Java: XOR indices & values

Python: XOR

O(n) O(1)

Optimal #2

Java: Gauss sum

Python: Gauss sum

O(n) O(1)

Java Solutions

Sort + scan O(n log n) O(1)

Sort; first index where nums[i] != i.

Pseudo-code

sort; for i: if nums[i] != i: return i; return n

Complexity

Time: O(n log n) — Sort.
Space: O(1) — None.

Edge cases & gotchas

  • Missing is n itself.

Java code

import java.util.*;
class Solution {
    public int missingNumber(int[] nums) {
        Arrays.sort(nums);
        for (int i = 0; i < nums.length; i++) if (nums[i] != i) return i;
        return nums.length;
    }
}
XOR indices & values O(n) O(1)

r ^= i ^ nums[i]; r ^= n.

Pseudo-code

r=n; for i: r ^= i ^ nums[i]

Complexity

Time: O(n) — Single pass.
Space: O(1) — None.

Java code

class Solution {
    public int missingNumber(int[] nums) {
        int r = nums.length;
        for (int i = 0; i < nums.length; i++) r ^= i ^ nums[i];
        return r;
    }
}
Gauss sum O(n) O(1)

Expected total - actual sum.

Pseudo-code

n*(n+1)/2 - sum(nums)

Complexity

Time: O(n) — Single pass.
Space: O(1) — None.

Java code

class Solution {
    public int missingNumber(int[] nums) {
        int n = nums.length;
        int total = n * (n + 1) / 2;
        for (int x : nums) total -= x;
        return total;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n log n)O(n)
SpaceO(1)O(1)
Difficulty1/51/5
WhenDon'tCleanest math
PE VerdictXOR — no overflow risk.

Python Solutions

Sort + scan O(n log n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n log n) — Sort.
Space: O(1) — None.

Python code

from typing import List
class Solution:
    def missingNumber(self, nums: List[int]) -> int:
        nums.sort()
        for i, x in enumerate(nums):
            if x != i: return i
        return len(nums)
XOR O(n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — None.

Python code

from typing import List
class Solution:
    def missingNumber(self, nums: List[int]) -> int:
        r = len(nums)
        for i, x in enumerate(nums): r ^= i ^ x
        return r
Gauss sum O(n) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(n) — Linear.
Space: O(1) — None.

Python code

from typing import List
class Solution:
    def missingNumber(self, nums: List[int]) -> int:
        n = len(nums)
        return n*(n+1)//2 - sum(nums)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(n log n)O(n)
SpaceO(1)O(1)
Difficulty1/51/5
WhenDon'tCleanest math
PE VerdictXOR.

What the interviewer is really testing

Sum vs XOR — XOR avoids overflow concerns.

Top gotchas

  • Initialize r = n (since indices only go up to n-1).
  • XOR each index AND each value — the missing value is the survivor.
  • Gauss sum is cleaner but can overflow on large arrays in Java.

Ship-it

XOR fold.

#149 LC 371 Medium Sum of Two Integers

Add without '+': sum = a XOR b (no carry); carry = (a AND b) << 1; loop.

Problem Statement

Return a + b without using + or - operators.

Signature: int getSum(int a, int b)

Examples

a=1, b=2 → 3
a=2, b=3 → 5
a=-1, b=1 → 0

Constraints

  • -1000 <= a, b <= 1000

Approach Overview

Brute Force

Java: Increment loop

Python: Inc/dec loop

O(|b|) O(1)

Optimal #1

Java: XOR + carry shift

Python: XOR + carry shift

O(32) O(1)

Optimal #2

Java: Recursive XOR + carry

Python: Cleaner sign-handle

O(32) O(32)

Java Solutions

Increment loop O(|b|) O(1)

Inc/dec until b is 0.

Pseudo-code

while b!=0: if b>0: a++,b--; else a--,b++; return a

Complexity

Time: O(|b|) — Linear in b.
Space: O(1) — None.

Edge cases & gotchas

  • Slow for big |b|.

Java code

class Solution {
    public int getSum(int a, int b) {
        while (b != 0) {
            if (b > 0) { a++; b--; } else { a--; b++; }
        }
        return a;
    }
}
XOR + carry shift O(32) O(1)

Iterate until carry becomes zero.

Pseudo-code

while b != 0: carry = (a & b) << 1; a = a ^ b; b = carry

Complexity

Time: O(32) — At most 32 iters.
Space: O(1) — None.

Edge cases & gotchas

  • Java handles negative ints natively in two's-complement.

Java code

class Solution {
    public int getSum(int a, int b) {
        while (b != 0) {
            int carry = (a & b) << 1;
            a ^= b;
            b = carry;
        }
        return a;
    }
}
Recursive XOR + carry O(32) O(32)

Same idea recursively.

Pseudo-code

getSum(a,b) = b==0 ? a : getSum(a^b, (a&b)<<1)

Complexity

Time: O(32) — Recursive.
Space: O(32) — Stack.

Java code

class Solution {
    public int getSum(int a, int b) {
        return b == 0 ? a : getSum(a ^ b, (a & b) << 1);
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(|b|)O(32)
SpaceO(1)O(32)
Difficulty1/54/5
WhenDon'tRecursive style
PE VerdictXOR + carry shift.

Python Solutions

Inc/dec loop O(|b|) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(|b|) — Linear.
Space: O(1) — None.

Python code

class Solution:
    def getSum(self, a: int, b: int) -> int:
        while b != 0:
            if b > 0: a += 1; b -= 1
            else: a -= 1; b += 1
        return a
XOR + carry shift O(32) O(1)

Mask to 32 bits because Python has arbitrary precision.

Pseudo-code

mask=0xFFFFFFFF; while b: a, b = (a ^ b) & mask, ((a & b) << 1) & mask; sign-extend if needed

Complexity

Time: O(32) — 32 iters.
Space: O(1) — None.

Edge cases & gotchas

  • Negative handling: convert back from 32-bit two's complement.

Python code

class Solution:
    def getSum(self, a: int, b: int) -> int:
        mask = 0xFFFFFFFF
        while b & mask:
            a, b = (a ^ b) & mask, ((a & b) << 1) & mask
        return a & mask if b > 0 else a if a <= 0x7FFFFFFF else ~(a ^ mask)
Cleaner sign-handle O(32) O(1)

Same with explicit unsign at end.

Pseudo-code

loop with mask; final: if a > INT_MAX subtract 2**32

Complexity

Time: O(32) — 32 iters.
Space: O(1) — None.

Python code

class Solution:
    def getSum(self, a: int, b: int) -> int:
        mask = 0xFFFFFFFF
        MAX = 0x7FFFFFFF
        while b != 0:
            a, b = (a ^ b) & mask, ((a & b) << 1) & mask
        return a if a <= MAX else a - (1 << 32)

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(|b|)O(32)
SpaceO(1)O(1)
Difficulty1/55/5
WhenDon'tDefault
PE VerdictXOR + carry shift, mask to 32 bits.

What the interviewer is really testing

Classic bit-arithmetic — show you know full-adder behavior.

Top gotchas

  • XOR is sum without carry; AND<<1 is the carry to add next round.
  • Java: native two's complement makes this trivial.
  • Python: must MASK to 32 bits and convert back since Python ints are unbounded.

Ship-it

XOR + carry shift (mask in Python).

#150 LC 7 Medium Reverse Integer

Pop digits with mod 10; push to result; detect overflow BEFORE pushing.

Problem Statement

Reverse digits of 32-bit signed integer. Return 0 if reversed value overflows.

Signature: int reverse(int x)

Examples

123 → 321
-123 → -321
120 → 21
1534236469 → 0 (overflow)

Constraints

  • -2^31 <= x <= 2^31 - 1

Approach Overview

Brute Force

Java: String reverse

Python: String reverse

O(log x) O(log x)

Optimal #1

Java: Pop-and-push with overflow check

Python: Pop-and-push (Python truncation quirk)

O(log x) O(1)

Optimal #2

Java: Use long, clamp at end

Python: Long compute + clamp

O(log x) O(1)

Java Solutions

String reverse O(log x) O(log x)

Use string ops, parse back, catch overflow.

Pseudo-code

sign = x<0; s = reverse(abs(x).toString()); try parse; on exception return 0

Complexity

Time: O(log x) — Digits.
Space: O(log x) — String.

Edge cases & gotchas

  • Integer.MIN_VALUE — Math.abs overflows.

Java code

class Solution {
    public int reverse(int x) {
        boolean neg = x < 0;
        String s = new StringBuilder(String.valueOf((long) Math.abs((long) x))).reverse().toString();
        try {
            long v = Long.parseLong(s);
            if (neg) v = -v;
            if (v < Integer.MIN_VALUE || v > Integer.MAX_VALUE) return 0;
            return (int) v;
        } catch (Exception e) { return 0; }
    }
}
Pop-and-push with overflow check O(log x) O(1)

Each iteration pop x%10, push to r; check overflow before pushing.

Pseudo-code

r=0; while x!=0: pop=x%10; x/=10; if would overflow Integer: return 0; r = r*10 + pop

Complexity

Time: O(log x) — Digit per iter.
Space: O(1) — None.

Edge cases & gotchas

  • Java truncates toward zero; works for negatives directly.

Java code

class Solution {
    public int reverse(int x) {
        int r = 0;
        while (x != 0) {
            int pop = x % 10;
            x /= 10;
            if (r > Integer.MAX_VALUE / 10 || (r == Integer.MAX_VALUE / 10 && pop > 7)) return 0;
            if (r < Integer.MIN_VALUE / 10 || (r == Integer.MIN_VALUE / 10 && pop < -8)) return 0;
            r = r * 10 + pop;
        }
        return r;
    }
}
Use long, clamp at end O(log x) O(1)

Compute in long; check against int range once.

Pseudo-code

long r=0; while x: r = r*10 + x%10; x/=10; if r out of int range: return 0

Complexity

Time: O(log x) — Digit per iter.
Space: O(1) — None.

Java code

class Solution {
    public int reverse(int x) {
        long r = 0;
        while (x != 0) { r = r * 10 + x % 10; x /= 10; }
        if (r < Integer.MIN_VALUE || r > Integer.MAX_VALUE) return 0;
        return (int) r;
    }
}

Java Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(log x)O(log x)
SpaceO(log x)O(1)
Difficulty2/52/5
WhenDon'tAllowed long
PE VerdictPop-and-push with overflow check.

Python Solutions

String reverse O(log x) O(log x)

Same.

Pseudo-code

see Java

Complexity

Time: O(log x) — Digits.
Space: O(log x) — String.

Python code

class Solution:
    def reverse(self, x: int) -> int:
        sign = -1 if x < 0 else 1
        r = sign * int(str(abs(x))[::-1])
        return r if -2**31 <= r <= 2**31 - 1 else 0
Pop-and-push (Python truncation quirk) O(log x) O(1)

Python // rounds toward neg infinity — handle negatives separately.

Pseudo-code

sign; work with abs(x); build r; restore sign; check range

Complexity

Time: O(log x) — Digits.
Space: O(1) — None.

Edge cases & gotchas

  • Python's % differs from Java for negatives.

Python code

class Solution:
    def reverse(self, x: int) -> int:
        sign = -1 if x < 0 else 1
        x = abs(x); r = 0
        while x:
            r = r * 10 + x % 10
            x //= 10
        r *= sign
        return r if -2**31 <= r <= 2**31 - 1 else 0
Long compute + clamp O(log x) O(1)

Same.

Pseudo-code

see Java

Complexity

Time: O(log x) — Digits.
Space: O(1) — None.

Python code

class Solution:
    def reverse(self, x: int) -> int:
        sign = -1 if x < 0 else 1
        n = abs(x); r = 0
        while n: r = r*10 + n % 10; n //= 10
        r *= sign
        return 0 if r < -2**31 or r > 2**31 - 1 else r

Python Trade-Off Table

DimensionBrute ForceOptimal #1Optimal #2
TimeO(log x)O(log x)
SpaceO(log x)O(1)
Difficulty2/52/5
WhenDon'tPermissive
PE VerdictPop-and-push, work on abs(x).

What the interviewer is really testing

Overflow detection is the actual trick.

Top gotchas

  • INT_MAX = 2147483647 — last digit constraint > 7 triggers overflow.
  • INT_MIN = -2147483648 — last digit constraint < -8 triggers overflow.
  • Python's // rounds toward -inf — operate on abs(x) and restore sign at the end.

Ship-it

Pop-and-push with explicit overflow check.

↑ Top