Top Interview 150

48 problems · six languages · every approach from naive to optimal

The NeetCode 250 topic pages already cover most of LeetCode's Top Interview 150 study plan; these are the 48 problems they miss. Each one is solved in Python, C++, Rust, TypeScript, Go, and Swift, and every solution is an approach ladder: it starts from a naive or instructive baseline and rebuilds toward the optimal answer. The tabs switch language everywhere at once; the ‹ › control at the bottom right of each code block steps through the ladder, and each implementation is published only after passing unit tests against the official LeetCode examples.

Array / String

1. Remove Duplicates from Sorted Array II

Medium · LC 80

Given a sorted array, remove duplicates in place so that each value appears at most twice, and return the length of the kept prefix. Walk the array once with a write pointer, copying each element forward whenever it differs from the element two slots behind the write position. The trick is that sorted order makes that single comparison sufficient: an element is a third-or-later copy exactly when it equals the value two slots back, so the filter is online with no lookahead.

list.pop(i) shifts the whole tail left on every delete. Passes on LeetCode's small tests but is quadratic — fine at n <= 2000, times out well before LeetCode's 3 * 10^4 worst case.

Scan each run of equal values and copy min(run, 2) of them forward. Linear now, and generalizes to "keep at most k" by changing a single constant — but it needs lookahead to find each run's end, and the nested loops obscure what is really a one-condition filter.

Sorted input means nums[i] is a 3rd-or-later copy exactly when it equals the element two slots behind the write pointer. One pass, one comparison per element, no lookahead — an online filter.

Walk each run of equal values, append min(run, 2) copies to a fresh vector, then copy the result back. Correct and linear, but the follow-up explicitly asks for O(1) extra memory.

Sorted input means nums[i] is a 3rd-or-later copy exactly when it equals the element two slots behind the write pointer, so keep everything else and overwrite in place — no scratch vector needed.

Walk each run of equal values, push min(run, 2) copies onto a fresh Vec, then copy the result back. Correct and linear, but the follow-up explicitly asks for O(1) extra memory.

Sorted input means nums[i] is a 3rd-or-later copy exactly when it equals the element two slots behind the write pointer, so keep everything else and overwrite in place — no scratch Vec needed.

Walk each run of equal values, push min(run, 2) copies onto a fresh array, then copy the result back. Correct and linear, but the follow-up explicitly asks for O(1) extra memory.

Sorted input means nums[i] is a 3rd-or-later copy exactly when it equals the element two slots behind the write pointer, so keep everything else and overwrite in place — no scratch array needed.

Walk each run of equal values, append min(run, 2) copies to a fresh slice, then copy the result back. Correct and linear, but the follow-up explicitly asks for O(1) extra memory.

Sorted input means nums[i] is a 3rd-or-later copy exactly when it equals the element two slots behind the write pointer, so keep everything else and overwrite in place — no scratch slice needed.

Walk each run of equal values, append min(run, 2) copies to a fresh array, then copy the result back. Correct and linear, but the follow-up explicitly asks for O(1) extra memory.

Sorted input means nums[i] is a 3rd-or-later copy exactly when it equals the element two slots behind the write pointer, so keep everything else and overwrite in place — no scratch array needed.

def removeDuplicates_pop(self, nums: List[int]) -> int:
    i = 2
    while i < len(nums):
        if nums[i] == nums[i - 2]:  # sorted => three equal in a row
            nums.pop(i)
        else:
            i += 1
    return len(nums)
def removeDuplicates_run_length(self, nums: List[int]) -> int:
    n = len(nums)
    write = read = 0
    while read < n:
        run_end = read
        while run_end < n and nums[run_end] == nums[read]:
            run_end += 1
        for _ in range(min(run_end - read, 2)):
            nums[write] = nums[read]
            write += 1
        read = run_end
    return write
def removeDuplicates(self, nums: List[int]) -> int:
    write = 0
    for num in nums:
        if write < 2 or num != nums[write - 2]:
            nums[write] = num
            write += 1
    return write
int removeDuplicatesExtraArray(vector<int>& nums) {
    vector<int> kept;
    size_t read = 0;
    while (read < nums.size()) {
        size_t runEnd = read;
        while (runEnd < nums.size() && nums[runEnd] == nums[read]) {
            ++runEnd;
        }
        for (size_t c = 0; c < min<size_t>(runEnd - read, 2); ++c) {
            kept.push_back(nums[read]);
        }
        read = runEnd;
    }
    copy(kept.begin(), kept.end(), nums.begin());
    return static_cast<int>(kept.size());
}
int removeDuplicates(vector<int>& nums) {
    int write = 0;
    for (int num : nums) {
        if (write < 2 || num != nums[write - 2]) {
            nums[write++] = num;
        }
    }
    return write;
}
pub fn remove_duplicates_extra_array(nums: &mut Vec<i32>) -> i32 {
    let mut kept: Vec<i32> = Vec::with_capacity(nums.len());
    let mut read = 0usize;
    while read < nums.len() {
        let mut run_end = read;
        while run_end < nums.len() && nums[run_end] == nums[read] {
            run_end += 1;
        }
        for _ in 0..(run_end - read).min(2) {
            kept.push(nums[read]);
        }
        read = run_end;
    }
    nums[..kept.len()].copy_from_slice(&kept);
    kept.len() as i32
}
pub fn remove_duplicates(nums: &mut Vec<i32>) -> i32 {
    let mut write = 0usize;
    for i in 0..nums.len() {
        let num = nums[i];
        if write < 2 || num != nums[write - 2] {
            nums[write] = num;
            write += 1;
        }
    }
    write as i32
}
function removeDuplicatesExtraArray(nums: number[]): number {
    const kept: number[] = [];
    let read = 0;
    while (read < nums.length) {
        let runEnd = read;
        while (runEnd < nums.length && nums[runEnd] === nums[read]) {
            runEnd += 1;
        }
        for (let c = 0; c < Math.min(runEnd - read, 2); c += 1) {
            kept.push(nums[read]);
        }
        read = runEnd;
    }
    for (let i = 0; i < kept.length; i += 1) {
        nums[i] = kept[i];
    }
    return kept.length;
}
function removeDuplicates(nums: number[]): number {
    let write = 0;
    for (const num of nums) {
        if (write < 2 || num !== nums[write - 2]) {
            nums[write] = num;
            write += 1;
        }
    }
    return write;
}
func removeDuplicatesExtraArray(nums []int) int {
	kept := make([]int, 0, len(nums))
	read := 0
	for read < len(nums) {
		runEnd := read
		for runEnd < len(nums) && nums[runEnd] == nums[read] {
			runEnd++
		}
		take := runEnd - read
		if take > 2 {
			take = 2
		}
		for c := 0; c < take; c++ {
			kept = append(kept, nums[read])
		}
		read = runEnd
	}
	copy(nums, kept)
	return len(kept)
}
func removeDuplicates(nums []int) int {
	write := 0
	for _, num := range nums {
		if write < 2 || num != nums[write-2] {
			nums[write] = num
			write++
		}
	}
	return write
}
func removeDuplicatesExtraArray(_ nums: inout [Int]) -> Int {
    var kept: [Int] = []
    kept.reserveCapacity(nums.count)
    var read = 0
    while read < nums.count {
        var runEnd = read
        while runEnd < nums.count && nums[runEnd] == nums[read] {
            runEnd += 1
        }
        for _ in 0..<min(runEnd - read, 2) {
            kept.append(nums[read])
        }
        read = runEnd
    }
    nums.replaceSubrange(0..<kept.count, with: kept)
    return kept.count
}
func removeDuplicates(_ nums: inout [Int]) -> Int {
    var write = 0
    for num in nums {
        if write < 2 || num != nums[write - 2] {
            nums[write] = num
            write += 1
        }
    }
    return write
}
Recommended Approach 1 of 3 · Delete in place (brute force)O(n^2) time · O(1) space

2. H-Index

Medium · LC 274

Given an array of citation counts, return the h-index, the largest h such that h papers have at least h citations. Clamp each count into a bucket array indexed zero through n, then sweep h from n downward, accumulating the running number of papers with at least h citations and returning the first h that the count reaches. The trick is the clamp: since h can never exceed n, any citation count above n behaves exactly like n, which caps the buckets and makes the whole thing linear with no sort.

After sorting high-to-low, position i (1-based) has i papers with >= citations[i-1] citations; the last position where the citation count still reaches i is the h-index. The sort dominates the cost.

"h papers have >= h citations" is monotone in h (true for all values up to the answer, false after), so binary search the largest true h. Drops Approach 1's sorted copy and never mutates the input.

h <= n, so any citation count above n acts exactly like n. Clamp every paper into buckets[0..n], then sweep h from n downward and return the first h where the running count of papers >= h. Linear time — no comparison sort, no repeated counting passes.

After sorting high-to-low, position i (1-based) has i papers with >= citations[i-1] citations; the last position where the citation count still reaches i is the h-index. The sort dominates the cost.

h <= n, so any citation count above n acts exactly like n. Clamp every paper into buckets[0..n], then sweep h from n downward and return the first h where the running count of papers with >= h citations reaches h. Beats the sort's n log n outright.

After sorting high-to-low, position i (1-based) has i papers with >= citations[i-1] citations; the last position where the citation count still reaches i is the h-index. The sort dominates the cost.

h <= n, so any citation count above n acts exactly like n. Clamp every paper into buckets[0..n], then sweep h from n downward and return the first h where the running count of papers with >= h citations reaches h. Beats the sort's n log n outright.

After sorting high-to-low, position i (1-based) has i papers with >= citations[i-1] citations; the last position where the citation count still reaches i is the h-index. The sort dominates the cost.

h <= n, so any citation count above n acts exactly like n. Clamp every paper into buckets[0..n], then sweep h from n downward and return the first h where the running count of papers with >= h citations reaches h. Beats the sort's n log n outright.

After sorting high-to-low, position i (1-based) has i papers with >= citations[i-1] citations; the last position where the citation count still reaches i is the h-index. The sort dominates the cost.

h <= n, so any citation count above n acts exactly like n. Clamp every paper into buckets[0..n], then sweep h from n downward and return the first h where the running count of papers with >= h citations reaches h. Beats the sort's n log n outright.

After sorting high-to-low, position i (1-based) has i papers with >= citations[i-1] citations; the last position where the citation count still reaches i is the h-index. The sort dominates the cost.

h <= n, so any citation count above n acts exactly like n. Clamp every paper into buckets[0..n], then sweep h from n downward and return the first h where the running count of papers with >= h citations reaches h. Beats the sort's n log n outright.

def hIndex_sort(self, citations: List[int]) -> int:
    h = 0
    for i, c in enumerate(sorted(citations, reverse=True), start=1):
        if c >= i:
            h = i
        else:
            break
    return h
def hIndex_binary_search(self, citations: List[int]) -> int:
    lo, hi = 0, len(citations)
    while lo < hi:
        mid = (lo + hi + 1) // 2  # upper mid so the loop shrinks
        if sum(c >= mid for c in citations) >= mid:
            lo = mid
        else:
            hi = mid - 1
    return lo
def hIndex(self, citations: List[int]) -> int:
    n = len(citations)
    buckets = [0] * (n + 1)
    for c in citations:
        buckets[min(c, n)] += 1

    papers = 0  # papers with >= h citations
    for h in range(n, -1, -1):
        papers += buckets[h]
        if papers >= h:
            return h
    return 0  # unreachable: h = 0 always satisfies papers >= 0
int hIndexSort(vector<int>& citations) {
    sort(citations.begin(), citations.end(), greater<int>());
    int h = 0;
    for (int i = 0; i < static_cast<int>(citations.size()); ++i) {
        if (citations[i] >= i + 1) {
            h = i + 1;
        } else {
            break;
        }
    }
    return h;
}
int hIndex(vector<int>& citations) {
    int n = static_cast<int>(citations.size());
    vector<int> buckets(n + 1, 0);
    for (int c : citations) {
        ++buckets[min(c, n)];
    }
    int papers = 0;  // papers with >= h citations
    for (int h = n; h >= 0; --h) {
        papers += buckets[h];
        if (papers >= h) {
            return h;
        }
    }
    return 0;  // unreachable: h = 0 always satisfies papers >= 0
}
pub fn h_index_sort(citations: Vec<i32>) -> i32 {
    let mut citations = citations;
    citations.sort_unstable_by(|a, b| b.cmp(a));
    let mut h = 0;
    for (i, &c) in citations.iter().enumerate() {
        if c >= i as i32 + 1 {
            h = i as i32 + 1;
        } else {
            break;
        }
    }
    h
}
pub fn h_index(citations: Vec<i32>) -> i32 {
    let n = citations.len();
    let mut buckets = vec![0i32; n + 1];
    for c in citations {
        buckets[(c as usize).min(n)] += 1;
    }
    let mut papers = 0i32; // papers with >= h citations
    for h in (0..=n).rev() {
        papers += buckets[h];
        if papers >= h as i32 {
            return h as i32;
        }
    }
    0 // unreachable: h = 0 always satisfies papers >= 0
}
function hIndexSort(citations: number[]): number {
    const sorted = [...citations].sort((a, b) => b - a);
    let h = 0;
    for (let i = 0; i < sorted.length; i += 1) {
        if (sorted[i] >= i + 1) {
            h = i + 1;
        } else {
            break;
        }
    }
    return h;
}
function hIndex(citations: number[]): number {
    const n = citations.length;
    const buckets = new Array<number>(n + 1).fill(0);
    for (const c of citations) {
        buckets[Math.min(c, n)] += 1;
    }
    let papers = 0; // papers with >= h citations
    for (let h = n; h >= 0; h -= 1) {
        papers += buckets[h];
        if (papers >= h) {
            return h;
        }
    }
    return 0; // unreachable: h = 0 always satisfies papers >= 0
}
func hIndexSort(citations []int) int {
	sort.Sort(sort.Reverse(sort.IntSlice(citations)))
	h := 0
	for i, c := range citations {
		if c >= i+1 {
			h = i + 1
		} else {
			break
		}
	}
	return h
}
func hIndex(citations []int) int {
	n := len(citations)
	buckets := make([]int, n+1)
	for _, c := range citations {
		if c > n {
			c = n
		}
		buckets[c]++
	}
	papers := 0 // papers with >= h citations
	for h := n; h >= 0; h-- {
		papers += buckets[h]
		if papers >= h {
			return h
		}
	}
	return 0 // unreachable: h = 0 always satisfies papers >= 0
}
func hIndexSort(_ citations: [Int]) -> Int {
    let sorted = citations.sorted(by: >)
    var h = 0
    for (i, c) in sorted.enumerated() {
        if c >= i + 1 {
            h = i + 1
        } else {
            break
        }
    }
    return h
}
func hIndex(_ citations: [Int]) -> Int {
    let n = citations.count
    var buckets = [Int](repeating: 0, count: n + 1)
    for c in citations {
        buckets[min(c, n)] += 1
    }
    var papers = 0  // papers with >= h citations
    for h in stride(from: n, through: 0, by: -1) {
        papers += buckets[h]
        if papers >= h {
            return h
        }
    }
    return 0  // unreachable: h = 0 always satisfies papers >= 0
}
Recommended Approach 1 of 3 · Sort descending and scanO(n log n) time · O(n) (sorted copy; O(1) if sorted in place) space

3. Insert Delete GetRandom O(1)

Medium · LC 380

Design a set that supports insert, remove, and getRandom in average O(1) time each. Pair a dense array of values with a hash map from value to index: insert appends and records the position, getRandom picks a uniform random array element, and remove swaps the victim with the last element before popping the tail. The trick is the swap-pop, which keeps the array free of holes so random selection stays uniform, and the pitfall is forgetting to update the moved element's map entry to its new index.

Correct, but misses the point of the problem: membership tests and deletions scan the whole array. Fine at a few thousand ops; the judge's 2 * 10^5 ops would crawl.

A val->index map kills the scans. remove() just tombstones the slot; the array is compacted once dead slots reach half its length, so at least half of every array is alive and getRandom's rejection loop finishes in < 2 expected draws.

The array gives uniform O(1) random access; the map gives O(1) lookup. remove() swap-pops the victim with the last element so the array never has holes — no tombstones, no compaction spikes, no wasted slots.

Correct, but misses the point of the problem: membership tests and erase() scan and shift the whole vector. Fine at a few thousand ops; the judge's 2 * 10^5 ops would crawl.

The vector gives uniform O(1) random access, the map gives O(1) lookup, and remove() swap-pops the victim with the last element so the vector never has holes — no scans, no tail shifts.

Correct, but misses the point of the problem: membership tests scan the whole Vec and Vec::remove shifts the tail. Fine at a few thousand ops; the judge's 2 * 10^5 ops would crawl.

The Vec gives uniform O(1) random access, the map gives O(1) lookup, and remove() swap-pops the victim with the last element so the Vec never has holes — no scans, no tail shifts.

Correct, but misses the point of the problem: membership tests scan the whole array and splice() shifts the tail. Fine at a few thousand ops; the judge's 2 * 10^5 ops would crawl.

The array gives uniform O(1) random access, the Map gives O(1) lookup, and remove() swap-pops the victim with the last element so the array never has holes — no scans, no tail shifts.

Correct, but misses the point of the problem: membership tests and deletions scan the whole slice. Fine at a few thousand ops; the judge's 2 * 10^5 ops would crawl.

The slice gives uniform O(1) random access, the map gives O(1) lookup, and Remove swap-pops the victim with the last element so the slice never has holes — no scans, no tail shifts.

Correct, but misses the point of the problem: membership tests scan the whole array and remove(at:) shifts the tail. Fine at a few thousand ops; the judge's 2 * 10^5 ops would crawl.

The array gives uniform O(1) random access, the dictionary gives O(1) lookup, and remove() swap-pops the victim with the last element so the array never has holes — no scans, no tail shifts.

class RandomizedSetScan:
    """Same interface as RandomizedSet, no auxiliary index."""

    def __init__(self):
        self.values = []  # current members, unordered

    def insert(self, val: int) -> bool:
        if val in self.values:  # O(n) scan
            return False
        self.values.append(val)
        return True

    def remove(self, val: int) -> bool:
        if val not in self.values:  # O(n) scan
            return False
        self.values.remove(val)  # another scan plus a tail shift
        return True

    def getRandom(self) -> int:
        return random.choice(self.values)
class RandomizedSetLazy:
    """Same interface as RandomizedSet, genuinely different bookkeeping."""

    _DEAD = object()  # sentinel; never equal to a stored int

    def __init__(self):
        self.values = []    # members interleaved with _DEAD tombstones
        self.index_of = {}  # val -> position in self.values
        self.dead = 0

    def insert(self, val: int) -> bool:
        if val in self.index_of:
            return False
        self.index_of[val] = len(self.values)
        self.values.append(val)
        return True

    def remove(self, val: int) -> bool:
        if val not in self.index_of:
            return False
        self.values[self.index_of.pop(val)] = self._DEAD
        self.dead += 1
        if self.dead * 2 >= len(self.values):
            self._compact()
        return True

    def getRandom(self) -> int:
        while True:  # < 2 expected iterations: at least half the slots live
            val = random.choice(self.values)
            if val is not self._DEAD:
                return val

    def _compact(self):
        self.values = [v for v in self.values if v is not self._DEAD]
        self.index_of = {v: i for i, v in enumerate(self.values)}
        self.dead = 0
class RandomizedSet:
    """LeetCode 380. Insert Delete GetRandom O(1) — array + index map."""

    def __init__(self):
        self.values = []    # dense array of current members
        self.index_of = {}  # val -> position in self.values

    def insert(self, val: int) -> bool:
        if val in self.index_of:
            return False
        self.index_of[val] = len(self.values)
        self.values.append(val)
        return True

    def remove(self, val: int) -> bool:
        if val not in self.index_of:
            return False
        idx = self.index_of.pop(val)
        last = self.values.pop()
        if idx < len(self.values):  # victim wasn't the last element
            self.values[idx] = last
            self.index_of[last] = idx
        return True

    def getRandom(self) -> int:
        return random.choice(self.values)
class RandomizedSetScan {
    vector<int> values;  // current members, unordered
    mt19937 rng{381};

public:
    RandomizedSetScan() {}

    bool insert(int val) {
        if (find(values.begin(), values.end(), val) != values.end()) {
            return false;
        }
        values.push_back(val);
        return true;
    }

    bool remove(int val) {
        auto it = find(values.begin(), values.end(), val);
        if (it == values.end()) {
            return false;
        }
        values.erase(it);  // shifts the whole tail left
        return true;
    }

    int getRandom() {
        uniform_int_distribution<int> dist(
            0, static_cast<int>(values.size()) - 1);
        return values[dist(rng)];
    }
};
class RandomizedSet {
    vector<int> values;               // dense array of current members
    unordered_map<int, int> indexOf;  // val -> position in values
    mt19937 rng{380};

public:
    RandomizedSet() {}

    bool insert(int val) {
        if (indexOf.count(val)) {
            return false;
        }
        indexOf[val] = static_cast<int>(values.size());
        values.push_back(val);
        return true;
    }

    bool remove(int val) {
        auto it = indexOf.find(val);
        if (it == indexOf.end()) {
            return false;
        }
        int idx = it->second;
        indexOf.erase(it);
        int last = values.back();
        values.pop_back();
        if (idx < static_cast<int>(values.size())) {  // victim wasn't last
            values[idx] = last;
            indexOf[last] = idx;
        }
        return true;
    }

    int getRandom() {
        uniform_int_distribution<int> dist(
            0, static_cast<int>(values.size()) - 1);
        return values[dist(rng)];
    }
};
struct RandomizedSetScan {
    values: Vec<i32>, // current members, unordered
    rng_state: Cell<u64>,
}

impl RandomizedSetScan {
    fn new() -> Self {
        RandomizedSetScan {
            values: Vec::new(),
            rng_state: Cell::new(0x0381_1234_5678_9abc),
        }
    }

    fn insert(&mut self, val: i32) -> bool {
        if self.values.contains(&val) {
            return false;
        }
        self.values.push(val);
        true
    }

    fn remove(&mut self, val: i32) -> bool {
        match self.values.iter().position(|&v| v == val) {
            None => false,
            Some(idx) => {
                self.values.remove(idx); // shifts the whole tail left
                true
            }
        }
    }

    fn get_random(&self) -> i32 {
        let x = xorshift64(&self.rng_state);
        self.values[(x % self.values.len() as u64) as usize]
    }
}
struct RandomizedSet {
    values: Vec<i32>,              // dense array of current members
    index_of: HashMap<i32, usize>, // val -> position in values
    rng_state: Cell<u64>,
}

impl RandomizedSet {
    fn new() -> Self {
        RandomizedSet {
            values: Vec::new(),
            index_of: HashMap::new(),
            rng_state: Cell::new(0x0380_1234_5678_9abc),
        }
    }

    fn insert(&mut self, val: i32) -> bool {
        if self.index_of.contains_key(&val) {
            return false;
        }
        self.index_of.insert(val, self.values.len());
        self.values.push(val);
        true
    }

    fn remove(&mut self, val: i32) -> bool {
        match self.index_of.remove(&val) {
            None => false,
            Some(idx) => {
                let last = self.values.pop().unwrap();
                if idx < self.values.len() {
                    // victim wasn't the last element: swap it in
                    self.values[idx] = last;
                    self.index_of.insert(last, idx);
                }
                true
            }
        }
    }

    fn get_random(&self) -> i32 {
        let x = xorshift64(&self.rng_state);
        self.values[(x % self.values.len() as u64) as usize]
    }
}
class RandomizedSetScan {
    private values: number[] = []; // current members, unordered

    insert(val: number): boolean {
        if (this.values.includes(val)) { // O(n) scan
            return false;
        }
        this.values.push(val);
        return true;
    }

    remove(val: number): boolean {
        const idx = this.values.indexOf(val); // O(n) scan
        if (idx < 0) {
            return false;
        }
        this.values.splice(idx, 1); // shifts the whole tail left
        return true;
    }

    getRandom(): number {
        return this.values[Math.floor(Math.random() * this.values.length)];
    }
}
class RandomizedSet {
    private values: number[] = [];              // dense array of current members
    private indexOf: Map<number, number> = new Map(); // val -> position in values

    insert(val: number): boolean {
        if (this.indexOf.has(val)) {
            return false;
        }
        this.indexOf.set(val, this.values.length);
        this.values.push(val);
        return true;
    }

    remove(val: number): boolean {
        const idx = this.indexOf.get(val);
        if (idx === undefined) {
            return false;
        }
        this.indexOf.delete(val);
        const last = this.values.pop() as number;
        if (idx < this.values.length) { // victim wasn't the last element
            this.values[idx] = last;
            this.indexOf.set(last, idx);
        }
        return true;
    }

    getRandom(): number {
        return this.values[Math.floor(Math.random() * this.values.length)];
    }
}
type RandomizedSetScan struct {
	values []int // current members, unordered
	rng    *rand.Rand
}

func ConstructorScan() RandomizedSetScan {
	return RandomizedSetScan{rng: rand.New(rand.NewSource(381))}
}

func (s *RandomizedSetScan) scanFor(val int) int {
	for i, v := range s.values {
		if v == val {
			return i
		}
	}
	return -1
}

func (s *RandomizedSetScan) Insert(val int) bool {
	if s.scanFor(val) >= 0 {
		return false
	}
	s.values = append(s.values, val)
	return true
}

func (s *RandomizedSetScan) Remove(val int) bool {
	idx := s.scanFor(val)
	if idx < 0 {
		return false
	}
	s.values = append(s.values[:idx], s.values[idx+1:]...) // shifts the tail
	return true
}

func (s *RandomizedSetScan) GetRandom() int {
	return s.values[s.rng.Intn(len(s.values))]
}
type RandomizedSet struct {
	values  []int       // dense array of current members
	indexOf map[int]int // val -> position in values
	rng     *rand.Rand
}

func Constructor() RandomizedSet {
	return RandomizedSet{
		indexOf: make(map[int]int),
		rng:     rand.New(rand.NewSource(380)),
	}
}

func (s *RandomizedSet) Insert(val int) bool {
	if _, ok := s.indexOf[val]; ok {
		return false
	}
	s.indexOf[val] = len(s.values)
	s.values = append(s.values, val)
	return true
}

func (s *RandomizedSet) Remove(val int) bool {
	idx, ok := s.indexOf[val]
	if !ok {
		return false
	}
	delete(s.indexOf, val)
	last := s.values[len(s.values)-1]
	s.values = s.values[:len(s.values)-1]
	if idx < len(s.values) { // victim wasn't the last element
		s.values[idx] = last
		s.indexOf[last] = idx
	}
	return true
}

func (s *RandomizedSet) GetRandom() int {
	return s.values[s.rng.Intn(len(s.values))]
}
class RandomizedSetScan {
    private var values: [Int] = []  // current members, unordered
    private var rng = SplitMix64(seed: 381)

    init() {}

    func insert(_ val: Int) -> Bool {
        if values.contains(val) {  // O(n) scan
            return false
        }
        values.append(val)
        return true
    }

    func remove(_ val: Int) -> Bool {
        guard let idx = values.firstIndex(of: val) else {  // O(n) scan
            return false
        }
        values.remove(at: idx)  // shifts the whole tail left
        return true
    }

    func getRandom() -> Int {
        return values[Int.random(in: 0..<values.count, using: &rng)]
    }
}
class RandomizedSet {
    private var values: [Int] = []       // dense array of current members
    private var indexOf: [Int: Int] = [:] // val -> position in values
    private var rng = SplitMix64(seed: 380)

    init() {}

    func insert(_ val: Int) -> Bool {
        if indexOf[val] != nil {
            return false
        }
        indexOf[val] = values.count
        values.append(val)
        return true
    }

    func remove(_ val: Int) -> Bool {
        guard let idx = indexOf[val] else {
            return false
        }
        indexOf[val] = nil
        let last = values.removeLast()
        if idx < values.count {  // victim wasn't the last element
            values[idx] = last
            indexOf[last] = idx
        }
        return true
    }

    func getRandom() -> Int {
        return values[Int.random(in: 0..<values.count, using: &rng)]
    }
}
Recommended Approach 1 of 3 · Plain array, linear scansO(n) insert/remove, O(1) getRandom time · O(n) space

4. Integer to Roman

Medium · LC 12

Given an integer between 1 and 3999, convert it to a Roman numeral. Walk a table of thirteen value and symbol pairs from largest to smallest, using divmod to take as many copies of each symbol as fit before moving on. The trick is listing the subtractive forms like CM and IV directly in the table, which lets the plain greedy rule handle them with no special cases and extends past 3999 by just adding larger entries.

Mirrors how you'd write a numeral by hand: keep subtracting the largest value that still fits, one symbol at a time. Instructive, but re-tests the same table entry once per emitted symbol.

Each decimal digit maps independently to a fixed Roman chunk, so precompute all chunks and concatenate thousands/hundreds/tens/ones. No loops at all — but the 31 hardcoded chunks only cover 1..3999 and hide the greedy rule that generates them.

For each entry take as many copies as fit (divmod), then move on. One pass over 13 entries, no hardcoded chunk tables, and it extends past 3999 by just adding larger entries.

Each decimal digit maps independently to a fixed Roman chunk, so precompute all chunks and concatenate thousands/hundreds/tens/ones. No loops at all — but the 31 hardcoded chunks only cover 1..3999 and hide the greedy rule that generates them.

Peel copies of each entry from largest to smallest. One pass over 13 entries, no hardcoded chunk tables, and it extends past 3999 by just adding larger entries.

Each decimal digit maps independently to a fixed Roman chunk, so precompute all chunks and concatenate thousands/hundreds/tens/ones. No loops at all — but the 31 hardcoded chunks only cover 1..3999 and hide the greedy rule that generates them.

Peel copies of each entry from largest to smallest. One pass over 13 entries, no hardcoded chunk tables, and it extends past 3999 by just adding larger entries.

Each decimal digit maps independently to a fixed Roman chunk, so precompute all chunks and concatenate thousands/hundreds/tens/ones. No loops at all — but the 31 hardcoded chunks only cover 1..3999 and hide the greedy rule that generates them.

Peel copies of each entry from largest to smallest. One pass over 13 entries, no hardcoded chunk tables, and it extends past 3999 by just adding larger entries.

Each decimal digit maps independently to a fixed Roman chunk, so precompute all chunks and concatenate thousands/hundreds/tens/ones. No loops at all — but the 31 hardcoded chunks only cover 1..3999 and hide the greedy rule that generates them.

Peel copies of each entry from largest to smallest. One pass over 13 entries, no hardcoded chunk tables, and it extends past 3999 by just adding larger entries.

Each decimal digit maps independently to a fixed Roman chunk, so precompute all chunks and concatenate thousands/hundreds/tens/ones. No loops at all — but the 31 hardcoded chunks only cover 1..3999 and hide the greedy rule that generates them.

Peel copies of each entry from largest to smallest. One pass over 13 entries, no hardcoded chunk tables, and it extends past 3999 by just adding larger entries.

def intToRoman_subtraction(self, num: int) -> str:
    parts = []
    for value, symbol in self._TABLE:
        while num >= value:
            num -= value
            parts.append(symbol)
    return "".join(parts)
def intToRoman_digit_lookup(self, num: int) -> str:
    thousands = ["", "M", "MM", "MMM"]
    hundreds = ["", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"]
    tens = ["", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"]
    ones = ["", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"]
    return (thousands[num // 1000] + hundreds[num // 100 % 10]
            + tens[num // 10 % 10] + ones[num % 10])
def intToRoman(self, num: int) -> str:
    parts = []
    for value, symbol in self._TABLE:
        if num == 0:
            break
        count, num = divmod(num, value)
        parts.append(symbol * count)
    return "".join(parts)
string intToRomanDigitLookup(int num) {
    static const char* const kThousands[] = {"", "M", "MM", "MMM"};
    static const char* const kHundreds[] = {
        "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"};
    static const char* const kTens[] = {
        "", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"};
    static const char* const kOnes[] = {
        "", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"};
    return string(kThousands[num / 1000]) + kHundreds[num / 100 % 10] +
           kTens[num / 10 % 10] + kOnes[num % 10];
}
string intToRoman(int num) {
    static const pair<int, const char*> kTable[] = {
        {1000, "M"}, {900, "CM"}, {500, "D"}, {400, "CD"},
        {100, "C"}, {90, "XC"}, {50, "L"}, {40, "XL"},
        {10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"},
    };
    string out;
    for (const auto& [value, symbol] : kTable) {
        while (num >= value) {
            num -= value;
            out += symbol;
        }
    }
    return out;
}
pub fn int_to_roman_digit_lookup(num: i32) -> String {
    const THOUSANDS: [&str; 4] = ["", "M", "MM", "MMM"];
    const HUNDREDS: [&str; 10] = [
        "", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM",
    ];
    const TENS: [&str; 10] = ["", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"];
    const ONES: [&str; 10] = ["", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"];
    let num = num as usize;
    format!(
        "{}{}{}{}",
        THOUSANDS[num / 1000],
        HUNDREDS[num / 100 % 10],
        TENS[num / 10 % 10],
        ONES[num % 10]
    )
}
pub fn int_to_roman(num: i32) -> String {
    const TABLE: [(i32, &str); 13] = [
        (1000, "M"), (900, "CM"), (500, "D"), (400, "CD"),
        (100, "C"), (90, "XC"), (50, "L"), (40, "XL"),
        (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I"),
    ];
    let mut num = num;
    let mut out = String::new();
    for (value, symbol) in TABLE {
        while num >= value {
            num -= value;
            out.push_str(symbol);
        }
    }
    out
}
function intToRomanDigitLookup(num: number): string {
    const thousands = ["", "M", "MM", "MMM"];
    const hundreds = ["", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"];
    const tens = ["", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"];
    const ones = ["", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"];
    return (
        thousands[Math.floor(num / 1000)] +
        hundreds[Math.floor(num / 100) % 10] +
        tens[Math.floor(num / 10) % 10] +
        ones[num % 10]
    );
}
function intToRoman(num: number): string {
    const table: Array<[number, string]> = [
        [1000, "M"], [900, "CM"], [500, "D"], [400, "CD"],
        [100, "C"], [90, "XC"], [50, "L"], [40, "XL"],
        [10, "X"], [9, "IX"], [5, "V"], [4, "IV"], [1, "I"],
    ];
    let out = "";
    for (const [value, symbol] of table) {
        while (num >= value) {
            num -= value;
            out += symbol;
        }
    }
    return out;
}
func intToRomanDigitLookup(num int) string {
	thousands := []string{"", "M", "MM", "MMM"}
	hundreds := []string{"", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"}
	tens := []string{"", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"}
	ones := []string{"", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"}
	return thousands[num/1000] + hundreds[num/100%10] + tens[num/10%10] + ones[num%10]
}
func intToRoman(num int) string {
	values := []int{1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1}
	symbols := []string{"M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"}
	var out strings.Builder
	for i, value := range values {
		for num >= value {
			num -= value
			out.WriteString(symbols[i])
		}
	}
	return out.String()
}
func intToRomanDigitLookup(_ num: Int) -> String {
    let thousands = ["", "M", "MM", "MMM"]
    let hundreds = ["", "C", "CC", "CCC", "CD", "D", "DC", "DCC", "DCCC", "CM"]
    let tens = ["", "X", "XX", "XXX", "XL", "L", "LX", "LXX", "LXXX", "XC"]
    let ones = ["", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"]
    return thousands[num / 1000] + hundreds[num / 100 % 10]
        + tens[num / 10 % 10] + ones[num % 10]
}
func intToRoman(_ num: Int) -> String {
    let table: [(value: Int, symbol: String)] = [
        (1000, "M"), (900, "CM"), (500, "D"), (400, "CD"),
        (100, "C"), (90, "XC"), (50, "L"), (40, "XL"),
        (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I"),
    ]
    var num = num
    var out = ""
    for (value, symbol) in table {
        while num >= value {
            num -= value
            out += symbol
        }
    }
    return out
}
Recommended Approach 1 of 3 · Repeated subtractionO(1) time · O(1) (at most 15 symbols peeled per call) space

5. Length of Last Word

Easy · LC 58

Given a string of words and spaces, return the length of the last word. Scan from the end of the string, first skipping any trailing spaces, then counting characters until the next space or the start of the string. The trick is that scanning backward never touches the front of a long string, so the typical cost is proportional to the last word rather than the whole input.

str.split() with no argument trims the ends and collapses runs of spaces, so the last token is exactly the last word. Costs a full token list just to look at one entry.

Track the word in progress; remember its length on every non-space char. Drops Approach 1's token list, but still walks the whole string even when only the tail matters.

Skip trailing spaces from the end, then count characters until the next space. Never touches the front of a long string.

Let a stringstream do the space handling: extraction skips runs of whitespace, so the last extracted token is the last word. Copies the whole string into a stream just to look at one token.

Skip trailing spaces from the end, then count characters until the next space. No copies, and never touches the front of a long string.

split_whitespace trims the ends and collapses runs of spaces, so the last token is exactly the last word. Materializing every token in a Vec just to read one entry is the naive part; the iterator could stream, but this is the version people write first.

Skip trailing spaces from the end, then count characters until the next space. No allocations, and never touches the front of a long string.

Split on single spaces and filter out the empty pieces that leading/trailing/repeated spaces produce; the last token is exactly the last word. Builds the whole token array to look at one entry.

Skip trailing spaces from the end, then count characters until the next space. No allocations, and never touches the front of a long string.

strings.Fields trims the ends and collapses runs of spaces, so the last token is exactly the last word. Allocates the whole token slice just to look at one entry.

Skip trailing spaces from the end, then count characters until the next space. No allocations, and never touches the front of a long string.

split(separator:) drops the empty pieces that leading/trailing/ repeated spaces would produce, so the last token is exactly the last word. Builds the whole token array just to look at one entry.

Skip trailing spaces from the end, then count characters until the next space. No token list; after the one-time array conversion (Swift strings lack integer indexing) only the tail is touched.

def lengthOfLastWord_split(self, s: str) -> int:
    return len(s.split()[-1])
def lengthOfLastWord_forward(self, s: str) -> int:
    last = current = 0
    for char in s:
        if char == ' ':
            current = 0
        else:
            current += 1
            last = current
    return last
def lengthOfLastWord(self, s: str) -> int:
    i = len(s) - 1
    while i >= 0 and s[i] == ' ':
        i -= 1
    length = 0
    while i >= 0 and s[i] != ' ':
        length += 1
        i -= 1
    return length
int lengthOfLastWordSplit(string s) {
    istringstream in(s);
    string word, last;
    while (in >> word) {
        last = word;
    }
    return static_cast<int>(last.size());
}
int lengthOfLastWord(string s) {
    int i = static_cast<int>(s.size()) - 1;
    while (i >= 0 && s[i] == ' ') {
        --i;
    }
    int length = 0;
    while (i >= 0 && s[i] != ' ') {
        ++length;
        --i;
    }
    return length;
}
pub fn length_of_last_word_split(s: String) -> i32 {
    let words: Vec<&str> = s.split_whitespace().collect();
    words.last().map_or(0, |w| w.len() as i32)
}
pub fn length_of_last_word(s: String) -> i32 {
    let bytes = s.as_bytes();
    let mut i = bytes.len();
    while i > 0 && bytes[i - 1] == b' ' {
        i -= 1;
    }
    let mut length = 0;
    while i > 0 && bytes[i - 1] != b' ' {
        length += 1;
        i -= 1;
    }
    length
}
function lengthOfLastWordSplit(s: string): number {
    const words = s.split(" ").filter((word) => word.length > 0);
    return words[words.length - 1].length;
}
function lengthOfLastWord(s: string): number {
    let i = s.length - 1;
    while (i >= 0 && s[i] === " ") {
        i--;
    }
    let length = 0;
    while (i >= 0 && s[i] !== " ") {
        length++;
        i--;
    }
    return length;
}
func lengthOfLastWordSplit(s string) int {
	words := strings.Fields(s)
	return len(words[len(words)-1])
}
func lengthOfLastWord(s string) int {
	i := len(s) - 1
	for i >= 0 && s[i] == ' ' {
		i--
	}
	length := 0
	for i >= 0 && s[i] != ' ' {
		length++
		i--
	}
	return length
}
func lengthOfLastWordSplit(_ s: String) -> Int {
    let words = s.split(separator: " ")
    return words.last?.count ?? 0
}
func lengthOfLastWord(_ s: String) -> Int {
    let chars = Array(s)
    var i = chars.count - 1
    while i >= 0 && chars[i] == " " {
        i -= 1
    }
    var length = 0
    while i >= 0 && chars[i] != " " {
        length += 1
        i -= 1
    }
    return length
}
Recommended Approach 1 of 3 · Split (the pythonic one-liner)O(n) time · O(n) space

6. Reverse Words in a String

Medium · LC 151

Given a string of words separated by arbitrary runs of spaces, return the words in reverse order joined by single spaces. Split the string with no separator argument, reverse the resulting word list, and join it back with single spaces. The trick is that argument-free split already trims both ends and collapses runs of spaces, doing all the whitespace cleanup for free, and since Python strings are immutable any answer costs O(n) space anyway.

Collect each word and prepend it to the answer built so far. Every prepend copies the whole result, so many short words go quadratic — fine at LeetCode's n <= 10^4, painful much beyond that.

The classic C/C++ in-place trick: reverse the whole char array so the words land in the right order (letters backwards), then reverse each word back while compacting spaces with a write pointer. Linear time — no copy per word like Approach 1 — at the price of extra passes.

Walk right-to-left; for each word locate its end, then its start, and slice it out. One backward pass instead of Approach 2's reverse-then-re-reverse, with no character-by-character rebuilding.

str.split() with no argument trims the ends and collapses runs of spaces, doing all the cleanup for free; then it is just a reversed join. Any answer must be O(n) space in Python (strings are immutable).

Tokenize with a stringstream and prepend each word to the answer built so far. Every prepend copies the whole result, so many short words go quadratic — fine at LeetCode's n <= 10^4, painful beyond.

Collect the words, reverse the vector, join with single spaces. Linear — each character is copied a constant number of times — but it buffers every word in a second container next to the input.

Reverse the whole string so the words land in the right order (letters backwards), then reverse each word back while compacting extra spaces with a write pointer. No auxiliary buffers at all.

Prepend each word to the answer built so far. Every prepend rebuilds the whole result string, so many short words go quadratic — fine at LeetCode's n <= 10^4, painful much beyond.

split_whitespace trims the ends and collapses runs of spaces, doing all the cleanup for free; then it is just a reversed join. Each byte is copied a constant number of times.

Grow each word char by char and prepend it to the answer built so far. Every prepend copies the whole result, so many short words go quadratic — fine at LeetCode's n <= 10^4, painful much beyond that.

Split on single spaces, filter out the empty pieces that leading/trailing/repeated spaces produce, then reverse and join with single spaces. Each character is copied a constant number of times.

Grow each word byte by byte and prepend it to the answer built so far. Both the append and the prepend copy their whole operand, so many short words go quadratic — fine at LeetCode's n <= 10^4, painful much beyond that.

strings.Fields trims the ends and collapses runs of spaces, doing all the cleanup for free; then reverse the slice in place and join with single spaces. Each byte is copied a constant number of times.

Prepend each word to the answer built so far. Every prepend copies the whole result string, so many short words go quadratic — fine at LeetCode's n <= 10^4, painful much beyond that.

split(separator:) drops the empty pieces that leading/trailing/ repeated spaces would produce, doing all the cleanup for free; then it is just a reversed join with single spaces. Each character is copied a constant number of times.

def reverseWords_concat(self, s: str) -> str:
    result = ""
    word = ""
    for char in s + " ":
        if char != ' ':
            word += char
        elif word:
            result = word if not result else word + " " + result
            word = ""
    return result
def reverseWords_reverse_all(self, s: str) -> str:
    chars = list(s)[::-1]
    n = len(chars)
    write = read = 0
    while read < n:
        while read < n and chars[read] == ' ':
            read += 1
        if read == n:
            break
        if write > 0:           # single separator between words
            chars[write] = ' '
            write += 1
        start = write
        while read < n and chars[read] != ' ':
            chars[write] = chars[read]
            write += 1
            read += 1
        chars[start:write] = chars[start:write][::-1]
    return "".join(chars[:write])
def reverseWords_two_pointer(self, s: str) -> str:
    words = []
    i = len(s) - 1
    while i >= 0:
        while i >= 0 and s[i] == ' ':
            i -= 1
        if i < 0:
            break
        end = i
        while i >= 0 and s[i] != ' ':
            i -= 1
        words.append(s[i + 1:end + 1])
    return " ".join(words)
def reverseWords(self, s: str) -> str:
    return " ".join(reversed(s.split()))
string reverseWordsConcat(string s) {
    istringstream in(s);
    string word, result;
    while (in >> word) {
        result = result.empty() ? word : word + " " + result;
    }
    return result;
}
string reverseWordsSplit(string s) {
    istringstream in(s);
    vector<string> words;
    string word;
    while (in >> word) {
        words.push_back(word);
    }
    reverse(words.begin(), words.end());
    string out;
    out.reserve(s.size());
    for (size_t i = 0; i < words.size(); ++i) {
        if (i > 0) {
            out += ' ';
        }
        out += words[i];
    }
    return out;
}
string reverseWords(string s) {
    reverse(s.begin(), s.end());
    const int n = static_cast<int>(s.size());
    int write = 0;
    for (int read = 0; read < n;) {
        while (read < n && s[read] == ' ') {
            ++read;
        }
        if (read == n) {
            break;
        }
        if (write > 0) {  // single separator between words
            s[write++] = ' ';
        }
        const int start = write;
        while (read < n && s[read] != ' ') {
            s[write++] = s[read++];
        }
        reverse(s.begin() + start, s.begin() + write);
    }
    s.resize(write);
    return s;
}
pub fn reverse_words_concat(s: String) -> String {
    let mut result = String::new();
    for word in s.split_whitespace() {
        if result.is_empty() {
            result = word.to_string();
        } else {
            result = format!("{} {}", word, result);
        }
    }
    result
}
pub fn reverse_words(s: String) -> String {
    s.split_whitespace().rev().collect::<Vec<_>>().join(" ")
}
function reverseWordsConcat(s: string): string {
    let result = "";
    let word = "";
    for (const ch of s + " ") {
        if (ch !== " ") {
            word += ch;
        } else if (word.length > 0) {
            result = result.length === 0 ? word : word + " " + result;
            word = "";
        }
    }
    return result;
}
function reverseWords(s: string): string {
    return s
        .split(" ")
        .filter((word) => word.length > 0)
        .reverse()
        .join(" ");
}
func reverseWordsConcat(s string) string {
	result := ""
	word := ""
	for i := 0; i <= len(s); i++ {
		if i < len(s) && s[i] != ' ' {
			word += string(s[i])
		} else if word != "" {
			if result == "" {
				result = word
			} else {
				result = word + " " + result
			}
			word = ""
		}
	}
	return result
}
func reverseWords(s string) string {
	words := strings.Fields(s)
	for i, j := 0, len(words)-1; i < j; i, j = i+1, j-1 {
		words[i], words[j] = words[j], words[i]
	}
	return strings.Join(words, " ")
}
func reverseWordsConcat(_ s: String) -> String {
    var result = ""
    for word in s.split(separator: " ") {
        result = result.isEmpty ? String(word) : String(word) + " " + result
    }
    return result
}
func reverseWords(_ s: String) -> String {
    return s.split(separator: " ").reversed().joined(separator: " ")
}
Recommended Approach 1 of 4 · Repeated concatenationO(n^2) worst case time · O(n) space

7. Zigzag Conversion

Medium · LC 6

Given a string and a row count, write the characters in a zigzag pattern down and diagonally back up across the rows, then read the result row by row. Keep one bucket per row plus a step direction, dropping each character into the current row and flipping the step whenever the top or bottom row is hit. The pitfall is a single row: with nowhere to bounce, the flip logic would walk off the end, so that case is returned unchanged up front.

Walk an actual numRows x n grid — down a column, then diagonally up-right — and read the non-empty cells row by row. Instructive, but almost every cell is wasted padding.

Row 0 and the last row take one char per cycle; each middle row also takes the up-stroke char at j + cycle - 2*row. Kills the grid: emits the answer in order with no storage beyond the output, at the cost of numRows passes over s and fiddly index arithmetic.

For char i, p = i % cycle is its position in the cycle; positions past the bottom fold back up to row cycle - p. One sequential pass over s (better locality than Approach 2's jumps), no direction state to maintain.

Drop each char into its row, bouncing off the top and bottom rows. Same single pass as Approach 3 with the modular arithmetic replaced by a direction flag — nothing to derive, nothing to misremember. numRows == 1 has nowhere to bounce (the flip logic would walk off the end), so it is returned as-is up front.

Walk an actual numRows x n grid — down a column, then diagonally up-right — and read the non-empty cells row by row. Instructive, but almost every cell is wasted padding.

The column never mattered — only the row order did. Drop each char into its row while bouncing off the top and bottom rows, then concatenate the rows. numRows == 1 has nowhere to bounce, so it is returned as-is.

Walk an actual num_rows x n grid — down a column, then diagonally up-right — and read the non-empty cells row by row. Instructive, but almost every cell is wasted padding.

The column never mattered — only the row order did. Drop each char into its row while bouncing off the top and bottom rows, then concatenate the rows. num_rows == 1 has nowhere to bounce, so it is returned as-is.

Walk an actual numRows x n grid — down a column, then diagonally up-right — and read the non-empty cells row by row. Instructive, but almost every cell is wasted padding.

The column never mattered — only the row order did. Drop each char into its row while bouncing off the top and bottom rows, then concatenate the rows. numRows === 1 has nowhere to bounce, so it is returned as-is.

Walk an actual numRows x n grid — down a column, then diagonally up-right — and read the non-empty cells row by row. Instructive, but almost every cell is wasted padding.

The column never mattered — only the row order did. Drop each char into its row while bouncing off the top and bottom rows, then concatenate the rows. numRows == 1 has nowhere to bounce, so it is returned as-is.

Walk an actual numRows x n grid — down a column, then diagonally up-right — and read the non-empty cells row by row. Instructive, but almost every cell is wasted padding.

The column never mattered — only the row order did. Drop each char into its row while bouncing off the top and bottom rows, then concatenate the rows. numRows == 1 has nowhere to bounce, so it is returned as-is.

def convert_grid(self, s: str, numRows: int) -> str:
    if numRows == 1:
        return s
    n = len(s)
    grid = [[''] * n for _ in range(numRows)]
    row = col = 0
    down = True
    for char in s:
        grid[row][col] = char
        if down:
            if row + 1 == numRows:
                down = False
                row -= 1
                col += 1
            else:
                row += 1
        elif row == 0:
            down = True
            row += 1
        else:
            row -= 1
            col += 1
    return "".join(char for r in grid for char in r if char)
def convert_jump(self, s: str, numRows: int) -> str:
    if numRows == 1:
        return s
    n = len(s)
    cycle = 2 * numRows - 2
    out = []
    for row in range(numRows):
        for j in range(row, n, cycle):
            out.append(s[j])
            up = j + cycle - 2 * row
            if 0 < row < numRows - 1 and up < n:
                out.append(s[up])
    return "".join(out)
def convert_fold(self, s: str, numRows: int) -> str:
    if numRows == 1:
        return s
    cycle = 2 * numRows - 2
    rows = [[] for _ in range(numRows)]
    for i, char in enumerate(s):
        p = i % cycle
        rows[p if p < numRows else cycle - p].append(char)
    return "".join("".join(r) for r in rows)
def convert(self, s: str, numRows: int) -> str:
    if numRows == 1:
        return s
    rows = [[] for _ in range(numRows)]
    row, step = 0, 1
    for char in s:
        rows[row].append(char)
        if row == 0:
            step = 1
        elif row == numRows - 1:
            step = -1
        row += step
    return "".join("".join(r) for r in rows)
string convertGrid(string s, int numRows) {
    if (numRows == 1) {
        return s;
    }
    const int n = static_cast<int>(s.size());
    vector<vector<char>> grid(numRows, vector<char>(n, '\0'));
    int row = 0, col = 0;
    bool down = true;
    for (char c : s) {
        grid[row][col] = c;
        if (down) {
            if (row + 1 == numRows) {
                down = false;
                --row;
                ++col;
            } else {
                ++row;
            }
        } else if (row == 0) {
            down = true;
            ++row;
        } else {
            --row;
            ++col;
        }
    }
    string out;
    out.reserve(s.size());
    for (const auto& r : grid) {
        for (char c : r) {
            if (c != '\0') {
                out += c;
            }
        }
    }
    return out;
}
string convert(string s, int numRows) {
    if (numRows == 1) {
        return s;
    }
    vector<string> rows(numRows);
    int row = 0, step = 1;
    for (char c : s) {
        rows[row] += c;
        if (row == 0) {
            step = 1;
        } else if (row == numRows - 1) {
            step = -1;
        }
        row += step;
    }
    string out;
    out.reserve(s.size());
    for (const string& r : rows) {
        out += r;
    }
    return out;
}
pub fn convert_grid(s: String, num_rows: i32) -> String {
    let num_rows = num_rows as usize;
    if num_rows == 1 {
        return s;
    }
    let bytes = s.as_bytes();
    let n = bytes.len();
    let mut grid = vec![vec![0u8; n]; num_rows];
    let (mut row, mut col) = (0usize, 0usize);
    let mut down = true;
    for &b in bytes {
        grid[row][col] = b;
        if down {
            if row + 1 == num_rows {
                down = false;
                row -= 1;
                col += 1;
            } else {
                row += 1;
            }
        } else if row == 0 {
            down = true;
            row += 1;
        } else {
            row -= 1;
            col += 1;
        }
    }
    let mut out = Vec::with_capacity(n);
    for r in &grid {
        for &b in r {
            if b != 0 {
                out.push(b);
            }
        }
    }
    String::from_utf8(out).unwrap()
}
pub fn convert(s: String, num_rows: i32) -> String {
    let num_rows = num_rows as usize;
    if num_rows == 1 {
        return s;
    }
    let mut rows = vec![String::new(); num_rows];
    let mut row = 0usize;
    let mut down = true;
    for c in s.chars() {
        rows[row].push(c);
        if row == 0 {
            down = true;
        } else if row == num_rows - 1 {
            down = false;
        }
        if down {
            row += 1;
        } else {
            row -= 1;
        }
    }
    rows.concat()
}
function convertGrid(s: string, numRows: number): string {
    if (numRows === 1) {
        return s;
    }
    const n = s.length;
    const grid: string[][] = Array.from({ length: numRows }, () =>
        new Array<string>(n).fill(""),
    );
    let row = 0;
    let col = 0;
    let down = true;
    for (const ch of s) {
        grid[row][col] = ch;
        if (down) {
            if (row + 1 === numRows) {
                down = false;
                row--;
                col++;
            } else {
                row++;
            }
        } else if (row === 0) {
            down = true;
            row++;
        } else {
            row--;
            col++;
        }
    }
    let out = "";
    for (const r of grid) {
        for (const ch of r) {
            out += ch;
        }
    }
    return out;
}
function convert(s: string, numRows: number): string {
    if (numRows === 1) {
        return s;
    }
    const rows: string[] = new Array(numRows).fill("");
    let row = 0;
    let step = 1;
    for (const ch of s) {
        rows[row] += ch;
        if (row === 0) {
            step = 1;
        } else if (row === numRows - 1) {
            step = -1;
        }
        row += step;
    }
    return rows.join("");
}
func convertGrid(s string, numRows int) string {
	if numRows == 1 {
		return s
	}
	n := len(s)
	grid := make([][]byte, numRows)
	for r := range grid {
		grid[r] = make([]byte, n)
	}
	row, col := 0, 0
	down := true
	for i := 0; i < n; i++ {
		grid[row][col] = s[i]
		if down {
			if row+1 == numRows {
				down = false
				row--
				col++
			} else {
				row++
			}
		} else if row == 0 {
			down = true
			row++
		} else {
			row--
			col++
		}
	}
	out := make([]byte, 0, n)
	for _, r := range grid {
		for _, b := range r {
			if b != 0 {
				out = append(out, b)
			}
		}
	}
	return string(out)
}
func convert(s string, numRows int) string {
	if numRows == 1 {
		return s
	}
	rows := make([][]byte, numRows)
	row, step := 0, 1
	for i := 0; i < len(s); i++ {
		rows[row] = append(rows[row], s[i])
		if row == 0 {
			step = 1
		} else if row == numRows-1 {
			step = -1
		}
		row += step
	}
	out := make([]byte, 0, len(s))
	for _, r := range rows {
		out = append(out, r...)
	}
	return string(out)
}
func convertGrid(_ s: String, _ numRows: Int) -> String {
    if numRows == 1 {
        return s
    }
    let chars = Array(s)
    let n = chars.count
    var grid = Array(repeating: [Character?](repeating: nil, count: n), count: numRows)
    var row = 0
    var col = 0
    var down = true
    for ch in chars {
        grid[row][col] = ch
        if down {
            if row + 1 == numRows {
                down = false
                row -= 1
                col += 1
            } else {
                row += 1
            }
        } else if row == 0 {
            down = true
            row += 1
        } else {
            row -= 1
            col += 1
        }
    }
    var out = ""
    out.reserveCapacity(n)
    for r in grid {
        for case let ch? in r {
            out.append(ch)
        }
    }
    return out
}
func convert(_ s: String, _ numRows: Int) -> String {
    if numRows == 1 {
        return s
    }
    var rows = [String](repeating: "", count: numRows)
    var row = 0
    var step = 1
    for ch in s {
        rows[row].append(ch)
        if row == 0 {
            step = 1
        } else if row == numRows - 1 {
            step = -1
        }
        row += step
    }
    return rows.joined()
}
Recommended Approach 1 of 4 · Simulate the grid literallyO(numRows * n) time · O(numRows * n) space

8. Find the Index of the First Occurrence in a String

Easy · LC 28

Given a haystack string and a needle string, return the index of the needle's first occurrence, or -1 if it never appears. Use Knuth-Morris-Pratt: precompute for the needle the table of longest proper prefixes that are also suffixes, then scan the haystack once, letting the needle pointer fall back through the table on every mismatch. The payoff is that no haystack character is ever re-read, which replaces the quadratic worst case of naive alignment-by-alignment comparison with O(n + m).

Try every alignment and compare char by char, bailing on the first mismatch. The worst case needs pathological inputs ("aaaa..." vs "aaab"); for interview-sized strings this is the clearest start. A needle longer than the haystack yields an empty range -> -1.

What you would write outside an interview: CPython uses a tuned Crochemore-Perrin two-way search under the hood, so this is already linear. The catch is that the algorithm stays a black box — the next rung builds the linear-time machinery by hand.

Precompute the longest-proper-prefix-that-is-also-a-suffix table for the needle, then scan the haystack once; on a mismatch the needle pointer falls back via the table and no haystack char is re-read.

The first thing most people write: carve out the m-char substring at every start and compare it to the needle. Correct, but every probe allocates a fresh copy just to throw it away.

Same alignments, but compare in place char by char and bail on the first mismatch — no allocation per probe. A needle longer than the haystack makes the loop bound negative, so no alignment is tried and we return -1.

Try every start index — even the tail ones where the needle can no longer fit — and compare byte by byte behind an explicit bounds check. The wasted tail probes and the per-step bound test are what the next rung trims away.

Reject a too-long needle up front (which also keeps the n - m subtraction from underflowing), then compare the m-byte window at each remaining alignment with slice equality — a memcmp under the hood, with no per-step bounds checks.

The first thing most people write: carve out the m-char slice at every start and compare it to the needle. Correct, but every probe builds a fresh string just to throw it away.

Same alignments, but compare in place char by char and bail on the first mismatch — no slice per probe. A needle longer than the haystack leaves no alignment to try, so the loop never runs and we return -1.

Try every start index — even the tail ones where the needle can no longer fit — and compare byte by byte behind an explicit bounds check. The wasted tail probes and the per-step bound test are what the next rung trims away.

Bound the loop so only alignments where the needle fits are tried; the inner compare then needs no bounds check and bails on the first mismatch. A needle longer than the haystack leaves no alignment to try, so the loop never runs and we return -1.

The first thing most people write: copy out the m-char window at every start and compare whole arrays. Correct, but every probe materializes a fresh array just to throw it away.

Same alignments, but compare in place char by char and bail on the first mismatch — no copy per probe. A needle longer than the haystack is rejected up front (it also keeps the range bound non-negative).

def strStr_sliding(self, haystack: str, needle: str) -> int:
    n, m = len(haystack), len(needle)
    for start in range(n - m + 1):
        j = 0
        while j < m and haystack[start + j] == needle[j]:
            j += 1
        if j == m:
            return start
    return -1
def strStr_find(self, haystack: str, needle: str) -> int:
    return haystack.find(needle)
def strStr(self, haystack: str, needle: str) -> int:
    m = len(needle)
    if m == 0:
        return 0

    # lps[i] = length of the longest proper prefix of needle[:i+1]
    # that is also a suffix of it.
    lps = [0] * m
    length = 0
    for i in range(1, m):
        while length and needle[i] != needle[length]:
            length = lps[length - 1]
        if needle[i] == needle[length]:
            length += 1
        lps[i] = length

    j = 0
    for i, char in enumerate(haystack):
        while j and char != needle[j]:
            j = lps[j - 1]
        if char == needle[j]:
            j += 1
        if j == m:
            return i - m + 1
    return -1
int strStrSubstr(string haystack, string needle) {
    const int n = static_cast<int>(haystack.size());
    const int m = static_cast<int>(needle.size());
    for (int start = 0; start + m <= n; ++start) {
        if (haystack.substr(start, m) == needle) {
            return start;
        }
    }
    return -1;
}
int strStr(string haystack, string needle) {
    const int n = static_cast<int>(haystack.size());
    const int m = static_cast<int>(needle.size());
    for (int start = 0; start + m <= n; ++start) {
        int j = 0;
        while (j < m && haystack[start + j] == needle[j]) {
            ++j;
        }
        if (j == m) {
            return start;
        }
    }
    return -1;
}
pub fn str_str_nested(haystack: String, needle: String) -> i32 {
    let h = haystack.as_bytes();
    let nd = needle.as_bytes();
    if nd.is_empty() {
        return 0;
    }
    for start in 0..h.len() {
        let mut j = 0;
        while start + j < h.len() && j < nd.len() && h[start + j] == nd[j] {
            j += 1;
        }
        if j == nd.len() {
            return start as i32;
        }
    }
    -1
}
pub fn str_str(haystack: String, needle: String) -> i32 {
    let h = haystack.as_bytes();
    let nd = needle.as_bytes();
    let (n, m) = (h.len(), nd.len());
    if m > n {
        return -1;
    }
    for start in 0..=(n - m) {
        if &h[start..start + m] == nd {
            return start as i32;
        }
    }
    -1
}
function strStrSlice(haystack: string, needle: string): number {
    const n = haystack.length;
    const m = needle.length;
    for (let start = 0; start + m <= n; start++) {
        if (haystack.slice(start, start + m) === needle) {
            return start;
        }
    }
    return -1;
}
function strStr(haystack: string, needle: string): number {
    const n = haystack.length;
    const m = needle.length;
    for (let start = 0; start + m <= n; start++) {
        let j = 0;
        while (j < m && haystack[start + j] === needle[j]) {
            j++;
        }
        if (j === m) {
            return start;
        }
    }
    return -1;
}
func strStrNested(haystack string, needle string) int {
	n, m := len(haystack), len(needle)
	if m == 0 {
		return 0
	}
	for start := 0; start < n; start++ {
		j := 0
		for start+j < n && j < m && haystack[start+j] == needle[j] {
			j++
		}
		if j == m {
			return start
		}
	}
	return -1
}
func strStr(haystack string, needle string) int {
	n, m := len(haystack), len(needle)
	for start := 0; start+m <= n; start++ {
		j := 0
		for j < m && haystack[start+j] == needle[j] {
			j++
		}
		if j == m {
			return start
		}
	}
	return -1
}
func strStrSlice(_ haystack: String, _ needle: String) -> Int {
    let h = Array(haystack)
    let nd = Array(needle)
    if nd.count > h.count {
        return -1
    }
    for start in 0...(h.count - nd.count) {
        if Array(h[start..<start + nd.count]) == nd {
            return start
        }
    }
    return -1
}
func strStr(_ haystack: String, _ needle: String) -> Int {
    let h = Array(haystack)
    let nd = Array(needle)
    if nd.count > h.count {
        return -1
    }
    for start in 0...(h.count - nd.count) {
        var j = 0
        while j < nd.count && h[start + j] == nd[j] {
            j += 1
        }
        if j == nd.count {
            return start
        }
    }
    return -1
}
Recommended Approach 1 of 3 · Sliding window compareO(n * m) worst case time · O(1) space

9. Text Justification

Hard · LC 68

Given a list of words and a maximum width, pack the words greedily into lines and pad each line with spaces to exactly that width, distributing extra spaces left-heavy between the words. Pack words until the next one no longer fits, then size the gaps with divmod, which gives the base gap width plus how many of the leftmost gaps receive one extra space, avoiding any per-space loop. The pitfall is the exceptions: the last line and any line holding a single word are left-justified and padded on the right instead of spread.

Pack by index ranges, then deal the leftover spaces one at a time onto the gaps from the left until none remain. Correct, but the O(W) dribble per line is what Approach 2 collapses into one divmod.

divmod(spaces, gaps) gives the base gap width plus how many of the leftmost gaps get one extra space — no per-space loops needed.

Pack by index ranges, then deal the leftover spaces one at a time onto the gaps from the left until none remain. Correct, but the O(W) dribble per line is what Approach 2 collapses into a div/mod.

(maxWidth - chars) div/mod gaps gives the base gap width plus how many of the leftmost gaps get one extra space — no per-space loops.

Pack by index ranges, then deal the leftover spaces one at a time onto the gaps from the left until none remain. Correct, but the O(W) dribble per line is what Approach 2 collapses into a div/mod.

(width - chars) div/mod gaps gives the base gap width plus how many of the leftmost gaps get one extra space — no per-space loops.

Pack by index ranges, then deal the leftover spaces one at a time onto the gaps from the left until none remain. Correct, but the O(W) dribble per line is what Approach 2 collapses into a div/mod.

(maxWidth - letters) div/mod gaps gives the base gap width plus how many of the leftmost gaps get one extra space — no per-space loops.

Pack by index ranges, then deal the leftover spaces one at a time onto the gaps from the left until none remain. Correct, but the O(W) dribble per line is what Approach 2 collapses into a div/mod.

(maxWidth - chars) div/mod gaps gives the base gap width plus how many of the leftmost gaps get one extra space — no per-space loops.

Pack by index ranges, then deal the leftover spaces one at a time onto the gaps from the left until none remain. Correct, but the O(W) dribble per line is what Approach 2 collapses into a div/mod.

(maxWidth - chars) div/mod gaps gives the base gap width plus how many of the leftmost gaps get one extra space — no per-space loops.

def fullJustify_roundrobin(self, words: List[str], maxWidth: int) -> List[str]:
    lines: List[str] = []
    i, n = 0, len(words)
    while i < n:
        j, width = i + 1, len(words[i])  # width counts 1 space per gap
        while j < n and width + 1 + len(words[j]) <= maxWidth:
            width += 1 + len(words[j])
            j += 1
        chunk = words[i:j]
        if j == n or len(chunk) == 1:    # last line or single word
            lines.append(" ".join(chunk).ljust(maxWidth))
        else:
            gaps = [1] * (len(chunk) - 1)
            spare, k = maxWidth - width, 0
            while spare:
                gaps[k % len(gaps)] += 1
                k += 1
                spare -= 1
            pieces = [chunk[0]]
            for gap, word in zip(gaps, chunk[1:]):
                pieces.append(" " * gap)
                pieces.append(word)
            lines.append("".join(pieces))
        i = j
    return lines
def fullJustify(self, words: List[str], maxWidth: int) -> List[str]:
    lines: List[str] = []
    line: List[str] = []   # words packed into the current line
    chars = 0              # letters only, no spaces

    for word in words:
        # len(line) = one mandatory space after each word already packed
        if chars + len(line) + len(word) > maxWidth:
            lines.append(self._justify(line, chars, maxWidth))
            line, chars = [], 0
        line.append(word)
        chars += len(word)

    lines.append(" ".join(line).ljust(maxWidth))  # last line: flush left
    return lines

@staticmethod
def _justify(line: List[str], chars: int, maxWidth: int) -> str:
    if len(line) == 1:                 # no gaps -> pad right
        return line[0].ljust(maxWidth)
    base, extra = divmod(maxWidth - chars, len(line) - 1)
    pieces = []
    for i, word in enumerate(line[:-1]):
        pieces.append(word)
        pieces.append(" " * (base + (1 if i < extra else 0)))
    pieces.append(line[-1])
    return "".join(pieces)
vector<string> fullJustifyRoundRobin(vector<string>& words, int maxWidth) {
    vector<string> lines;
    int n = (int)words.size();
    int i = 0;
    while (i < n) {
        int j = i + 1;
        int width = (int)words[i].size();  // counts 1 space per gap
        while (j < n && width + 1 + (int)words[j].size() <= maxWidth) {
            width += 1 + (int)words[j].size();
            ++j;
        }
        if (j == n || j - i == 1) {        // last line or single word
            string line = words[i];
            for (int w = i + 1; w < j; ++w) {
                line += ' ';
                line += words[w];
            }
            line.append(maxWidth - (int)line.size(), ' ');
            lines.push_back(line);
        } else {
            vector<int> gaps(j - i - 1, 1);
            int spare = maxWidth - width;
            for (size_t k = 0; spare > 0; --spare, ++k)
                ++gaps[k % gaps.size()];
            string line = words[i];
            for (int w = i + 1; w < j; ++w) {
                line.append(gaps[w - i - 1], ' ');
                line += words[w];
            }
            lines.push_back(line);
        }
        i = j;
    }
    return lines;
}
    vector<string> fullJustify(vector<string>& words, int maxWidth) {
        vector<string> lines;
        vector<string> line;   // words packed into the current line
        int chars = 0;         // letters only, no spaces

        for (const string& word : words) {
            // line.size() = one mandatory space after each packed word
            if (chars + (int)line.size() + (int)word.size() > maxWidth) {
                lines.push_back(justify(line, chars, maxWidth));
                line.clear();
                chars = 0;
            }
            line.push_back(word);
            chars += (int)word.size();
        }

        string last;           // final line: single spaces, pad right
        for (size_t i = 0; i < line.size(); ++i) {
            if (i > 0) last += ' ';
            last += line[i];
        }
        last.append(maxWidth - (int)last.size(), ' ');
        lines.push_back(last);
        return lines;
    }

private:
    static string justify(const vector<string>& line, int chars, int maxWidth) {
        if (line.size() == 1) {          // no gaps -> pad right
            string s = line[0];
            s.append(maxWidth - (int)s.size(), ' ');
            return s;
        }
        int gaps = (int)line.size() - 1;
        int base = (maxWidth - chars) / gaps;
        int extra = (maxWidth - chars) % gaps;
        string s;
        for (int i = 0; i < gaps; ++i) {
            s += line[i];
            s.append(base + (i < extra ? 1 : 0), ' ');
        }
        s += line.back();
        return s;
    }
pub fn full_justify_round_robin(words: Vec<String>, max_width: i32) -> Vec<String> {
    let width = max_width as usize;
    let mut lines: Vec<String> = Vec::new();
    let n = words.len();
    let mut i = 0;
    while i < n {
        let mut j = i + 1;
        let mut used = words[i].len(); // counts 1 space per gap
        while j < n && used + 1 + words[j].len() <= width {
            used += 1 + words[j].len();
            j += 1;
        }
        let chunk = &words[i..j];
        if j == n || chunk.len() == 1 {
            // last line or single word
            let mut line = chunk.join(" ");
            line.push_str(&" ".repeat(width - line.len()));
            lines.push(line);
        } else {
            let slots = chunk.len() - 1;
            let mut gaps = vec![1usize; slots];
            let mut spare = width - used;
            let mut k = 0;
            while spare > 0 {
                gaps[k % slots] += 1;
                k += 1;
                spare -= 1;
            }
            let mut line = chunk[0].clone();
            for (gap, word) in gaps.iter().zip(&chunk[1..]) {
                line.push_str(&" ".repeat(*gap));
                line.push_str(word);
            }
            lines.push(line);
        }
        i = j;
    }
    lines
}
pub fn full_justify(words: Vec<String>, max_width: i32) -> Vec<String> {
    let width = max_width as usize;
    let mut lines: Vec<String> = Vec::new();
    let mut line: Vec<&str> = Vec::new(); // words packed into this line
    let mut chars = 0usize; // letters only, no spaces

    for word in &words {
        // line.len() = one mandatory space after each packed word
        if chars + line.len() + word.len() > width {
            lines.push(Self::justify(&line, chars, width));
            line.clear();
            chars = 0;
        }
        line.push(word);
        chars += word.len();
    }

    let mut last = line.join(" "); // final line: single spaces, pad right
    last.push_str(&" ".repeat(width - last.len()));
    lines.push(last);
    lines
}

fn justify(line: &[&str], chars: usize, width: usize) -> String {
    if line.len() == 1 {
        // no gaps -> pad right
        return format!("{}{}", line[0], " ".repeat(width - line[0].len()));
    }
    let gaps = line.len() - 1;
    let base = (width - chars) / gaps;
    let extra = (width - chars) % gaps;
    let mut s = String::new();
    for (i, word) in line[..gaps].iter().enumerate() {
        s.push_str(word);
        s.push_str(&" ".repeat(base + usize::from(i < extra)));
    }
    s.push_str(line[gaps]);
    s
}
function fullJustifyRoundRobin(words: string[], maxWidth: number): string[] {
    const lines: string[] = [];
    const n = words.length;
    let i = 0;
    while (i < n) {
        let j = i + 1;
        let width = words[i].length;   // counts 1 space per gap
        while (j < n && width + 1 + words[j].length <= maxWidth) {
            width += 1 + words[j].length;
            j++;
        }
        if (j === n || j - i === 1) {  // last line or single word
            lines.push(words.slice(i, j).join(" ").padEnd(maxWidth));
        } else {
            const gaps = new Array<number>(j - i - 1).fill(1);
            for (let spare = maxWidth - width, k = 0; spare > 0; spare--, k++) {
                gaps[k % gaps.length]++;
            }
            let line = words[i];
            for (let w = i + 1; w < j; w++) {
                line += " ".repeat(gaps[w - i - 1]) + words[w];
            }
            lines.push(line);
        }
        i = j;
    }
    return lines;
}
function fullJustify(words: string[], maxWidth: number): string[] {
    const lines: string[] = [];
    let line: string[] = [];   // words packed into the current line
    let chars = 0;             // letters only, no spaces

    const justify = (packed: string[], letters: number): string => {
        if (packed.length === 1) {           // no gaps -> pad right
            return packed[0].padEnd(maxWidth);
        }
        const gaps = packed.length - 1;
        const base = Math.floor((maxWidth - letters) / gaps);
        const extra = (maxWidth - letters) % gaps;
        let s = "";
        for (let i = 0; i < gaps; i++) {
            s += packed[i];
            s += " ".repeat(base + (i < extra ? 1 : 0));
        }
        return s + packed[gaps];
    };

    for (const word of words) {
        // line.length = one mandatory space after each packed word
        if (chars + line.length + word.length > maxWidth) {
            lines.push(justify(line, chars));
            line = [];
            chars = 0;
        }
        line.push(word);
        chars += word.length;
    }

    lines.push(line.join(" ").padEnd(maxWidth));  // last line: flush left
    return lines;
}
func fullJustifyRoundRobin(words []string, maxWidth int) []string {
	var lines []string
	n := len(words)
	for i := 0; i < n; {
		j, width := i+1, len(words[i]) // width counts 1 space per gap
		for j < n && width+1+len(words[j]) <= maxWidth {
			width += 1 + len(words[j])
			j++
		}
		if j == n || j-i == 1 { // last line or single word
			line := strings.Join(words[i:j], " ")
			lines = append(lines, line+strings.Repeat(" ", maxWidth-len(line)))
		} else {
			gaps := make([]int, j-i-1)
			for g := range gaps {
				gaps[g] = 1
			}
			for spare, k := maxWidth-width, 0; spare > 0; spare-- {
				gaps[k%len(gaps)]++
				k++
			}
			var b strings.Builder
			b.WriteString(words[i])
			for w := i + 1; w < j; w++ {
				b.WriteString(strings.Repeat(" ", gaps[w-i-1]))
				b.WriteString(words[w])
			}
			lines = append(lines, b.String())
		}
		i = j
	}
	return lines
}
func fullJustify(words []string, maxWidth int) []string {
	var lines []string
	var line []string // words packed into the current line
	chars := 0        // letters only, no spaces

	for _, word := range words {
		// len(line) = one mandatory space after each packed word
		if chars+len(line)+len(word) > maxWidth {
			lines = append(lines, justifyLine(line, chars, maxWidth))
			line, chars = nil, 0
		}
		line = append(line, word)
		chars += len(word)
	}

	last := strings.Join(line, " ") // final line: single spaces, pad right
	last += strings.Repeat(" ", maxWidth-len(last))
	return append(lines, last)
}

func justifyLine(line []string, chars, maxWidth int) string {
	if len(line) == 1 { // no gaps -> pad right
		return line[0] + strings.Repeat(" ", maxWidth-len(line[0]))
	}
	gaps := len(line) - 1
	base, extra := (maxWidth-chars)/gaps, (maxWidth-chars)%gaps
	var b strings.Builder
	for i, word := range line[:gaps] {
		b.WriteString(word)
		width := base
		if i < extra {
			width++
		}
		b.WriteString(strings.Repeat(" ", width))
	}
	b.WriteString(line[gaps])
	return b.String()
}
func fullJustifyRoundRobin(_ words: [String], _ maxWidth: Int) -> [String] {
    var lines: [String] = []
    let n = words.count
    var i = 0
    while i < n {
        var j = i + 1
        var width = words[i].count  // counts 1 space per gap
        while j < n && width + 1 + words[j].count <= maxWidth {
            width += 1 + words[j].count
            j += 1
        }
        if j == n || j - i == 1 {   // last line or single word
            let line = words[i..<j].joined(separator: " ")
            lines.append(line + String(repeating: " ", count: maxWidth - line.count))
        } else {
            var gaps = [Int](repeating: 1, count: j - i - 1)
            var spare = maxWidth - width
            var k = 0
            while spare > 0 {
                gaps[k % gaps.count] += 1
                k += 1
                spare -= 1
            }
            var line = words[i]
            for w in (i + 1)..<j {
                line += String(repeating: " ", count: gaps[w - i - 1])
                line += words[w]
            }
            lines.append(line)
        }
        i = j
    }
    return lines
}
func fullJustify(_ words: [String], _ maxWidth: Int) -> [String] {
    var lines: [String] = []
    var line: [String] = []   // words packed into the current line
    var chars = 0             // letters only, no spaces

    func justify(_ line: [String], _ chars: Int) -> String {
        if line.count == 1 {  // no gaps -> pad right
            return line[0] + String(repeating: " ", count: maxWidth - line[0].count)
        }
        let gaps = line.count - 1
        let base = (maxWidth - chars) / gaps
        let extra = (maxWidth - chars) % gaps
        var s = ""
        for i in 0..<gaps {
            s += line[i]
            s += String(repeating: " ", count: base + (i < extra ? 1 : 0))
        }
        s += line[gaps]
        return s
    }

    for word in words {
        // line.count = one mandatory space after each packed word
        if chars + line.count + word.count > maxWidth {
            lines.append(justify(line, chars))
            line = []
            chars = 0
        }
        line.append(word)
        chars += word.count
    }

    let last = line.joined(separator: " ")  // final line: flush left
    lines.append(last + String(repeating: " ", count: maxWidth - last.count))
    return lines
}
Recommended Approach 1 of 2 · Greedy + round-robin space sprinklingO(n) time · O(n) space

Two Pointers

10. Is Subsequence

Easy · LC 392

Given two strings, determine whether the first is a subsequence of the second. Walk the containing string once with a pointer into the candidate, advancing the pointer only when the characters match; the candidate is a subsequence exactly when its pointer reaches the end. The trick is that greedy matching is safe, since taking the earliest usable occurrence of each character never hurts, while the follow-up with huge numbers of queries against one string calls for precomputed occurrence lists and binary search instead.

Chase each char of s to its next occurrence in t; every search resumes right after the previous match, so t is walked only once in total — but that single pass is hidden inside the find() calls.

The same greedy pass in one line: iter(t) is consumed left to right, so each membership test resumes where the previous match stopped — no manual position bookkeeping at all.

Approaches 1-2 rescan t for every query. For billions of s queries against one t, precompute where each char occurs, then binary-search for the next usable occurrence per char of s. (Rebuilt per call here; in the follow-up you build it once.)

For a single query nothing beats one explicit walk of t, advancing in s only on a match; s is a subsequence iff its pointer reaches the end. The invariant Approaches 1-2 hide, spelled out.

Chase each char of s to its next occurrence in t; every search resumes right after the previous match, so t is walked only once in total — but that single pass is hidden inside the find() calls.

The same single pass made explicit: walk t once and advance in s only on a match; s is a subsequence iff its pointer reaches the end. No library calls, and the invariant is plain to see.

Chase each char of s to its next occurrence in t; every search resumes right after the previous match, so t is walked only once in total — but that single pass is hidden inside the searches.

The same single pass made explicit: walk t once and advance in s only on a match; s is a subsequence iff its pointer reaches the end. No library calls, and the invariant is plain to see.

Chase each char of s to its next occurrence in t; every search resumes right after the previous match, so t is walked only once in total — but that single pass is hidden inside the indexOf calls.

The same single pass made explicit: walk t once and advance in s only on a match; s is a subsequence iff its pointer reaches the end. No library calls, and the invariant is plain to see.

Chase each char of s to its next occurrence in t; every search resumes right after the previous match, so t is walked only once in total — but that single pass is hidden inside the IndexByte calls.

The same single pass made explicit: walk t once and advance in s only on a match; s is a subsequence iff its pointer reaches the end. No library calls, and the invariant is plain to see.

Chase each char of s to its next occurrence in t; every search resumes right after the previous match, so t is walked only once in total — but that single pass is hidden inside the searches.

The same single pass made explicit: walk t once and advance in s only on a match; s is a subsequence iff its pointer reaches the end. No library calls, and the invariant is plain to see.

def isSubsequence_find_scan(self, s: str, t: str) -> bool:
    pos = 0
    for ch in s:
        pos = t.find(ch, pos)
        if pos == -1:
            return False
        pos += 1
    return True
def isSubsequence_iterator(self, s: str, t: str) -> bool:
    it = iter(t)
    return all(ch in it for ch in s)
def isSubsequence_binary_search(self, s: str, t: str) -> bool:
    positions: Dict[str, List[int]] = {}
    for idx, ch in enumerate(t):
        positions.setdefault(ch, []).append(idx)

    prev = -1                      # last index of t consumed so far
    for ch in s:
        idx_list = positions.get(ch)
        if not idx_list:
            return False
        nxt = bisect_right(idx_list, prev)
        if nxt == len(idx_list):   # no occurrence left after prev
            return False
        prev = idx_list[nxt]
    return True
def isSubsequence(self, s: str, t: str) -> bool:
    i = 0
    for ch in t:
        if i < len(s) and s[i] == ch:
            i += 1
    return i == len(s)
bool isSubsequenceIndexScan(string s, string t) {
    size_t pos = 0;
    for (char ch : s) {
        pos = t.find(ch, pos);
        if (pos == string::npos) return false;
        ++pos;
    }
    return true;
}
bool isSubsequence(string s, string t) {
    size_t i = 0;
    for (char ch : t) {
        if (i < s.size() && s[i] == ch) ++i;
    }
    return i == s.size();
}
pub fn is_subsequence_index_scan(s: String, t: String) -> bool {
    let t = t.as_bytes();
    let mut pos = 0usize;
    for &ch in s.as_bytes() {
        match t[pos..].iter().position(|&b| b == ch) {
            Some(off) => pos += off + 1,
            None => return false,
        }
    }
    true
}
pub fn is_subsequence(s: String, t: String) -> bool {
    let s = s.as_bytes();
    let mut i = 0;
    for &ch in t.as_bytes() {
        if i < s.len() && s[i] == ch {
            i += 1;
        }
    }
    i == s.len()
}
function isSubsequenceIndexScan(s: string, t: string): boolean {
    let pos = 0;
    for (const ch of s) {
        pos = t.indexOf(ch, pos);
        if (pos < 0) {
            return false;
        }
        pos++;
    }
    return true;
}
function isSubsequence(s: string, t: string): boolean {
    let i = 0;
    for (let j = 0; j < t.length; j++) {
        if (i < s.length && s[i] === t[j]) {
            i++;
        }
    }
    return i === s.length;
}
func isSubsequenceIndexScan(s string, t string) bool {
	pos := 0
	for i := 0; i < len(s); i++ {
		off := strings.IndexByte(t[pos:], s[i])
		if off < 0 {
			return false
		}
		pos += off + 1
	}
	return true
}
func isSubsequence(s string, t string) bool {
	i := 0
	for j := 0; j < len(t); j++ {
		if i < len(s) && s[i] == t[j] {
			i++
		}
	}
	return i == len(s)
}
func isSubsequenceIndexScan(_ s: String, _ t: String) -> Bool {
    let tChars = Array(t)
    var pos = 0
    for ch in s {
        guard let idx = tChars[pos...].firstIndex(of: ch) else {
            return false
        }
        pos = idx + 1
    }
    return true
}
func isSubsequence(_ s: String, _ t: String) -> Bool {
    let sChars = Array(s)
    var i = 0
    for ch in t {
        if i < sChars.count && sChars[i] == ch {
            i += 1
        }
    }
    return i == sChars.count
}
Recommended Approach 1 of 4 · Greedy index scan with str.find()O(n) time · O(1) space

Sliding Window

11. Substring with Concatenation of All Words

Hard · LC 30

Given a string and a list of words that all share the same length, find every starting index of a substring formed by concatenating all the words in some order. Since every valid start is congruent to some offset modulo the word length, slide a word-count window word by word from each such offset, shrinking from the left whenever some word's count exceeds its quota and recording the left edge whenever the window holds exactly all the words. The trick is working at word granularity, with a hard window reset on any word not in the list, so each word slot is counted only about twice per offset instead of the brute force's full recount at every start.

Cut the next k words at every start and compare multisets. Simple and correct, but rebuilds the whole count at each index — fine at these test sizes, times out on LeetCode's 10^4-char strings.

Every valid start is congruent to some offset mod L, so slide a word-count window word-by-word from each of the L offsets, shrinking from the left whenever some word count exceeds its quota — each word slot is now counted twice per offset instead of once per start.

Cut the next k words at every start and burn them off a copy of the quota map. Simple and correct, but rebuilds the whole count at each index — fine here, times out on LeetCode's 10^4 chars.

Every valid start is congruent to some offset mod L, so slide a word-count window word-by-word from each of the L offsets, shrinking from the left when a word count exceeds its quota — each word slot is counted twice per offset instead of once per start.

Cut the next k words at every start and burn them off a copy of the quota map. Simple and correct, but rebuilds the whole count at each index — fine here, times out on LeetCode's 10^4 chars.

Every valid start is congruent to some offset mod L, so slide a word-count window word-by-word from each of the L offsets, shrinking from the left when a word count exceeds its quota — each word slot is counted twice per offset instead of once per start.

Cut the next k words at every start and burn them off a copy of the quota map. Simple and correct, but rebuilds the whole count at each index — fine here, times out on LeetCode's 10^4 chars.

Every valid start is congruent to some offset mod L, so slide a word-count window word-by-word from each of the L offsets, shrinking from the left when a word count exceeds its quota — each word slot is counted twice per offset instead of once per start.

Cut the next k words at every start and burn them off a copy of the quota map. Simple and correct, but rebuilds the whole count at each index — fine here, times out on LeetCode's 10^4 chars.

Every valid start is congruent to some offset mod L, so slide a word-count window word-by-word from each of the L offsets, shrinking from the left when a word count exceeds its quota — each word slot is counted twice per offset instead of once per start.

Cut the next k words at every start and burn them off a copy of the quota map. Simple and correct, but rebuilds the whole count at each index — fine here, times out on LeetCode's 10^4 chars.

Every valid start is congruent to some offset mod L, so slide a word-count window word-by-word from each of the L offsets, shrinking from the left when a word count exceeds its quota — each word slot is counted twice per offset instead of once per start.

def findSubstring_counter(self, s: str, words: List[str]) -> List[int]:
    k, L = len(words), len(words[0])
    total = k * L
    need = Counter(words)
    result: List[int] = []
    for start in range(len(s) - total + 1):
        seen = Counter(s[i:i + L] for i in range(start, start + total, L))
        if seen == need:
            result.append(start)
    return result
def findSubstring(self, s: str, words: List[str]) -> List[int]:
    n, k, L = len(s), len(words), len(words[0])
    total = k * L
    if n < total:
        return []

    need = Counter(words)
    result: List[int] = []

    for offset in range(L):
        window: Counter = Counter()
        matched = 0        # words currently inside the window
        left = offset
        for right in range(offset, n - L + 1, L):
            word = s[right:right + L]
            if word not in need:
                window.clear()          # hard reset past the junk word
                matched = 0
                left = right + L
                continue
            window[word] += 1
            matched += 1
            while window[word] > need[word]:  # too many copies -> shrink
                window[s[left:left + L]] -= 1
                left += L
                matched -= 1
            if matched == k:
                result.append(left)
                window[s[left:left + L]] -= 1  # slide one word forward
                left += L
                matched -= 1
    return result
vector<int> findSubstringBruteForce(string s, vector<string>& words) {
    int n = (int)s.size();
    int k = (int)words.size();
    int len = (int)words[0].size();
    int total = k * len;

    unordered_map<string, int> need;
    for (const string& w : words) ++need[w];

    vector<int> result;
    for (int start = 0; start + total <= n; ++start) {
        unordered_map<string, int> remaining = need;
        bool ok = true;
        for (int pos = start; pos < start + total; pos += len) {
            auto it = remaining.find(s.substr(pos, len));
            if (it == remaining.end() || it->second == 0) {
                ok = false;
                break;
            }
            --it->second;
        }
        if (ok) result.push_back(start);
    }
    return result;
}
vector<int> findSubstring(string s, vector<string>& words) {
    int n = (int)s.size();
    int k = (int)words.size();
    int len = (int)words[0].size();
    vector<int> result;
    if (n < k * len) return result;

    unordered_map<string, int> need;
    for (const string& w : words) ++need[w];

    for (int offset = 0; offset < len; ++offset) {
        unordered_map<string, int> window;
        int matched = 0;       // words currently inside the window
        int left = offset;
        for (int right = offset; right + len <= n; right += len) {
            string word = s.substr(right, len);
            auto it = need.find(word);
            if (it == need.end()) {
                window.clear();          // hard reset past the junk word
                matched = 0;
                left = right + len;
                continue;
            }
            ++window[word];
            ++matched;
            while (window[word] > it->second) {  // too many copies
                --window[s.substr(left, len)];
                left += len;
                --matched;
            }
            if (matched == k) {
                result.push_back(left);
                --window[s.substr(left, len)];   // slide one word forward
                left += len;
                --matched;
            }
        }
    }
    return result;
}
pub fn find_substring_brute_force(s: String, words: Vec<String>) -> Vec<i32> {
    let n = s.len();
    let k = words.len();
    let len = words[0].len();
    let total = k * len;

    let mut need: HashMap<&str, i32> = HashMap::new();
    for w in &words {
        *need.entry(w.as_str()).or_insert(0) += 1;
    }

    let mut result: Vec<i32> = Vec::new();
    let mut start = 0;
    while start + total <= n {
        let mut remaining = need.clone();
        let mut ok = true;
        let mut pos = start;
        for _ in 0..k {
            let word = &s[pos..pos + len];
            match remaining.get_mut(word) {
                Some(count) if *count > 0 => *count -= 1,
                _ => {
                    ok = false;
                    break;
                }
            }
            pos += len;
        }
        if ok {
            result.push(start as i32);
        }
        start += 1;
    }
    result
}
pub fn find_substring(s: String, words: Vec<String>) -> Vec<i32> {
    let n = s.len();
    let k = words.len();
    let len = words[0].len();
    let mut result: Vec<i32> = Vec::new();
    if n < k * len {
        return result;
    }

    let mut need: HashMap<&str, i32> = HashMap::new();
    for w in &words {
        *need.entry(w.as_str()).or_insert(0) += 1;
    }

    for offset in 0..len {
        let mut window: HashMap<&str, i32> = HashMap::new();
        let mut matched = 0; // words currently inside the window
        let mut left = offset;
        let mut right = offset;
        while right + len <= n {
            let word = &s[right..right + len];
            match need.get(word) {
                None => {
                    window.clear(); // hard reset past the junk word
                    matched = 0;
                    left = right + len;
                }
                Some(&quota) => {
                    *window.entry(word).or_insert(0) += 1;
                    matched += 1;
                    while window[word] > quota {
                        // too many copies -> shrink
                        *window.get_mut(&s[left..left + len]).unwrap() -= 1;
                        left += len;
                        matched -= 1;
                    }
                    if matched == k {
                        result.push(left as i32);
                        // slide one word forward
                        *window.get_mut(&s[left..left + len]).unwrap() -= 1;
                        left += len;
                        matched -= 1;
                    }
                }
            }
            right += len;
        }
    }
    result
}
function findSubstringBruteForce(s: string, words: string[]): number[] {
    const k = words.length;
    const len = words[0].length;
    const total = k * len;

    const need = new Map<string, number>();
    for (const w of words) {
        need.set(w, (need.get(w) ?? 0) + 1);
    }

    const result: number[] = [];
    for (let start = 0; start + total <= s.length; start++) {
        const remaining = new Map(need);
        let ok = true;
        for (let pos = start; pos < start + total; pos += len) {
            const word = s.slice(pos, pos + len);
            const count = remaining.get(word) ?? 0;
            if (count === 0) {
                ok = false;
                break;
            }
            remaining.set(word, count - 1);
        }
        if (ok) {
            result.push(start);
        }
    }
    return result;
}
function findSubstring(s: string, words: string[]): number[] {
    const n = s.length;
    const k = words.length;
    const len = words[0].length;
    const result: number[] = [];
    if (n < k * len) {
        return result;
    }

    const need = new Map<string, number>();
    for (const w of words) {
        need.set(w, (need.get(w) ?? 0) + 1);
    }

    for (let offset = 0; offset < len; offset++) {
        const window = new Map<string, number>();
        let matched = 0;   // words currently inside the window
        let left = offset;
        for (let right = offset; right + len <= n; right += len) {
            const word = s.slice(right, right + len);
            const quota = need.get(word);
            if (quota === undefined) {
                window.clear();        // hard reset past the junk word
                matched = 0;
                left = right + len;
                continue;
            }
            window.set(word, (window.get(word) ?? 0) + 1);
            matched++;
            while ((window.get(word) ?? 0) > quota) {  // too many copies
                const drop = s.slice(left, left + len);
                window.set(drop, (window.get(drop) ?? 0) - 1);
                left += len;
                matched--;
            }
            if (matched === k) {
                result.push(left);
                const drop = s.slice(left, left + len);  // slide forward
                window.set(drop, (window.get(drop) ?? 0) - 1);
                left += len;
                matched--;
            }
        }
    }
    return result;
}
func findSubstringBruteForce(s string, words []string) []int {
	k, length := len(words), len(words[0])
	total := k * length

	need := make(map[string]int, k)
	for _, w := range words {
		need[w]++
	}

	result := []int{}
	for start := 0; start+total <= len(s); start++ {
		remaining := make(map[string]int, len(need))
		for w, c := range need {
			remaining[w] = c
		}
		ok := true
		for pos := start; pos < start+total; pos += length {
			word := s[pos : pos+length]
			if remaining[word] == 0 {
				ok = false
				break
			}
			remaining[word]--
		}
		if ok {
			result = append(result, start)
		}
	}
	return result
}
func findSubstring(s string, words []string) []int {
	n, k, length := len(s), len(words), len(words[0])
	result := []int{}
	if n < k*length {
		return result
	}

	need := make(map[string]int, k)
	for _, w := range words {
		need[w]++
	}

	for offset := 0; offset < length; offset++ {
		window := make(map[string]int)
		matched := 0 // words currently inside the window
		left := offset
		for right := offset; right+length <= n; right += length {
			word := s[right : right+length]
			quota, known := need[word]
			if !known {
				window = make(map[string]int) // hard reset past the junk word
				matched = 0
				left = right + length
				continue
			}
			window[word]++
			matched++
			for window[word] > quota { // too many copies -> shrink
				window[s[left:left+length]]--
				left += length
				matched--
			}
			if matched == k {
				result = append(result, left)
				window[s[left:left+length]]-- // slide one word forward
				left += length
				matched--
			}
		}
	}
	return result
}
func findSubstringBruteForce(_ s: String, _ words: [String]) -> [Int] {
    let chars = Array(s)
    let n = chars.count
    let k = words.count
    let len = words[0].count
    let total = k * len

    var need: [String: Int] = [:]
    for w in words { need[w, default: 0] += 1 }

    var result: [Int] = []
    var start = 0
    while start + total <= n {
        var remaining = need
        var ok = true
        var pos = start
        for _ in 0..<k {
            let word = String(chars[pos..<pos + len])
            if let count = remaining[word], count > 0 {
                remaining[word] = count - 1
            } else {
                ok = false
                break
            }
            pos += len
        }
        if ok { result.append(start) }
        start += 1
    }
    return result
}
func findSubstring(_ s: String, _ words: [String]) -> [Int] {
    let chars = Array(s)
    let n = chars.count
    let k = words.count
    let len = words[0].count
    var result: [Int] = []
    if n < k * len { return result }

    var need: [String: Int] = [:]
    for w in words { need[w, default: 0] += 1 }

    for offset in 0..<len {
        var window: [String: Int] = [:]
        var matched = 0   // words currently inside the window
        var left = offset
        var right = offset
        while right + len <= n {
            let word = String(chars[right..<right + len])
            guard let quota = need[word] else {
                window.removeAll()   // hard reset past the junk word
                matched = 0
                left = right + len
                right += len
                continue
            }
            window[word, default: 0] += 1
            matched += 1
            while window[word]! > quota {  // too many copies -> shrink
                window[String(chars[left..<left + len])]! -= 1
                left += len
                matched -= 1
            }
            if matched == k {
                result.append(left)
                // slide one word forward
                window[String(chars[left..<left + len])]! -= 1
                left += len
                matched -= 1
            }
            right += len
        }
    }
    return result
}
Recommended Approach 1 of 2 · Brute force, one Counter per startO(n * k * L) time · O(k * L) space

Matrix

12. Game of Life

Medium · LC 289

Given a grid of live and dead cells, apply one simultaneous step of Conway's Game of Life rules in place. Encode two generations in each cell with two bits, keeping the current state in the low bit and writing the next state into the second bit so neighbor counting reads only the low bit; a final pass shifts every cell right to reveal the new board. The trick is that simultaneous updates require every neighbor read to see the old state even after a cell's fate is decided, and the two-bit encoding preserves that without an O(m * n) snapshot copy.

Read every neighbor from an untouched copy of the board — the obvious baseline, at the cost of duplicating the whole grid.

Count neighbor "touches" around live cells only; a cell is alive next round iff it is touched 3 times, or 2 while already alive. Scales to huge, mostly-dead boards where even the snapshot's dense copy is unaffordable; here it writes back in bounds.

Bit 0 keeps the current state while bit 1 stores the next one, so neighbor counts read `& 1` and no copy is needed at all; a final pass shifts every cell right.

Read every neighbor from an untouched copy of the board — the obvious baseline, at the cost of duplicating the whole grid.

Bit 0 keeps the current state while bit 1 stores the next one, so neighbor counts read `& 1` and no copy is needed at all; a final pass shifts every cell right.

Read every neighbor from an untouched copy of the board — the obvious baseline, at the cost of duplicating the whole grid.

Bit 0 keeps the current state while bit 1 stores the next one, so neighbor counts read `& 1` and no copy is needed at all; a final pass shifts every cell right.

Read every neighbor from an untouched copy of the board — the obvious baseline, at the cost of duplicating the whole grid.

Bit 0 keeps the current state while bit 1 stores the next one, so neighbor counts read `& 1` and no copy is needed at all; a final pass shifts every cell right.

Read every neighbor from an untouched copy of the board — the obvious baseline, at the cost of duplicating the whole grid.

Bit 0 keeps the current state while bit 1 stores the next one, so neighbor counts read `& 1` and no copy is needed at all; a final pass shifts every cell right.

Read every neighbor from an untouched copy of the board — the obvious baseline, at the cost of duplicating the whole grid.

Bit 0 keeps the current state while bit 1 stores the next one, so neighbor counts read `& 1` and no copy is needed at all; a final pass shifts every cell right.

def gameOfLife_copy(self, board: List[List[int]]) -> None:
    m, n = len(board), len(board[0])
    snap = [row[:] for row in board]
    for r in range(m):
        for c in range(n):
            live = sum(
                snap[r + dr][c + dc]
                for dr, dc in self._NEIGHBORS
                if 0 <= r + dr < m and 0 <= c + dc < n
            )
            board[r][c] = int(live == 3 or (live == 2 and snap[r][c]))
def gameOfLife_sparse(self, board: List[List[int]]) -> None:
    m, n = len(board), len(board[0])
    live = {(r, c) for r in range(m) for c in range(n) if board[r][c]}
    touches = Counter(
        (r + dr, c + dc) for r, c in live for dr, dc in self._NEIGHBORS
    )
    next_live = {
        cell for cell, cnt in touches.items()
        if cnt == 3 or (cnt == 2 and cell in live)
    }
    for r in range(m):
        for c in range(n):
            board[r][c] = int((r, c) in next_live)
def gameOfLife(self, board: List[List[int]]) -> None:
    m, n = len(board), len(board[0])
    for r in range(m):
        for c in range(n):
            live = sum(
                board[r + dr][c + dc] & 1
                for dr, dc in self._NEIGHBORS
                if 0 <= r + dr < m and 0 <= c + dc < n
            )
            # survive with 2-3 neighbors; birth with exactly 3
            if live == 3 or (live == 2 and board[r][c] & 1):
                board[r][c] |= 2
    for r in range(m):
        for c in range(n):
            board[r][c] >>= 1
void gameOfLifeSnapshot(vector<vector<int>>& board) {
    int m = (int)board.size();
    int n = (int)board[0].size();
    vector<vector<int>> snap = board;
    for (int r = 0; r < m; ++r) {
        for (int c = 0; c < n; ++c) {
            int live = 0;
            for (int dr = -1; dr <= 1; ++dr) {
                for (int dc = -1; dc <= 1; ++dc) {
                    if (dr == 0 && dc == 0) continue;
                    int rr = r + dr, cc = c + dc;
                    if (rr >= 0 && rr < m && cc >= 0 && cc < n)
                        live += snap[rr][cc];
                }
            }
            // survive with 2-3 neighbors; birth with exactly 3
            board[r][c] = (live == 3 || (live == 2 && snap[r][c] == 1)) ? 1 : 0;
        }
    }
}
void gameOfLife(vector<vector<int>>& board) {
    int m = (int)board.size();
    int n = (int)board[0].size();
    for (int r = 0; r < m; ++r) {
        for (int c = 0; c < n; ++c) {
            int live = 0;
            for (int dr = -1; dr <= 1; ++dr) {
                for (int dc = -1; dc <= 1; ++dc) {
                    if (dr == 0 && dc == 0) continue;
                    int rr = r + dr, cc = c + dc;
                    if (rr >= 0 && rr < m && cc >= 0 && cc < n)
                        live += board[rr][cc] & 1;
                }
            }
            // survive with 2-3 neighbors; birth with exactly 3
            if (live == 3 || (live == 2 && (board[r][c] & 1)))
                board[r][c] |= 2;
        }
    }
    for (int r = 0; r < m; ++r)
        for (int c = 0; c < n; ++c)
            board[r][c] >>= 1;
}
pub fn game_of_life_snapshot(board: &mut Vec<Vec<i32>>) {
    let m = board.len() as i32;
    let n = board[0].len() as i32;
    let snap = board.clone();
    for r in 0..m {
        for c in 0..n {
            let mut live = 0;
            for dr in -1..=1 {
                for dc in -1..=1 {
                    if dr == 0 && dc == 0 {
                        continue;
                    }
                    let (rr, cc) = (r + dr, c + dc);
                    if rr >= 0 && rr < m && cc >= 0 && cc < n {
                        live += snap[rr as usize][cc as usize];
                    }
                }
            }
            // survive with 2-3 neighbors; birth with exactly 3
            let was_live = snap[r as usize][c as usize] == 1;
            board[r as usize][c as usize] =
                i32::from(live == 3 || (live == 2 && was_live));
        }
    }
}
pub fn game_of_life(board: &mut Vec<Vec<i32>>) {
    let m = board.len() as i32;
    let n = board[0].len() as i32;
    for r in 0..m {
        for c in 0..n {
            let mut live = 0;
            for dr in -1..=1 {
                for dc in -1..=1 {
                    if dr == 0 && dc == 0 {
                        continue;
                    }
                    let (rr, cc) = (r + dr, c + dc);
                    if rr >= 0 && rr < m && cc >= 0 && cc < n {
                        live += board[rr as usize][cc as usize] & 1;
                    }
                }
            }
            // survive with 2-3 neighbors; birth with exactly 3
            let cell = &mut board[r as usize][c as usize];
            if live == 3 || (live == 2 && *cell & 1 == 1) {
                *cell |= 2;
            }
        }
    }
    for row in board.iter_mut() {
        for cell in row.iter_mut() {
            *cell >>= 1;
        }
    }
}
function gameOfLifeSnapshot(board: number[][]): void {
    const m = board.length;
    const n = board[0].length;
    const snap = board.map((row) => [...row]);
    for (let r = 0; r < m; r++) {
        for (let c = 0; c < n; c++) {
            let live = 0;
            for (let dr = -1; dr <= 1; dr++) {
                for (let dc = -1; dc <= 1; dc++) {
                    if (dr === 0 && dc === 0) {
                        continue;
                    }
                    const rr = r + dr;
                    const cc = c + dc;
                    if (rr >= 0 && rr < m && cc >= 0 && cc < n) {
                        live += snap[rr][cc];
                    }
                }
            }
            // survive with 2-3 neighbors; birth with exactly 3
            board[r][c] = live === 3 || (live === 2 && snap[r][c] === 1) ? 1 : 0;
        }
    }
}
/**
 Do not return anything, modify board in-place instead.
 */
function gameOfLife(board: number[][]): void {
    const m = board.length;
    const n = board[0].length;
    for (let r = 0; r < m; r++) {
        for (let c = 0; c < n; c++) {
            let live = 0;
            for (let dr = -1; dr <= 1; dr++) {
                for (let dc = -1; dc <= 1; dc++) {
                    if (dr === 0 && dc === 0) {
                        continue;
                    }
                    const rr = r + dr;
                    const cc = c + dc;
                    if (rr >= 0 && rr < m && cc >= 0 && cc < n) {
                        live += board[rr][cc] & 1;
                    }
                }
            }
            // survive with 2-3 neighbors; birth with exactly 3
            if (live === 3 || (live === 2 && (board[r][c] & 1) === 1)) {
                board[r][c] |= 2;
            }
        }
    }
    for (let r = 0; r < m; r++) {
        for (let c = 0; c < n; c++) {
            board[r][c] >>= 1;
        }
    }
}
func gameOfLifeSnapshot(board [][]int) {
	m, n := len(board), len(board[0])
	snap := make([][]int, m)
	for r := range board {
		snap[r] = append([]int{}, board[r]...)
	}
	for r := 0; r < m; r++ {
		for c := 0; c < n; c++ {
			live := 0
			for dr := -1; dr <= 1; dr++ {
				for dc := -1; dc <= 1; dc++ {
					if dr == 0 && dc == 0 {
						continue
					}
					rr, cc := r+dr, c+dc
					if rr >= 0 && rr < m && cc >= 0 && cc < n {
						live += snap[rr][cc]
					}
				}
			}
			// survive with 2-3 neighbors; birth with exactly 3
			if live == 3 || (live == 2 && snap[r][c] == 1) {
				board[r][c] = 1
			} else {
				board[r][c] = 0
			}
		}
	}
}
func gameOfLife(board [][]int) {
	m, n := len(board), len(board[0])
	for r := 0; r < m; r++ {
		for c := 0; c < n; c++ {
			live := 0
			for dr := -1; dr <= 1; dr++ {
				for dc := -1; dc <= 1; dc++ {
					if dr == 0 && dc == 0 {
						continue
					}
					rr, cc := r+dr, c+dc
					if rr >= 0 && rr < m && cc >= 0 && cc < n {
						live += board[rr][cc] & 1
					}
				}
			}
			// survive with 2-3 neighbors; birth with exactly 3
			if live == 3 || (live == 2 && board[r][c]&1 == 1) {
				board[r][c] |= 2
			}
		}
	}
	for r := 0; r < m; r++ {
		for c := 0; c < n; c++ {
			board[r][c] >>= 1
		}
	}
}
func gameOfLifeSnapshot(_ board: inout [[Int]]) {
    let m = board.count
    let n = board[0].count
    let snap = board
    for r in 0..<m {
        for c in 0..<n {
            var live = 0
            for dr in -1...1 {
                for dc in -1...1 {
                    if dr == 0 && dc == 0 { continue }
                    let rr = r + dr, cc = c + dc
                    if rr >= 0 && rr < m && cc >= 0 && cc < n {
                        live += snap[rr][cc]
                    }
                }
            }
            // survive with 2-3 neighbors; birth with exactly 3
            board[r][c] = (live == 3 || (live == 2 && snap[r][c] == 1)) ? 1 : 0
        }
    }
}
func gameOfLife(_ board: inout [[Int]]) {
    let m = board.count
    let n = board[0].count
    for r in 0..<m {
        for c in 0..<n {
            var live = 0
            for dr in -1...1 {
                for dc in -1...1 {
                    if dr == 0 && dc == 0 { continue }
                    let rr = r + dr, cc = c + dc
                    if rr >= 0 && rr < m && cc >= 0 && cc < n {
                        live += board[rr][cc] & 1
                    }
                }
            }
            // survive with 2-3 neighbors; birth with exactly 3
            if live == 3 || (live == 2 && board[r][c] & 1 == 1) {
                board[r][c] |= 2
            }
        }
    }
    for r in 0..<m {
        for c in 0..<n {
            board[r][c] >>= 1
        }
    }
}
Recommended Approach 1 of 3 · Snapshot copyO(m * n) time · O(m * n) space

Hashmap

13. Ransom Note

Easy · LC 383

Given a ransom note and a magazine, decide whether the note can be built from the magazine's letters, using each letter at most once. Count the magazine into a fixed array of 26 slots indexed by letter, then walk the note decrementing each needed count and failing the moment a count is already zero. The trick is that the lowercase-only constraint lets a plain array replace a hash map, so every lookup is direct index arithmetic and the extra space is a true constant.

Strings are immutable, so "removing" a char rebuilds the whole string. Passes on LeetCode (tiny tests) but too slow for an interview.

Count both strings, then verify the magazine has >= each needed char.

Count the magazine once, then subtract the note's chars from it.

Same idea as Approach 3, but an array beats a hashmap because the constraint guarantees lowercase a-z, so we index directly.

Cool but strictly worse than 2/3. Sort both, walk them in lockstep.

For each needed char, find one copy in the magazine and erase it, shifting the whole tail left. Quadratic, so fine on suites like this one but too slow for LeetCode's 10^5-char strings.

Sorting groups equal letters, so one lockstep walk matches every needed char — no per-char rescan of the magazine like Approach 1.

Count the magazine's letters once, then spend counts while scanning the note; a deficit means the note cannot be built. Linear time and no sorting — the version to write in an interview.

For each needed char, find one copy in the magazine and remove it, shifting the whole tail left. Quadratic, so fine on suites like this one but too slow for LeetCode's 10^5-char strings.

Sorting groups equal letters, so one lockstep walk matches every needed char — no per-char rescan of the magazine like Approach 1.

Count the magazine's letters once, then spend counts while scanning the note; a deficit means the note cannot be built. Linear time and no sorting — the version to write in an interview.

For each needed char, find one copy in the magazine and rebuild the string without it. Quadratic, so fine on suites like this one but too slow for LeetCode's 10^5-char strings.

Sorting groups equal letters, so one lockstep walk matches every needed char — no per-char rescan of the magazine like Approach 1.

Count the magazine's letters once, then spend counts while scanning the note; a deficit means the note cannot be built. Linear time and no sorting — the version to write in an interview.

For each needed char, find one copy in the magazine and rebuild the string without it. Quadratic, so fine on suites like this one but too slow for LeetCode's 10^5-char strings.

Sorting groups equal letters, so one lockstep walk matches every needed char — no per-char rescan of the magazine like Approach 1.

Count the magazine's letters once, then spend counts while scanning the note; a deficit means the note cannot be built. Linear time and no sorting — the version to write in an interview.

For each needed char, find one copy in the magazine and remove it, shifting the whole tail left. Quadratic, so fine on suites like this one but too slow for LeetCode's 10^5-char strings.

Sorting groups equal letters, so one lockstep walk matches every needed char — no per-char rescan of the magazine like Approach 1.

Count the magazine's letters once, then spend counts while scanning the note; a deficit means the note cannot be built. Linear time and no sorting — the version to write in an interview.

def canConstruct_simulation(self, ransomNote: str, magazine: str) -> bool:
    for char in ransomNote:
        if char in magazine:
            # rebuild magazine without one copy of `char` -> O(m)
            magazine = magazine.replace(char, "", 1)
        else:
            return False
    return True
def canConstruct_two_maps(self, ransomNote: str, magazine: str) -> bool:
    if len(ransomNote) > len(magazine):  # early exit optimization
        return False

    note_counts = {}
    for char in ransomNote:
        note_counts[char] = note_counts.get(char, 0) + 1

    mag_counts = {}
    for char in magazine:
        mag_counts[char] = mag_counts.get(char, 0) + 1

    for char, count in note_counts.items():
        if mag_counts.get(char, 0) < count:
            return False
    return True
def canConstruct_one_map(self, ransomNote: str, magazine: str) -> bool:
    if len(ransomNote) > len(magazine):
        return False

    mag_counts = {}
    for char in magazine:
        mag_counts[char] = mag_counts.get(char, 0) + 1

    for char in ransomNote:
        if mag_counts.get(char, 0) == 0:
            return False
        mag_counts[char] -= 1
    return True
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
    letter_count = [0] * 26
    for char in magazine:
        letter_count[ord(char) - ord('a')] += 1

    for char in ransomNote:
        idx = ord(char) - ord('a')
        if letter_count[idx] > 0:
            letter_count[idx] -= 1
        else:
            return False
    return True
def canConstruct_sorting(self, ransomNote: str, magazine: str) -> bool:
    if len(ransomNote) > len(magazine):
        return False

    note = sorted(ransomNote)
    mag = sorted(magazine)
    i = j = 0
    while i < len(note) and j < len(mag):
        if note[i] == mag[j]:      # matched a needed char
            i += 1
            j += 1
        elif note[i] > mag[j]:     # this magazine char is never needed
            j += 1
        else:                      # needed char can't appear later -> fail
            return False
    return i == len(note)          # consumed every char we needed?

# ------------------------------------------------------------------
# Pythonic one-liners (great for showing off, fine in practice)
# ------------------------------------------------------------------
def canConstruct_counter_le(self, ransomNote: str, magazine: str) -> bool:
    # Counter <= Counter is a multiset-subset test.
    return Counter(ransomNote) <= Counter(magazine)

def canConstruct_counter_sub(self, ransomNote: str, magazine: str) -> bool:
    # Counter subtraction drops zero/negative counts; leftover => missing chars.
    return not (Counter(ransomNote) - Counter(magazine))
bool canConstructSimulation(std::string ransomNote, std::string magazine) {
    for (char c : ransomNote) {
        const size_t pos = magazine.find(c);
        if (pos == std::string::npos) {
            return false;
        }
        magazine.erase(pos, 1);  // O(m) shift per erased char
    }
    return true;
}
bool canConstructSorting(std::string ransomNote, std::string magazine) {
    if (ransomNote.size() > magazine.size()) {
        return false;
    }
    std::sort(ransomNote.begin(), ransomNote.end());
    std::sort(magazine.begin(), magazine.end());
    size_t i = 0;
    for (size_t j = 0; i < ransomNote.size() && j < magazine.size(); ++j) {
        if (ransomNote[i] == magazine[j]) {
            ++i;  // matched a needed char; also consumes magazine[j]
        } else if (ransomNote[i] < magazine[j]) {
            return false;  // needed char cannot appear later
        }
    }
    return i == ransomNote.size();
}
bool canConstruct(std::string ransomNote, std::string magazine) {
    std::array<int, 26> counts{};
    for (char c : magazine) {
        ++counts[c - 'a'];
    }
    for (char c : ransomNote) {
        if (--counts[c - 'a'] < 0) {
            return false;
        }
    }
    return true;
}
pub fn can_construct_simulation(ransom_note: String, magazine: String) -> bool {
    let mut mag = magazine.into_bytes();
    for b in ransom_note.bytes() {
        match mag.iter().position(|&m| m == b) {
            Some(pos) => {
                mag.remove(pos); // O(m) shift per removed char
            }
            None => return false,
        }
    }
    true
}
pub fn can_construct_sorting(ransom_note: String, magazine: String) -> bool {
    if ransom_note.len() > magazine.len() {
        return false;
    }
    let mut note = ransom_note.into_bytes();
    let mut mag = magazine.into_bytes();
    note.sort_unstable();
    mag.sort_unstable();
    let (mut i, mut j) = (0, 0);
    while i < note.len() && j < mag.len() {
        if note[i] == mag[j] {
            i += 1; // matched a needed char
            j += 1;
        } else if note[i] > mag[j] {
            j += 1; // this magazine char is never needed
        } else {
            return false; // needed char cannot appear later
        }
    }
    i == note.len()
}
pub fn can_construct(ransom_note: String, magazine: String) -> bool {
    let mut counts = [0i32; 26];
    for b in magazine.bytes() {
        counts[(b - b'a') as usize] += 1;
    }
    for b in ransom_note.bytes() {
        let idx = (b - b'a') as usize;
        counts[idx] -= 1;
        if counts[idx] < 0 {
            return false;
        }
    }
    true
}
function canConstructSimulation(ransomNote: string, magazine: string): boolean {
    let mag = magazine;
    for (const c of ransomNote) {
        const pos = mag.indexOf(c);
        if (pos < 0) {
            return false;
        }
        mag = mag.slice(0, pos) + mag.slice(pos + 1); // O(m) rebuild per char
    }
    return true;
}
function canConstructSorting(ransomNote: string, magazine: string): boolean {
    if (ransomNote.length > magazine.length) {
        return false;
    }
    const note = [...ransomNote].sort();
    const mag = [...magazine].sort();
    let i = 0;
    for (let j = 0; i < note.length && j < mag.length; j++) {
        if (note[i] === mag[j]) {
            i++; // matched a needed char; also consumes mag[j]
        } else if (note[i] < mag[j]) {
            return false; // needed char cannot appear later
        }
    }
    return i === note.length;
}
function canConstruct(ransomNote: string, magazine: string): boolean {
    const counts = new Array<number>(26).fill(0);
    const a = "a".charCodeAt(0);
    for (let i = 0; i < magazine.length; i++) {
        counts[magazine.charCodeAt(i) - a]++;
    }
    for (let i = 0; i < ransomNote.length; i++) {
        const idx = ransomNote.charCodeAt(i) - a;
        if (counts[idx] === 0) {
            return false;
        }
        counts[idx]--;
    }
    return true;
}
func canConstructSimulation(ransomNote string, magazine string) bool {
	for i := 0; i < len(ransomNote); i++ {
		pos := strings.IndexByte(magazine, ransomNote[i])
		if pos < 0 {
			return false
		}
		magazine = magazine[:pos] + magazine[pos+1:] // O(m) rebuild per char
	}
	return true
}
func canConstructSorting(ransomNote string, magazine string) bool {
	if len(ransomNote) > len(magazine) {
		return false
	}
	note := []byte(ransomNote)
	mag := []byte(magazine)
	sort.Slice(note, func(a, b int) bool { return note[a] < note[b] })
	sort.Slice(mag, func(a, b int) bool { return mag[a] < mag[b] })
	i := 0
	for j := 0; i < len(note) && j < len(mag); j++ {
		if note[i] == mag[j] {
			i++ // matched a needed char; also consumes mag[j]
		} else if note[i] < mag[j] {
			return false // needed char cannot appear later
		}
	}
	return i == len(note)
}
func canConstruct(ransomNote string, magazine string) bool {
	var counts [26]int
	for i := 0; i < len(magazine); i++ {
		counts[magazine[i]-'a']++
	}
	for i := 0; i < len(ransomNote); i++ {
		idx := ransomNote[i] - 'a'
		if counts[idx] == 0 {
			return false
		}
		counts[idx]--
	}
	return true
}
func canConstructSimulation(_ ransomNote: String, _ magazine: String) -> Bool {
    var mag = Array(magazine)
    for c in ransomNote {
        guard let pos = mag.firstIndex(of: c) else {
            return false
        }
        mag.remove(at: pos)  // O(m) shift per removed char
    }
    return true
}
func canConstructSorting(_ ransomNote: String, _ magazine: String) -> Bool {
    if ransomNote.count > magazine.count {
        return false
    }
    let note = ransomNote.sorted()
    let mag = magazine.sorted()
    var i = 0
    var j = 0
    while i < note.count && j < mag.count {
        if note[i] == mag[j] {
            i += 1  // matched a needed char
            j += 1
        } else if note[i] > mag[j] {
            j += 1  // this magazine char is never needed
        } else {
            return false  // needed char cannot appear later
        }
    }
    return i == note.count
}
func canConstruct(_ ransomNote: String, _ magazine: String) -> Bool {
    var counts = [Int](repeating: 0, count: 26)
    let a = Int(UInt8(ascii: "a"))
    for b in magazine.utf8 {
        counts[Int(b) - a] += 1
    }
    for b in ransomNote.utf8 {
        let idx = Int(b) - a
        counts[idx] -= 1
        if counts[idx] < 0 {
            return false
        }
    }
    return true
}
Recommended Approach 1 of 5 · Simulation (brute force)O(m * n) time · O(m) space

14. Isomorphic Strings

Easy · LC 205

Given two strings, decide whether the characters of one can be replaced to obtain the other, with every occurrence of a character mapped consistently. Walk both strings in lockstep with two hash maps, one from the first string to the second and one back, and fail as soon as either direction contradicts an earlier pairing. The pitfall is forgetting the reverse map: a single forward map lets two different characters collapse onto the same target, which silently breaks the required one-to-one correspondence.

Two strings are isomorphic iff, position by position, their chars first appeared at the same index. str.index rescans the string on every call: quadratic — fine at n <= 2000, times out on LeetCode's 5 * 10^4.

Drops the quadratic rescans: the pairing is a bijection exactly when the number of distinct (s_char, t_char) pairs equals the number of distinct chars on each side. Still three full passes and no early exit on the first conflict.

The first-occurrence idea from Approach 1, memoized: one map per string of char -> first index. Single pass, and it stops at the first conflicting position instead of scanning everything.

Map s->t and t->s in one pass; a conflict in either direction means the required one-to-one character correspondence is broken. Same cost as Approach 3, but it states the bijection invariant directly — the version to reach for in an interview.

Two strings are isomorphic iff, position by position, their chars first appeared at the same index. find rescans from the front on every call: quadratic — fine at n <= 2000, times out on LeetCode's 5 * 10^4.

The first-occurrence idea from Approach 1, memoized: record each char's first position (+1 so 0 can mean "unseen") instead of rescanning. Single pass, early exit on the first conflict.

Store each character's partner (+1 so 0 can mean "unmapped") in both directions and reject any conflicting pairing. Same cost as Approach 2, but it states the bijection invariant directly — the version to reach for in an interview.

Two strings are isomorphic iff, position by position, their chars first appeared at the same index. position() rescans from the front on every call: quadratic — fine at n <= 2000, times out on LeetCode's 5 * 10^4.

The first-occurrence idea from Approach 1, memoized: record each char's first position (+1 so 0 can mean "unseen") instead of rescanning. Single pass, early exit on the first conflict.

Store each character's partner (+1 so 0 can mean "unmapped") in both directions and reject any conflicting pairing. Same cost as Approach 2, but it states the bijection invariant directly — the version to reach for in an interview.

Two strings are isomorphic iff, position by position, their chars first appeared at the same index. indexOf rescans from the front on every call: quadratic — fine at n <= 2000, times out on LeetCode's 5 * 10^4.

The first-occurrence idea from Approach 1, memoized: record each char's first position instead of rescanning. Single pass, early exit on the first conflict.

Walk both strings in lockstep with a Map in each direction, rejecting any conflicting pairing. Same cost as Approach 2, but it states the bijection invariant directly — the version to reach for in an interview.

Two strings are isomorphic iff, position by position, their chars first appeared at the same index. IndexByte rescans from the front on every call: quadratic — fine at n <= 2000, times out on LeetCode's 5 * 10^4.

The first-occurrence idea from Approach 1, memoized: record each char's first position (+1 so 0 can mean "unseen") instead of rescanning. Single pass, early exit on the first conflict.

Store each character's partner (+1 so 0 can mean "unmapped") in both directions and reject any conflicting pairing. Same cost as Approach 2, but it states the bijection invariant directly — the version to reach for in an interview.

Two strings are isomorphic iff, position by position, their chars first appeared at the same index. firstIndex rescans from the front on every call: quadratic — fine at n <= 2000, times out on LeetCode's 5 * 10^4.

The first-occurrence idea from Approach 1, memoized: record each char's first position instead of rescanning. Single pass, early exit on the first conflict.

Walk both strings in lockstep with a dictionary in each direction, rejecting any conflicting pairing. Same cost as Approach 2, but it states the bijection invariant directly — the version to reach for in an interview.

def isIsomorphic_index_of(self, s: str, t: str) -> bool:
    return all(s.index(cs) == t.index(ct) for cs, ct in zip(s, t))
def isIsomorphic_zip_sets(self, s: str, t: str) -> bool:
    return len(set(zip(s, t))) == len(set(s)) == len(set(t))
def isIsomorphic_first_index(self, s: str, t: str) -> bool:
    first_s = {}
    first_t = {}
    for i, (cs, ct) in enumerate(zip(s, t)):
        if first_s.setdefault(cs, i) != first_t.setdefault(ct, i):
            return False
    return True
def isIsomorphic(self, s: str, t: str) -> bool:
    s_to_t = {}
    t_to_s = {}
    for cs, ct in zip(s, t):
        if s_to_t.setdefault(cs, ct) != ct:
            return False
        if t_to_s.setdefault(ct, cs) != cs:
            return False
    return True
bool isIsomorphicIndexOf(std::string s, std::string t) {
    for (size_t i = 0; i < s.size(); ++i) {
        if (s.find(s[i]) != t.find(t[i])) {
            return false;
        }
    }
    return true;
}
bool isIsomorphicFirstIndex(std::string s, std::string t) {
    std::array<int, 256> firstS{};  // first index + 1; 0 = unseen
    std::array<int, 256> firstT{};
    for (size_t i = 0; i < s.size(); ++i) {
        int& fs = firstS[static_cast<unsigned char>(s[i])];
        int& ft = firstT[static_cast<unsigned char>(t[i])];
        if (fs != ft) {
            return false;
        }
        if (fs == 0) {
            fs = ft = static_cast<int>(i) + 1;
        }
    }
    return true;
}
bool isIsomorphic(std::string s, std::string t) {
    std::array<int, 256> mapS{};  // s-char -> t-char + 1 (0 = unmapped)
    std::array<int, 256> mapT{};  // t-char -> s-char + 1
    for (size_t i = 0; i < s.size(); ++i) {
        const unsigned char cs = s[i];
        const unsigned char ct = t[i];
        if (mapS[cs] != 0 && mapS[cs] != ct + 1) {
            return false;
        }
        if (mapT[ct] != 0 && mapT[ct] != cs + 1) {
            return false;
        }
        mapS[cs] = ct + 1;
        mapT[ct] = cs + 1;
    }
    return true;
}
pub fn is_isomorphic_index_of(s: String, t: String) -> bool {
    let (sb, tb) = (s.as_bytes(), t.as_bytes());
    for i in 0..sb.len() {
        let first_s = sb.iter().position(|&c| c == sb[i]);
        let first_t = tb.iter().position(|&c| c == tb[i]);
        if first_s != first_t {
            return false;
        }
    }
    true
}
pub fn is_isomorphic_first_index(s: String, t: String) -> bool {
    let mut first_s = [0usize; 256]; // first index + 1; 0 = unseen
    let mut first_t = [0usize; 256];
    for (i, (cs, ct)) in s.bytes().zip(t.bytes()).enumerate() {
        let (fs, ft) = (first_s[cs as usize], first_t[ct as usize]);
        if fs != ft {
            return false;
        }
        if fs == 0 {
            first_s[cs as usize] = i + 1;
            first_t[ct as usize] = i + 1;
        }
    }
    true
}
pub fn is_isomorphic(s: String, t: String) -> bool {
    let mut map_s = [0u16; 256]; // partner byte + 1; 0 = unmapped
    let mut map_t = [0u16; 256];
    for (cs, ct) in s.bytes().zip(t.bytes()) {
        let (i, j) = (cs as usize, ct as usize);
        if map_s[i] != 0 && map_s[i] != ct as u16 + 1 {
            return false;
        }
        if map_t[j] != 0 && map_t[j] != cs as u16 + 1 {
            return false;
        }
        map_s[i] = ct as u16 + 1;
        map_t[j] = cs as u16 + 1;
    }
    true
}
function isIsomorphicIndexOf(s: string, t: string): boolean {
    for (let i = 0; i < s.length; i++) {
        if (s.indexOf(s[i]) !== t.indexOf(t[i])) {
            return false;
        }
    }
    return true;
}
function isIsomorphicFirstIndex(s: string, t: string): boolean {
    const firstS = new Map<string, number>();
    const firstT = new Map<string, number>();
    for (let i = 0; i < s.length; i++) {
        const fs = firstS.get(s[i]) ?? -1;
        const ft = firstT.get(t[i]) ?? -1;
        if (fs !== ft) {
            return false;
        }
        if (fs === -1) {
            firstS.set(s[i], i);
            firstT.set(t[i], i);
        }
    }
    return true;
}
function isIsomorphic(s: string, t: string): boolean {
    const sToT = new Map<string, string>();
    const tToS = new Map<string, string>();
    for (let i = 0; i < s.length; i++) {
        const cs = s[i];
        const ct = t[i];
        const mappedT = sToT.get(cs);
        if (mappedT !== undefined && mappedT !== ct) {
            return false;
        }
        const mappedS = tToS.get(ct);
        if (mappedS !== undefined && mappedS !== cs) {
            return false;
        }
        sToT.set(cs, ct);
        tToS.set(ct, cs);
    }
    return true;
}
func isIsomorphicIndexOf(s string, t string) bool {
	for i := 0; i < len(s); i++ {
		if strings.IndexByte(s, s[i]) != strings.IndexByte(t, t[i]) {
			return false
		}
	}
	return true
}
func isIsomorphicFirstIndex(s string, t string) bool {
	var firstS, firstT [256]int // first index + 1; 0 = unseen
	for i := 0; i < len(s); i++ {
		fs, ft := firstS[s[i]], firstT[t[i]]
		if fs != ft {
			return false
		}
		if fs == 0 {
			firstS[s[i]] = i + 1
			firstT[t[i]] = i + 1
		}
	}
	return true
}
func isIsomorphic(s string, t string) bool {
	var mapS, mapT [256]int // partner byte + 1; 0 = unmapped
	for i := 0; i < len(s); i++ {
		cs, ct := s[i], t[i]
		if mapS[cs] != 0 && mapS[cs] != int(ct)+1 {
			return false
		}
		if mapT[ct] != 0 && mapT[ct] != int(cs)+1 {
			return false
		}
		mapS[cs] = int(ct) + 1
		mapT[ct] = int(cs) + 1
	}
	return true
}
func isIsomorphicIndexOf(_ s: String, _ t: String) -> Bool {
    let sa = Array(s)
    let ta = Array(t)
    for i in 0..<sa.count {
        if sa.firstIndex(of: sa[i]) != ta.firstIndex(of: ta[i]) {
            return false
        }
    }
    return true
}
func isIsomorphicFirstIndex(_ s: String, _ t: String) -> Bool {
    var firstS = [Character: Int]()
    var firstT = [Character: Int]()
    for (i, (cs, ct)) in zip(s, t).enumerated() {
        let fs = firstS[cs] ?? -1
        let ft = firstT[ct] ?? -1
        if fs != ft {
            return false
        }
        if fs == -1 {
            firstS[cs] = i
            firstT[ct] = i
        }
    }
    return true
}
func isIsomorphic(_ s: String, _ t: String) -> Bool {
    var sToT = [Character: Character]()
    var tToS = [Character: Character]()
    for (cs, ct) in zip(s, t) {
        if let mapped = sToT[cs], mapped != ct {
            return false
        }
        if let mapped = tToS[ct], mapped != cs {
            return false
        }
        sToT[cs] = ct
        tToS[ct] = cs
    }
    return true
}
Recommended Approach 1 of 4 · Brute force, compare first occurrences with str.indexO(n^2) time · O(1) space

15. Word Pattern

Easy · LC 290

Given a pattern of characters and a sentence of words, decide whether the words follow the pattern under a full one-to-one correspondence. Split the sentence into words, then walk pattern and words in lockstep with two hash maps, character to word and word to character, failing on the first conflict in either direction. The pitfall is the pair of easy misses: a length mismatch between pattern and word list is an automatic no, and checking only one mapping direction lets two different characters claim the same word.

The pairing is a bijection iff every pair of positions agrees: pattern chars match exactly when the words match. No maps at all, but comparing all pairs is quadratic — fine at w <= 2000, too slow once w grows (LeetCode's limits happen to be tiny, so it passes there too).

Drops the quadratic pair loop: a bijection leaves exactly as many distinct (char, word) pairs as distinct chars and distinct words — any remap inflates the pairs. Still three passes and no early exit.

Chars and words must first appear at the same positions; comparing first-seen indices needs only one map per side and no pair storage, and it stops at the first conflicting position.

Map char->word and word->char in one pass; a conflict in either direction means the pairing is not one-to-one. Same cost as Approach 3, but it states the bijection invariant directly — the version to reach for in an interview.

The pairing is a bijection iff every pair of positions agrees: pattern chars match exactly when the words match. No maps at all, but comparing all pairs is quadratic — fine at w <= 2000, too slow once w grows.

Chars and words must first appear at the same positions (+1 so 0 can mean "unseen"). Drops the quadratic pair loop of Approach 1: single pass, early exit on the first conflict.

Keep char -> word and word -> char maps; any conflicting pairing breaks the required bijection. Same cost as Approach 2, but it states the bijection invariant directly — the version to reach for in an interview.

The pairing is a bijection iff every pair of positions agrees: pattern chars match exactly when the words match. No maps at all, but comparing all pairs is quadratic — fine at w <= 2000, too slow once w grows.

Chars and words must first appear at the same positions (+1 so 0 can mean "unseen"). Drops the quadratic pair loop of Approach 1: single pass, early exit on the first conflict.

Keep char -> word and word -> char maps; any conflicting pairing breaks the required bijection. Same cost as Approach 2, but it states the bijection invariant directly — the version to reach for in an interview.

The pairing is a bijection iff every pair of positions agrees: pattern chars match exactly when the words match. No maps at all, but comparing all pairs is quadratic — fine at w <= 2000, too slow once w grows.

Chars and words must first appear at the same positions. Drops the quadratic pair loop of Approach 1: single pass, early exit on the first conflict.

Keep char -> word and word -> char Maps; any conflicting pairing breaks the required bijection. Same cost as Approach 2, but it states the bijection invariant directly — the version to reach for in an interview.

The pairing is a bijection iff every pair of positions agrees: pattern chars match exactly when the words match. No maps at all, but comparing all pairs is quadratic — fine at w <= 2000, too slow once w grows.

Chars and words must first appear at the same positions (+1 so 0 can mean "unseen"). Drops the quadratic pair loop of Approach 1: single pass, early exit on the first conflict.

Keep char -> word and word -> char maps; any conflicting pairing breaks the required bijection. Same cost as Approach 2, but it states the bijection invariant directly — the version to reach for in an interview.

The pairing is a bijection iff every pair of positions agrees: pattern chars match exactly when the words match. No maps at all, but comparing all pairs is quadratic — fine at w <= 2000, too slow once w grows.

Chars and words must first appear at the same positions. Drops the quadratic pair loop of Approach 1: single pass, early exit on the first conflict.

Keep char -> word and word -> char dictionaries; any conflicting pairing breaks the required bijection. Same cost as Approach 2, but it states the bijection invariant directly — the version to reach for in an interview.

def wordPattern_pairwise(self, pattern: str, s: str) -> bool:
    words = s.split()
    if len(words) != len(pattern):
        return False

    n = len(words)
    for i in range(n):
        for j in range(i + 1, n):
            if (pattern[i] == pattern[j]) != (words[i] == words[j]):
                return False
    return True
def wordPattern_zip_sets(self, pattern: str, s: str) -> bool:
    words = s.split()
    return (len(words) == len(pattern)
            and len(set(zip(pattern, words)))
            == len(set(pattern)) == len(set(words)))
def wordPattern_first_index(self, pattern: str, s: str) -> bool:
    words = s.split()
    if len(words) != len(pattern):
        return False

    first_char = {}
    first_word = {}
    for i, (char, word) in enumerate(zip(pattern, words)):
        if first_char.setdefault(char, i) != first_word.setdefault(word, i):
            return False
    return True
def wordPattern(self, pattern: str, s: str) -> bool:
    words = s.split()
    if len(words) != len(pattern):
        return False

    char_to_word = {}
    word_to_char = {}
    for char, word in zip(pattern, words):
        if char_to_word.setdefault(char, word) != word:
            return False
        if word_to_char.setdefault(word, char) != char:
            return False
    return True
bool wordPatternPairwise(std::string pattern, std::string s) {
    std::vector<std::string> words;
    std::istringstream stream(s);
    std::string word;
    while (stream >> word) {
        words.push_back(word);
    }
    if (words.size() != pattern.size()) {
        return false;
    }

    for (size_t i = 0; i < words.size(); ++i) {
        for (size_t j = i + 1; j < words.size(); ++j) {
            if ((pattern[i] == pattern[j]) != (words[i] == words[j])) {
                return false;
            }
        }
    }
    return true;
}
bool wordPatternFirstIndex(std::string pattern, std::string s) {
    std::vector<std::string> words;
    std::istringstream stream(s);
    std::string word;
    while (stream >> word) {
        words.push_back(word);
    }
    if (words.size() != pattern.size()) {
        return false;
    }

    std::array<int, 256> firstChar{};  // first index + 1; 0 = unseen
    std::unordered_map<std::string, int> firstWord;
    for (size_t i = 0; i < words.size(); ++i) {
        int& fc = firstChar[static_cast<unsigned char>(pattern[i])];
        const auto it = firstWord.find(words[i]);
        const int fw = it == firstWord.end() ? 0 : it->second;
        if (fc != fw) {
            return false;
        }
        if (fc == 0) {
            fc = static_cast<int>(i) + 1;
            firstWord.emplace(words[i], fc);
        }
    }
    return true;
}
bool wordPattern(std::string pattern, std::string s) {
    std::vector<std::string> words;
    std::istringstream stream(s);
    std::string word;
    while (stream >> word) {
        words.push_back(word);
    }
    if (words.size() != pattern.size()) {
        return false;
    }

    std::unordered_map<char, std::string> charToWord;
    std::unordered_map<std::string, char> wordToChar;
    for (size_t i = 0; i < words.size(); ++i) {
        const char c = pattern[i];
        const auto [itC, newChar] = charToWord.emplace(c, words[i]);
        if (!newChar && itC->second != words[i]) {
            return false;
        }
        const auto [itW, newWord] = wordToChar.emplace(words[i], c);
        if (!newWord && itW->second != c) {
            return false;
        }
    }
    return true;
}
pub fn word_pattern_pairwise(pattern: String, s: String) -> bool {
    let words: Vec<&str> = s.split(' ').collect();
    let pat = pattern.as_bytes();
    if words.len() != pat.len() {
        return false;
    }

    for i in 0..words.len() {
        for j in i + 1..words.len() {
            if (pat[i] == pat[j]) != (words[i] == words[j]) {
                return false;
            }
        }
    }
    true
}
pub fn word_pattern_first_index(pattern: String, s: String) -> bool {
    let words: Vec<&str> = s.split(' ').collect();
    if words.len() != pattern.len() {
        return false;
    }

    let mut first_char = [0usize; 256]; // first index + 1; 0 = unseen
    let mut first_word: HashMap<&str, usize> = HashMap::new();
    for (i, (c, w)) in pattern.bytes().zip(words).enumerate() {
        let fc = first_char[c as usize];
        let fw = *first_word.get(w).unwrap_or(&0);
        if fc != fw {
            return false;
        }
        if fc == 0 {
            first_char[c as usize] = i + 1;
            first_word.insert(w, i + 1);
        }
    }
    true
}
pub fn word_pattern(pattern: String, s: String) -> bool {
    let words: Vec<&str> = s.split(' ').collect();
    if words.len() != pattern.len() {
        return false;
    }

    let mut char_to_word: HashMap<char, &str> = HashMap::new();
    let mut word_to_char: HashMap<&str, char> = HashMap::new();
    for (c, w) in pattern.chars().zip(words) {
        if *char_to_word.entry(c).or_insert(w) != w {
            return false;
        }
        if *word_to_char.entry(w).or_insert(c) != c {
            return false;
        }
    }
    true
}
function wordPatternPairwise(pattern: string, s: string): boolean {
    const words = s.split(" ");
    if (words.length !== pattern.length) {
        return false;
    }

    for (let i = 0; i < words.length; i++) {
        for (let j = i + 1; j < words.length; j++) {
            if ((pattern[i] === pattern[j]) !== (words[i] === words[j])) {
                return false;
            }
        }
    }
    return true;
}
function wordPatternFirstIndex(pattern: string, s: string): boolean {
    const words = s.split(" ");
    if (words.length !== pattern.length) {
        return false;
    }

    const firstChar = new Map<string, number>();
    const firstWord = new Map<string, number>();
    for (let i = 0; i < pattern.length; i++) {
        const fc = firstChar.get(pattern[i]) ?? -1;
        const fw = firstWord.get(words[i]) ?? -1;
        if (fc !== fw) {
            return false;
        }
        if (fc === -1) {
            firstChar.set(pattern[i], i);
            firstWord.set(words[i], i);
        }
    }
    return true;
}
function wordPattern(pattern: string, s: string): boolean {
    const words = s.split(" ");
    if (words.length !== pattern.length) {
        return false;
    }

    const charToWord = new Map<string, string>();
    const wordToChar = new Map<string, string>();
    for (let i = 0; i < pattern.length; i++) {
        const c = pattern[i];
        const w = words[i];
        const seenWord = charToWord.get(c);
        if (seenWord !== undefined && seenWord !== w) {
            return false;
        }
        const seenChar = wordToChar.get(w);
        if (seenChar !== undefined && seenChar !== c) {
            return false;
        }
        charToWord.set(c, w);
        wordToChar.set(w, c);
    }
    return true;
}
func wordPatternPairwise(pattern string, s string) bool {
	words := strings.Split(s, " ")
	if len(words) != len(pattern) {
		return false
	}

	for i := 0; i < len(words); i++ {
		for j := i + 1; j < len(words); j++ {
			if (pattern[i] == pattern[j]) != (words[i] == words[j]) {
				return false
			}
		}
	}
	return true
}
func wordPatternFirstIndex(pattern string, s string) bool {
	words := strings.Split(s, " ")
	if len(words) != len(pattern) {
		return false
	}

	var firstChar [256]int // first index + 1; 0 = unseen
	firstWord := make(map[string]int)
	for i := 0; i < len(pattern); i++ {
		fc, fw := firstChar[pattern[i]], firstWord[words[i]]
		if fc != fw {
			return false
		}
		if fc == 0 {
			firstChar[pattern[i]] = i + 1
			firstWord[words[i]] = i + 1
		}
	}
	return true
}
func wordPattern(pattern string, s string) bool {
	words := strings.Split(s, " ")
	if len(words) != len(pattern) {
		return false
	}

	charToWord := make(map[byte]string)
	wordToChar := make(map[string]byte)
	for i := 0; i < len(pattern); i++ {
		c, w := pattern[i], words[i]
		if seen, ok := charToWord[c]; ok && seen != w {
			return false
		}
		if seen, ok := wordToChar[w]; ok && seen != c {
			return false
		}
		charToWord[c] = w
		wordToChar[w] = c
	}
	return true
}
func wordPatternPairwise(_ pattern: String, _ s: String) -> Bool {
    let words = s.split(separator: " ")
    let chars = Array(pattern)
    guard words.count == chars.count else {
        return false
    }

    for i in 0..<words.count {
        for j in (i + 1)..<words.count {
            if (chars[i] == chars[j]) != (words[i] == words[j]) {
                return false
            }
        }
    }
    return true
}
func wordPatternFirstIndex(_ pattern: String, _ s: String) -> Bool {
    let words = s.split(separator: " ")
    guard words.count == pattern.count else {
        return false
    }

    var firstChar = [Character: Int]()
    var firstWord = [Substring: Int]()
    for (i, (c, w)) in zip(pattern, words).enumerated() {
        let fc = firstChar[c] ?? -1
        let fw = firstWord[w] ?? -1
        if fc != fw {
            return false
        }
        if fc == -1 {
            firstChar[c] = i
            firstWord[w] = i
        }
    }
    return true
}
func wordPattern(_ pattern: String, _ s: String) -> Bool {
    let words = s.split(separator: " ")
    guard words.count == pattern.count else {
        return false
    }

    var charToWord = [Character: Substring]()
    var wordToChar = [Substring: Character]()
    for (c, w) in zip(pattern, words) {
        if let seen = charToWord[c], seen != w {
            return false
        }
        if let seen = wordToChar[w], seen != c {
            return false
        }
        charToWord[c] = w
        wordToChar[w] = c
    }
    return true
}
Recommended Approach 1 of 4 · Brute force, pairwise consistency checkO(w^2 * L) for words of length <= L time · O(n + m) space

Intervals

16. Summary Ranges

Easy · LC 228

Given a sorted array of unique integers, return the shortest list of ranges that covers all the numbers exactly. Anchor the start of the current run, walk forward while each next value is exactly one more than the previous, then emit the run and jump past it. The trick is the emission step: a single-number run is printed by itself while a longer run joins its start and end with an arrow, and formatting runs directly during the walk keeps it one pass with no storage beyond the output.

Collect every index where a run starts (index 0 plus any spot where the gap to the previous value exceeds one), then format neighbors in a second pass. Easy to reason about, but keeps whole side lists.

Inside a consecutive run, value - index is constant (both step by one), so grouping on that key slices out the runs directly. Only one run is buffered at a time — no whole-array side lists.

Anchor the start of the current run, walk while each next value is exactly previous + 1, then emit the run and jump past it. A single walk that formats runs directly, with no intermediate storage.

First merge the values into a list of [start, end] runs, then format each run in a second pass. Easy to reason about, but the intermediate list stores every run twice.

Anchor the start of the current run, extend while each next value is exactly previous + 1, then emit "a->b" directly. A single walk that formats runs as it goes — no intermediate run list.

First merge the values into a list of (start, end) runs, then format each run in a second pass. Easy to reason about, but the intermediate list stores every run twice.

Anchor the start of the current run, extend while each next value is exactly previous + 1, then emit "a->b" directly. A single walk that formats runs as it goes — no intermediate run list.

First merge the values into a list of [start, end] runs, then format each run in a second pass. Easy to reason about, but the intermediate list stores every run twice.

Anchor the start of the current run, extend while each next value is exactly previous + 1, then emit "a->b" directly. A single walk that formats runs as it goes — no intermediate run list.

First merge the values into a list of [start, end] runs, then format each run in a second pass. Easy to reason about, but the intermediate list stores every run twice.

Anchor the start of the current run, extend while each next value is exactly previous + 1, then emit "a->b" directly. A single walk that formats runs as it goes — no intermediate run list.

First merge the values into a list of (start, end) runs, then format each run in a second pass. Easy to reason about, but the intermediate list stores every run twice.

Anchor the start of the current run, extend while each next value is exactly previous + 1, then emit "a->b" directly. A single walk that formats runs as it goes — no intermediate run list.

def summaryRanges_breakpoints(self, nums: list[int]) -> list[str]:
    if not nums:
        return []
    starts = [0] + [i for i in range(1, len(nums))
                    if nums[i] != nums[i - 1] + 1]
    ends = [i - 1 for i in starts[1:]] + [len(nums) - 1]
    return [str(nums[a]) if a == b else f"{nums[a]}->{nums[b]}"
            for a, b in zip(starts, ends)]
def summaryRanges_groupby(self, nums: list[int]) -> list[str]:
    ranges = []
    for _, run in groupby(enumerate(nums), key=lambda p: p[1] - p[0]):
        pairs = list(run)
        first, last = pairs[0][1], pairs[-1][1]
        ranges.append(str(first) if first == last else f"{first}->{last}")
    return ranges
def summaryRanges(self, nums: list[int]) -> list[str]:
    ranges = []
    i, n = 0, len(nums)
    while i < n:
        start = i
        while i + 1 < n and nums[i + 1] == nums[i] + 1:
            i += 1
        if start == i:
            ranges.append(str(nums[start]))
        else:
            ranges.append(f"{nums[start]}->{nums[i]}")
        i += 1
    return ranges
std::vector<std::string> summaryRangesTwoPass(std::vector<int>& nums) {
    std::vector<std::pair<int, int>> runs;  // inclusive [start, end]
    for (int x : nums) {
        if (!runs.empty() &&
            static_cast<long long>(x) ==
                static_cast<long long>(runs.back().second) + 1) {
            runs.back().second = x;
        } else {
            runs.push_back({x, x});
        }
    }
    std::vector<std::string> ranges;
    ranges.reserve(runs.size());
    for (const auto& [start, end] : runs) {
        if (start == end) {
            ranges.push_back(std::to_string(start));
        } else {
            ranges.push_back(std::to_string(start) + "->" +
                             std::to_string(end));
        }
    }
    return ranges;
}
std::vector<std::string> summaryRanges(std::vector<int>& nums) {
    std::vector<std::string> ranges;
    const size_t n = nums.size();
    for (size_t i = 0; i < n;) {
        const size_t start = i;
        while (i + 1 < n &&
               static_cast<long long>(nums[i + 1]) ==
                   static_cast<long long>(nums[i]) + 1) {
            ++i;
        }
        if (start == i) {
            ranges.push_back(std::to_string(nums[start]));
        } else {
            ranges.push_back(std::to_string(nums[start]) + "->" +
                             std::to_string(nums[i]));
        }
        ++i;
    }
    return ranges;
}
pub fn summary_ranges_two_pass(nums: Vec<i32>) -> Vec<String> {
    let mut runs: Vec<(i32, i32)> = Vec::new(); // inclusive (start, end)
    for &x in &nums {
        match runs.last_mut() {
            Some(run) if x as i64 == run.1 as i64 + 1 => run.1 = x,
            _ => runs.push((x, x)),
        }
    }
    runs.iter()
        .map(|&(start, end)| {
            if start == end {
                start.to_string()
            } else {
                format!("{}->{}", start, end)
            }
        })
        .collect()
}
pub fn summary_ranges(nums: Vec<i32>) -> Vec<String> {
    let mut ranges = Vec::new();
    let n = nums.len();
    let mut i = 0;
    while i < n {
        let start = i;
        while i + 1 < n && nums[i + 1] as i64 == nums[i] as i64 + 1 {
            i += 1;
        }
        if start == i {
            ranges.push(nums[start].to_string());
        } else {
            ranges.push(format!("{}->{}", nums[start], nums[i]));
        }
        i += 1;
    }
    ranges
}
function summaryRangesTwoPass(nums: number[]): string[] {
    const runs: Array<[number, number]> = []; // inclusive [start, end]
    for (const x of nums) {
        const last = runs[runs.length - 1];
        if (last !== undefined && x === last[1] + 1) {
            last[1] = x;
        } else {
            runs.push([x, x]);
        }
    }
    return runs.map(([start, end]) => (start === end ? `${start}` : `${start}->${end}`));
}
function summaryRanges(nums: number[]): string[] {
    const ranges: string[] = [];
    const n = nums.length;
    for (let i = 0; i < n; ) {
        const start = i;
        while (i + 1 < n && nums[i + 1] === nums[i] + 1) {
            i++;
        }
        ranges.push(start === i ? `${nums[start]}` : `${nums[start]}->${nums[i]}`);
        i++;
    }
    return ranges;
}
func summaryRangesTwoPass(nums []int) []string {
	type run struct{ start, end int }
	runs := []run{}
	for _, x := range nums {
		if len(runs) > 0 && x == runs[len(runs)-1].end+1 {
			runs[len(runs)-1].end = x
		} else {
			runs = append(runs, run{x, x})
		}
	}
	ranges := []string{}
	for _, r := range runs {
		if r.start == r.end {
			ranges = append(ranges, strconv.Itoa(r.start))
		} else {
			ranges = append(ranges, strconv.Itoa(r.start)+"->"+strconv.Itoa(r.end))
		}
	}
	return ranges
}
func summaryRanges(nums []int) []string {
	ranges := []string{}
	n := len(nums)
	for i := 0; i < n; {
		start := i
		for i+1 < n && nums[i+1] == nums[i]+1 {
			i++
		}
		if start == i {
			ranges = append(ranges, strconv.Itoa(nums[start]))
		} else {
			ranges = append(ranges, strconv.Itoa(nums[start])+"->"+strconv.Itoa(nums[i]))
		}
		i++
	}
	return ranges
}
func summaryRangesTwoPass(_ nums: [Int]) -> [String] {
    var runs = [(start: Int, end: Int)]()  // inclusive
    for x in nums {
        if let last = runs.last, x == last.end + 1 {
            runs[runs.count - 1].end = x
        } else {
            runs.append((x, x))
        }
    }
    return runs.map { $0.start == $0.end ? "\($0.start)" : "\($0.start)->\($0.end)" }
}
func summaryRanges(_ nums: [Int]) -> [String] {
    var ranges = [String]()
    var i = 0
    while i < nums.count {
        let start = i
        while i + 1 < nums.count && nums[i + 1] == nums[i] + 1 {
            i += 1
        }
        ranges.append(start == i ? "\(nums[start])" : "\(nums[start])->\(nums[i])")
        i += 1
    }
    return ranges
}
Recommended Approach 1 of 3 · Breakpoint detection (two-pass)O(n) time · O(n) for the breakpoint lists space

17. Minimum Number of Arrows to Burst Balloons

Medium · LC 452

Given balloons as horizontal intervals, find the minimum number of vertical arrows needed to burst them all. Sort the balloons by right end and shoot each arrow at the end of the first un-burst balloon, starting a new arrow only when a later balloon begins past the current arrow's position. The insight is that shooting at the earliest end is always safe, since every balloon overlapping that one must span its coordinate — and touching edges count as hits, so a new arrow is needed only when a start strictly exceeds the arrow's position.

Track the overlap window of the current group; when a balloon starts past the window, the group takes one arrow and a new group begins. Correct, but it carries an extra min() per balloon — more state than the problem actually needs.

Shooting at the earliest un-burst balloon's end is always safe: every balloon overlapping it must span that x, so one arrow there also bursts each later balloon whose start is <= that end. The window bookkeeping disappears: the arrow position only ever moves forward.

Track the overlap window of the current group; when a balloon starts past the window, the group takes one arrow and a new group begins. Correct, but it carries an extra min() per balloon — more state than the problem actually needs.

Shooting at the earliest un-burst balloon's end is always safe: every balloon overlapping it must span that x, so one arrow there also bursts each later balloon whose start is <= that end. The window bookkeeping disappears: the arrow position only ever moves forward.

Track the overlap window of the current group; when a balloon starts past the window, the group takes one arrow and a new group begins. Correct, but it carries an extra min() per balloon — more state than the problem actually needs.

Shooting at the earliest un-burst balloon's end is always safe: every balloon overlapping it must span that x, so one arrow there also bursts each later balloon whose start is <= that end. The window bookkeeping disappears: the arrow position only ever moves forward.

Track the overlap window of the current group; when a balloon starts past the window, the group takes one arrow and a new group begins. Correct, but it carries an extra Math.min per balloon — more state than the problem actually needs.

Shooting at the earliest un-burst balloon's end is always safe: every balloon overlapping it must span that x, so one arrow there also bursts each later balloon whose start is <= that end. The window bookkeeping disappears: the arrow position only ever moves forward.

Track the overlap window of the current group; when a balloon starts past the window, the group takes one arrow and a new group begins. Correct, but it carries an extra min per balloon — more state than the problem actually needs.

Shooting at the earliest un-burst balloon's end is always safe: every balloon overlapping it must span that x, so one arrow there also bursts each later balloon whose start is <= that end. The window bookkeeping disappears: the arrow position only ever moves forward.

Track the overlap window of the current group; when a balloon starts past the window, the group takes one arrow and a new group begins. Correct, but it carries an extra min() per balloon — more state than the problem actually needs.

Shooting at the earliest un-burst balloon's end is always safe: every balloon overlapping it must span that x, so one arrow there also bursts each later balloon whose start is <= that end. The window bookkeeping disappears: the arrow position only ever moves forward.

def findMinArrowShots_sort_by_start(self, points: List[List[int]]) -> int:
    points.sort()
    arrows = 1
    window_end = points[0][1]
    for start, end in points[1:]:
        if start > window_end:
            arrows += 1
            window_end = end
        else:
            window_end = min(window_end, end)
    return arrows
def findMinArrowShots(self, points: List[List[int]]) -> int:
    points.sort(key=lambda p: p[1])
    arrows = 1
    arrow_x = points[0][1]
    for start, end in points:
        if start > arrow_x:
            arrows += 1
            arrow_x = end
    return arrows
int findMinArrowShotsSortByStart(vector<vector<int>>& points) {
    sort(points.begin(), points.end(),
         [](const vector<int>& a, const vector<int>& b) {
             if (a[0] != b[0]) return a[0] < b[0];
             return a[1] < b[1];
         });
    int arrows = 1;
    int windowEnd = points[0][1];
    for (size_t i = 1; i < points.size(); ++i) {
        if (points[i][0] > windowEnd) {
            ++arrows;
            windowEnd = points[i][1];
        } else {
            windowEnd = min(windowEnd, points[i][1]);
        }
    }
    return arrows;
}
int findMinArrowShots(vector<vector<int>>& points) {
    sort(points.begin(), points.end(),
         [](const vector<int>& a, const vector<int>& b) { return a[1] < b[1]; });
    int arrows = 1;
    int arrowX = points[0][1];
    for (const auto& p : points) {
        if (p[0] > arrowX) {
            ++arrows;
            arrowX = p[1];
        }
    }
    return arrows;
}
pub fn find_min_arrow_shots_sort_by_start(mut points: Vec<Vec<i32>>) -> i32 {
    points.sort_unstable_by(|a, b| a[0].cmp(&b[0]).then(a[1].cmp(&b[1])));
    let mut arrows = 1;
    let mut window_end = points[0][1];
    for p in points.iter().skip(1) {
        if p[0] > window_end {
            arrows += 1;
            window_end = p[1];
        } else {
            window_end = window_end.min(p[1]);
        }
    }
    arrows
}
pub fn find_min_arrow_shots(mut points: Vec<Vec<i32>>) -> i32 {
    points.sort_unstable_by_key(|p| p[1]);
    let mut arrows = 1;
    let mut arrow_x = points[0][1];
    for p in &points {
        if p[0] > arrow_x {
            arrows += 1;
            arrow_x = p[1];
        }
    }
    arrows
}
function findMinArrowShotsSortByStart(points: number[][]): number {
    points.sort((a, b) => {
        if (a[0] !== b[0]) return a[0] < b[0] ? -1 : 1;
        return a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0;
    });
    let arrows = 1;
    let windowEnd = points[0][1];
    for (let i = 1; i < points.length; i++) {
        if (points[i][0] > windowEnd) {
            arrows++;
            windowEnd = points[i][1];
        } else {
            windowEnd = Math.min(windowEnd, points[i][1]);
        }
    }
    return arrows;
}
function findMinArrowShots(points: number[][]): number {
    points.sort((a, b) => (a[1] < b[1] ? -1 : a[1] > b[1] ? 1 : 0));
    let arrows = 1;
    let arrowX = points[0][1];
    for (const p of points) {
        if (p[0] > arrowX) {
            arrows++;
            arrowX = p[1];
        }
    }
    return arrows;
}
func findMinArrowShotsSortByStart(points [][]int) int {
	sort.Slice(points, func(i, j int) bool {
		if points[i][0] != points[j][0] {
			return points[i][0] < points[j][0]
		}
		return points[i][1] < points[j][1]
	})
	arrows := 1
	windowEnd := points[0][1]
	for _, p := range points[1:] {
		if p[0] > windowEnd {
			arrows++
			windowEnd = p[1]
		} else if p[1] < windowEnd {
			windowEnd = p[1]
		}
	}
	return arrows
}
func findMinArrowShots(points [][]int) int {
	sort.Slice(points, func(i, j int) bool { return points[i][1] < points[j][1] })
	arrows := 1
	arrowX := points[0][1]
	for _, p := range points {
		if p[0] > arrowX {
			arrows++
			arrowX = p[1]
		}
	}
	return arrows
}
func findMinArrowShotsSortByStart(_ points: [[Int]]) -> Int {
    let sorted = points.sorted {
        if $0[0] != $1[0] { return $0[0] < $1[0] }
        return $0[1] < $1[1]
    }
    var arrows = 1
    var windowEnd = sorted[0][1]
    for p in sorted.dropFirst() {
        if p[0] > windowEnd {
            arrows += 1
            windowEnd = p[1]
        } else {
            windowEnd = min(windowEnd, p[1])
        }
    }
    return arrows
}
func findMinArrowShots(_ points: [[Int]]) -> Int {
    let sorted = points.sorted { $0[1] < $1[1] }
    var arrows = 1
    var arrowX = sorted[0][1]
    for p in sorted where p[0] > arrowX {
        arrows += 1
        arrowX = p[1]
    }
    return arrows
}
Recommended Approach 1 of 2 · Sort by start, shrink a running intersectionO(n log n) time · O(n) for the sort space

Stack

18. Basic Calculator

Hard · LC 224

Given a string containing digits, plus and minus signs, parentheses, and spaces, evaluate the expression without any built-in evaluator. Scan once, building each number digit by digit and folding it into a running result with the pending sign; an opening parenthesis pushes the outer result and sign onto a stack and starts fresh, while a closing one folds the inner result back into the popped context. The subtlety is that unary minus works for free because a fresh result starts at zero, and the last pending number must still be flushed into the result after the loop ends.

Each '(' recurses to evaluate its subexpression, mirroring the grammar directly — but the call stack grows with the nesting depth, which can hit Python's recursion limit on deeply nested input.

Trades the recursion for an explicit stack — no depth limit. Add every number straight into the total with (its own sign) times (the sign of the enclosing parens); ')' is then just a pop.

'(' saves the outer partial result and its sign, then starts fresh; ')' folds the inner result back into the saved context. Each char is handled exactly once with no inner digit loop, and unary minus works for free because a fresh result starts at 0.

Each '(' recurses to evaluate its subexpression, mirroring the grammar directly — but the call stack grows with the nesting depth, which risks stack overflow on deeply nested input.

Same idea with an explicit stack, so nesting depth can never blow the call stack: '(' saves the outer context and starts fresh; ')' folds the inner result back into it. Unary minus works for free because a fresh result starts at 0.

Each '(' recurses to evaluate its subexpression, mirroring the grammar directly — but the call stack grows with the nesting depth, which risks stack overflow on deeply nested input.

Same idea with an explicit stack, so nesting depth can never blow the call stack: '(' saves the outer context and starts fresh; ')' folds the inner result back into it. Unary minus works for free because a fresh result starts at 0.

Each '(' recurses to evaluate its subexpression, mirroring the grammar directly — but the call stack grows with the nesting depth, which risks a stack overflow on deeply nested input.

Same idea with an explicit stack, so nesting depth can never blow the call stack: '(' saves the outer context and starts fresh; ')' folds the inner result back into it. Unary minus works for free because a fresh result starts at 0.

Each '(' recurses to evaluate its subexpression, mirroring the grammar directly — but the call stack grows with the nesting depth, which risks stack growth on deeply nested input.

Same idea with an explicit stack, so nesting depth can never blow the call stack: '(' saves the outer context and starts fresh; ')' folds the inner result back into it. Unary minus works for free because a fresh result starts at 0.

Each '(' recurses to evaluate its subexpression, mirroring the grammar directly — but the call stack grows with the nesting depth, which risks stack overflow on deeply nested input.

Same idea with an explicit stack, so nesting depth can never blow the call stack: '(' saves the outer context and starts fresh; ')' folds the inner result back into it. Unary minus works for free because a fresh result starts at 0.

def calculate_recursive(self, s: str) -> int:
    def parse(i: int) -> tuple[int, int]:
        """Evaluate from s[i]; return (value, index just past what was read)."""
        result = 0
        sign = 1
        while i < len(s):
            ch = s[i]
            if ch.isdigit():
                num = 0
                while i < len(s) and s[i].isdigit():
                    num = num * 10 + int(s[i])
                    i += 1
                result += sign * num
                continue
            if ch == "+":
                sign = 1
            elif ch == "-":
                sign = -1
            elif ch == "(":
                value, i = parse(i + 1)
                result += sign * value
                continue
            elif ch == ")":
                return result, i + 1
            i += 1
        return result, i

    return parse(0)[0]
def calculate_sign_stack(self, s: str) -> int:
    total = 0
    sign = 1
    context = [1]  # sign multiplier of each enclosing paren level
    i = 0
    while i < len(s):
        ch = s[i]
        if ch.isdigit():
            num = 0
            while i < len(s) and s[i].isdigit():
                num = num * 10 + int(s[i])
                i += 1
            total += sign * num
            continue
        if ch == "+":
            sign = context[-1]
        elif ch == "-":
            sign = -context[-1]
        elif ch == "(":
            context.append(sign)
        elif ch == ")":
            context.pop()
        i += 1
    return total
def calculate(self, s: str) -> int:
    result = 0
    sign = 1
    num = 0
    stack = []  # (outer result, outer sign) per open paren
    for ch in s:
        if ch.isdigit():
            num = num * 10 + int(ch)
        elif ch == "+":
            result += sign * num
            num, sign = 0, 1
        elif ch == "-":
            result += sign * num
            num, sign = 0, -1
        elif ch == "(":
            stack.append((result, sign))
            result, sign = 0, 1
        elif ch == ")":
            result += sign * num
            num = 0
            outer_result, outer_sign = stack.pop()
            result = outer_result + outer_sign * result
    return result + sign * num
    int calculateRecursive(string s) {
        size_t i = 0;
        return static_cast<int>(parseExpr(s, i));
    }

private:
    // Evaluate s from index i until ')' or the end; leaves i just past
    // what was read.
    long long parseExpr(const string& s, size_t& i) {
        long long result = 0, num = 0, sign = 1;
        while (i < s.size()) {
            char c = s[i];
            if (c >= '0' && c <= '9') {
                num = num * 10 + (c - '0');
            } else if (c == '+') {
                result += sign * num;
                num = 0;
                sign = 1;
            } else if (c == '-') {
                result += sign * num;
                num = 0;
                sign = -1;
            } else if (c == '(') {
                ++i;
                result += sign * parseExpr(s, i);
                num = 0;
                sign = 1;
                continue;  // i is already past the matching ')'
            } else if (c == ')') {
                ++i;
                return result + sign * num;
            }
            ++i;
        }
        return result + sign * num;
    }

public:
int calculate(string s) {
    long long result = 0, num = 0, sign = 1;
    vector<pair<long long, long long>> stack;  // (outer result, outer sign)
    for (char c : s) {
        if (c >= '0' && c <= '9') {
            num = num * 10 + (c - '0');
        } else if (c == '+') {
            result += sign * num;
            num = 0;
            sign = 1;
        } else if (c == '-') {
            result += sign * num;
            num = 0;
            sign = -1;
        } else if (c == '(') {
            stack.push_back({result, sign});
            result = 0;
            sign = 1;
        } else if (c == ')') {
            result += sign * num;
            num = 0;
            result = stack.back().first + stack.back().second * result;
            stack.pop_back();
        }
    }
    return static_cast<int>(result + sign * num);
}
pub fn calculate_recursive(s: String) -> i32 {
    Self::parse_expr(s.as_bytes(), 0).0 as i32
}

// Evaluate s from index i until b')' or the end; returns the value and
// the index just past what was read.
fn parse_expr(s: &[u8], mut i: usize) -> (i64, usize) {
    let mut result: i64 = 0;
    let mut num: i64 = 0;
    let mut sign: i64 = 1;
    while i < s.len() {
        match s[i] {
            b'0'..=b'9' => num = num * 10 + i64::from(s[i] - b'0'),
            b'+' => {
                result += sign * num;
                num = 0;
                sign = 1;
            }
            b'-' => {
                result += sign * num;
                num = 0;
                sign = -1;
            }
            b'(' => {
                let (inner, next) = Self::parse_expr(s, i + 1);
                result += sign * inner;
                num = 0;
                sign = 1;
                i = next; // already past the matching ')'
                continue;
            }
            b')' => return (result + sign * num, i + 1),
            _ => {} // spaces
        }
        i += 1;
    }
    (result + sign * num, i)
}
pub fn calculate(s: String) -> i32 {
    let mut result: i64 = 0;
    let mut num: i64 = 0;
    let mut sign: i64 = 1;
    let mut stack: Vec<(i64, i64)> = Vec::new(); // (outer result, outer sign)
    for c in s.bytes() {
        match c {
            b'0'..=b'9' => num = num * 10 + i64::from(c - b'0'),
            b'+' => {
                result += sign * num;
                num = 0;
                sign = 1;
            }
            b'-' => {
                result += sign * num;
                num = 0;
                sign = -1;
            }
            b'(' => {
                stack.push((result, sign));
                result = 0;
                sign = 1;
            }
            b')' => {
                result += sign * num;
                num = 0;
                let (outer_result, outer_sign) = stack.pop().unwrap();
                result = outer_result + outer_sign * result;
            }
            _ => {} // spaces
        }
    }
    (result + sign * num) as i32
}
function calculateRecursive(s: string): number {
    return parseExpr(s, 0)[0];
}

// Evaluate s from index i until ")" or the end; returns [value, index
// just past what was read].
function parseExpr(s: string, i: number): [number, number] {
    let result = 0;
    let num = 0;
    let sign = 1;
    while (i < s.length) {
        const c = s.charAt(i);
        if (c >= "0" && c <= "9") {
            num = num * 10 + (s.charCodeAt(i) - 48);
        } else if (c === "+") {
            result += sign * num;
            num = 0;
            sign = 1;
        } else if (c === "-") {
            result += sign * num;
            num = 0;
            sign = -1;
        } else if (c === "(") {
            const [inner, next] = parseExpr(s, i + 1);
            result += sign * inner;
            num = 0;
            sign = 1;
            i = next; // already past the matching ")"
            continue;
        } else if (c === ")") {
            return [result + sign * num, i + 1];
        }
        i++;
    }
    return [result + sign * num, i];
}
function calculate(s: string): number {
    let result = 0;
    let num = 0;
    let sign = 1;
    const stack: number[] = []; // flattened (outer result, outer sign) pairs
    for (let i = 0; i < s.length; i++) {
        const c = s.charAt(i);
        if (c >= "0" && c <= "9") {
            num = num * 10 + (s.charCodeAt(i) - 48);
        } else if (c === "+") {
            result += sign * num;
            num = 0;
            sign = 1;
        } else if (c === "-") {
            result += sign * num;
            num = 0;
            sign = -1;
        } else if (c === "(") {
            stack.push(result, sign);
            result = 0;
            sign = 1;
        } else if (c === ")") {
            result += sign * num;
            num = 0;
            const outerSign = stack.pop()!;
            const outerResult = stack.pop()!;
            result = outerResult + outerSign * result;
        }
    }
    return result + sign * num;
}
func calculateRecursive(s string) int {
	result, _ := parseExpr(s, 0)
	return result
}

// parseExpr evaluates s from index i until ')' or the end; it returns the
// value and the index just past what was read.
func parseExpr(s string, i int) (int, int) {
	result, num, sign := 0, 0, 1
	for i < len(s) {
		c := s[i]
		switch {
		case c >= '0' && c <= '9':
			num = num*10 + int(c-'0')
		case c == '+':
			result += sign * num
			num, sign = 0, 1
		case c == '-':
			result += sign * num
			num, sign = 0, -1
		case c == '(':
			var inner int
			inner, i = parseExpr(s, i+1)
			result += sign * inner
			num, sign = 0, 1
			continue // i is already past the matching ')'
		case c == ')':
			return result + sign*num, i + 1
		}
		i++
	}
	return result + sign*num, i
}
func calculate(s string) int {
	type frame struct{ result, sign int }
	result, num, sign := 0, 0, 1
	var stack []frame
	for i := 0; i < len(s); i++ {
		c := s[i]
		switch {
		case c >= '0' && c <= '9':
			num = num*10 + int(c-'0')
		case c == '+':
			result += sign * num
			num, sign = 0, 1
		case c == '-':
			result += sign * num
			num, sign = 0, -1
		case c == '(':
			stack = append(stack, frame{result, sign})
			result, sign = 0, 1
		case c == ')':
			result += sign * num
			num = 0
			outer := stack[len(stack)-1]
			stack = stack[:len(stack)-1]
			result = outer.result + outer.sign*result
		}
	}
	return result + sign*num
}
func calculateRecursive(_ s: String) -> Int {
    let bytes = Array(s.utf8)
    var i = 0
    return parseExpr(bytes, &i)
}

// Evaluate bytes from index i until ")" or the end; leaves i just past
// what was read.
private func parseExpr(_ bytes: [UInt8], _ i: inout Int) -> Int {
    let zero = UInt8(ascii: "0")
    let nine = UInt8(ascii: "9")
    var result = 0
    var num = 0
    var sign = 1
    while i < bytes.count {
        let c = bytes[i]
        if c >= zero && c <= nine {
            num = num * 10 + Int(c - zero)
        } else if c == UInt8(ascii: "+") {
            result += sign * num
            num = 0
            sign = 1
        } else if c == UInt8(ascii: "-") {
            result += sign * num
            num = 0
            sign = -1
        } else if c == UInt8(ascii: "(") {
            i += 1
            result += sign * parseExpr(bytes, &i)
            num = 0
            sign = 1
            continue  // i is already past the matching ")"
        } else if c == UInt8(ascii: ")") {
            i += 1
            return result + sign * num
        }
        i += 1
    }
    return result + sign * num
}
func calculate(_ s: String) -> Int {
    var result = 0
    var num = 0
    var sign = 1
    var stack: [(result: Int, sign: Int)] = []  // one frame per open paren
    for ch in s {
        if let digit = ch.wholeNumberValue {
            num = num * 10 + digit
        } else if ch == "+" {
            result += sign * num
            num = 0
            sign = 1
        } else if ch == "-" {
            result += sign * num
            num = 0
            sign = -1
        } else if ch == "(" {
            stack.append((result, sign))
            result = 0
            sign = 1
        } else if ch == ")" {
            result += sign * num
            num = 0
            let outer = stack.removeLast()
            result = outer.result + outer.sign * result
        }
    }
    return result + sign * num
}
Recommended Approach 1 of 3 · Recursive descent on parenthesesO(n) time · O(n) recursion depth space

Linked List

19. Remove Duplicates from Sorted List II

Medium · LC 82

Given a sorted linked list, delete every node whose value appears more than once, keeping only the values that occur exactly once. Anchor a dummy node before the head and track the last node known to survive; when the run in front of it spans more than one node of the same value, unlink the entire run, otherwise advance onto the unique node. The dummy head is the key move, turning duplicates at the head into a non-special case, and the pitfall is advancing the survivor pointer before confirming the run ahead really has length one.

Count every value, then stitch together the nodes seen exactly once. Ignores sortedness entirely (it would work on an unsorted list too), which is exactly why it pays for a hash map the problem never needs.

Uses sortedness: if the head starts a duplicate run, drop the whole run and recurse on the remainder; otherwise keep the head. The hash map is gone, but a long list still burns O(n) recursion stack.

`prev` is the last node known to survive. If the run in front of it is longer than one node, unlink the whole run; otherwise advance. The dummy head makes duplicates at the head a non-special case, and the stack and hash map of the earlier rungs both disappear.

Count every value, then stitch together the nodes seen exactly once. Ignores sortedness entirely (it would work unsorted too), which is exactly why it pays for a hash map the problem never needs.

`prev` is the last surviving node; if the run in front of it has length > 1, unlink the whole run, otherwise advance. The dummy head makes duplicates at the head a non-special case, and the hash map of the counting rung disappears.

Count every value, then rebuild the chain from the nodes seen exactly once. Ignores sortedness entirely (it would work unsorted too), which is exactly why it pays for a hash map the problem never needs.

Pop nodes off the input; if the next node shares the current value, drop the whole run, otherwise append the node to the result chain. One pass, no hash map — the counting rung's extra memory is gone.

Count every value, then stitch together the nodes seen exactly once. Ignores sortedness entirely (it would work unsorted too), which is exactly why it pays for a map the problem never needs.

`prev` is the last surviving node; if the run in front of it has length > 1, unlink the whole run, otherwise advance. The dummy head makes duplicates at the head a non-special case, and the map of the counting rung disappears.

Count every value, then stitch together the nodes seen exactly once. Ignores sortedness entirely (it would work unsorted too), which is exactly why it pays for a hash map the problem never needs.

prev is the last surviving node; if the run in front of it has length > 1, unlink the whole run, otherwise advance. The dummy head makes duplicates at the head a non-special case, and the hash map of the counting rung disappears.

Count every value, then stitch together the nodes seen exactly once. Ignores sortedness entirely (it would work unsorted too), which is exactly why it pays for a dictionary the problem never needs.

`prev` is the last surviving node; if the run in front of it has length > 1, unlink the whole run, otherwise advance. The dummy head makes duplicates at the head a non-special case, and the dictionary of the counting rung disappears.

def deleteDuplicates_counting(self, head: Optional[ListNode]) -> Optional[ListNode]:
    counts = {}
    node = head
    while node:
        counts[node.val] = counts.get(node.val, 0) + 1
        node = node.next

    dummy = tail = ListNode()
    node = head
    while node:
        if counts[node.val] == 1:
            tail.next = node
            tail = node
        node = node.next
    tail.next = None
    return dummy.next
def deleteDuplicates_recursion(self, head: Optional[ListNode]) -> Optional[ListNode]:
    if not head or not head.next:
        return head
    if head.val == head.next.val:
        run_val = head.val
        while head and head.val == run_val:
            head = head.next
        return self.deleteDuplicates_recursion(head)
    head.next = self.deleteDuplicates_recursion(head.next)
    return head
def deleteDuplicates(self, head: Optional[ListNode]) -> Optional[ListNode]:
    dummy = ListNode(0, head)
    prev = dummy
    while prev.next:
        cur = prev.next
        if cur.next and cur.next.val == cur.val:
            run_val = cur.val
            while prev.next and prev.next.val == run_val:
                prev.next = prev.next.next   # unlink every copy
        else:
            prev = cur                       # unique value, keep it
    return dummy.next
ListNode* deleteDuplicatesCounting(ListNode* head) {
    std::unordered_map<int, int> counts;
    for (ListNode* node = head; node; node = node->next) ++counts[node->val];

    ListNode dummy;
    ListNode* tail = &dummy;
    for (ListNode* node = head; node; node = node->next) {
        if (counts[node->val] == 1) {
            tail->next = node;
            tail = node;
        }
    }
    tail->next = nullptr;
    return dummy.next;
}
ListNode* deleteDuplicates(ListNode* head) {
    ListNode dummy(0, head);
    ListNode* prev = &dummy;
    while (prev->next) {
        ListNode* cur = prev->next;
        if (cur->next && cur->next->val == cur->val) {
            int runVal = cur->val;
            while (prev->next && prev->next->val == runVal)
                prev->next = prev->next->next;  // unlink every copy
        } else {
            prev = cur;                         // unique value, keep it
        }
    }
    return dummy.next;
}
pub fn delete_duplicates_counting(head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
    use std::collections::HashMap;

    let mut counts: HashMap<i32, u32> = HashMap::new();
    let mut p = head.as_ref();
    while let Some(node) = p {
        *counts.entry(node.val).or_insert(0) += 1;
        p = node.next.as_ref();
    }

    let mut dummy = Box::new(ListNode::new(0));
    let mut tail = &mut dummy;
    let mut cur = head;
    while let Some(mut node) = cur {
        cur = node.next.take();
        if counts[&node.val] == 1 {
            tail.next = Some(node);
            tail = tail.next.as_mut().unwrap();
        }
    }
    dummy.next
}
pub fn delete_duplicates(head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
    let mut dummy = Box::new(ListNode::new(0));
    let mut tail = &mut dummy;
    let mut cur = head;

    while let Some(mut node) = cur {
        cur = node.next.take();
        if cur.as_ref().is_some_and(|n| n.val == node.val) {
            // drop the whole run of this value
            let run_val = node.val;
            while cur.as_ref().is_some_and(|n| n.val == run_val) {
                let mut skipped = cur.unwrap();
                cur = skipped.next.take();
            }
        } else {
            tail.next = Some(node); // unique value, keep it
            tail = tail.next.as_mut().unwrap();
        }
    }
    dummy.next
}
function deleteDuplicatesCounting(head: ListNode | null): ListNode | null {
    const counts = new Map<number, number>();
    for (let node = head; node !== null; node = node.next) {
        counts.set(node.val, (counts.get(node.val) ?? 0) + 1);
    }

    const dummy = new ListNode();
    let tail = dummy;
    for (let node = head; node !== null; node = node.next) {
        if (counts.get(node.val) === 1) {
            tail.next = node;
            tail = node;
        }
    }
    tail.next = null;
    return dummy.next;
}
function deleteDuplicates(head: ListNode | null): ListNode | null {
    const dummy = new ListNode(0, head);
    let prev = dummy;
    while (prev.next !== null) {
        const cur = prev.next;
        if (cur.next !== null && cur.next.val === cur.val) {
            const runVal = cur.val;
            while (prev.next !== null && prev.next.val === runVal) {
                prev.next = prev.next.next; // unlink every copy
            }
        } else {
            prev = cur;                     // unique value, keep it
        }
    }
    return dummy.next;
}
func deleteDuplicatesCounting(head *ListNode) *ListNode {
	counts := map[int]int{}
	for node := head; node != nil; node = node.Next {
		counts[node.Val]++
	}

	dummy := &ListNode{}
	tail := dummy
	for node := head; node != nil; node = node.Next {
		if counts[node.Val] == 1 {
			tail.Next = node
			tail = node
		}
	}
	tail.Next = nil
	return dummy.Next
}
func deleteDuplicates(head *ListNode) *ListNode {
	dummy := &ListNode{Next: head}
	prev := dummy
	for prev.Next != nil {
		cur := prev.Next
		if cur.Next != nil && cur.Next.Val == cur.Val {
			runVal := cur.Val
			for prev.Next != nil && prev.Next.Val == runVal {
				prev.Next = prev.Next.Next // unlink every copy
			}
		} else {
			prev = cur // unique value, keep it
		}
	}
	return dummy.Next
}
func deleteDuplicatesCounting(_ head: ListNode?) -> ListNode? {
    var counts: [Int: Int] = [:]
    var node = head
    while let n = node {
        counts[n.val, default: 0] += 1
        node = n.next
    }

    let dummy = ListNode()
    var tail = dummy
    node = head
    while let n = node {
        if counts[n.val] == 1 {
            tail.next = n
            tail = n
        }
        node = n.next
    }
    tail.next = nil
    return dummy.next
}
func deleteDuplicates(_ head: ListNode?) -> ListNode? {
    let dummy = ListNode(0, head)
    var prev = dummy
    while let cur = prev.next {
        if let next = cur.next, next.val == cur.val {
            let runVal = cur.val
            while let node = prev.next, node.val == runVal {
                prev.next = node.next  // unlink every copy
            }
        } else {
            prev = cur                 // unique value, keep it
        }
    }
    return dummy.next
}
Recommended Approach 1 of 3 · Two-pass countingO(n) time · O(n) space

20. Rotate List

Medium · LC 61

Given a linked list and a count k, rotate the list to the right by k places. Walk to the tail while counting the length, link the tail back to the head to close the list into a ring, then step forward from the head to the node that becomes the new tail and cut the ring just after it. The pitfall is that k can dwarf the length, so it must first be reduced modulo the node count, and a remainder of zero means the list is returned unchanged rather than cut.

Collect the nodes, compute the rotated order by slicing, relink. The simplest possible index arithmetic, at the price of an O(n) auxiliary array the pointer-only rungs below do without.

Drops the array: after reducing k, send a lead pointer k nodes ahead; when it reaches the tail, the lag pointer sits on the new tail. Constant space, but it walks the list three times in total.

Walk to the tail (learning n), link tail -> head to form a ring, then cut the ring after node n - k%n. Same bounds as the two-pointer walk but only one full pass plus a partial one — and no juggling of two moving pointers.

Each step walks to the second-to-last node and moves the tail to the front. Quadratic once k % n grows with n — fine at these test sizes, times out against LeetCode's n = 500, k = 2 * 10^9 only if you forget the k %= n reduction; still wasteful even with it.

Walk to the tail (learning n), link tail -> head to form a ring, then cut the ring after node n - k%n. All k steps collapse into a single cut, so the repeated tail-walks of Approach 1 disappear.

Each step detaches the tail node and pushes it onto the front. Quadratic once k % n grows with n — fine at these test sizes, wasteful on LeetCode's n = 500 even with the k %= n reduction.

Count the length, reduce k mod n, then split the list after n - k nodes and swap the two halves (take the second half, walk to its tail, reattach the first half). All k steps collapse into a single cut, so the repeated tail-walks of Approach 1 disappear.

Each step walks to the second-to-last node and moves the tail to the front. Quadratic once k % n grows with n — fine at these test sizes, wasteful on LeetCode's n = 500 even with the k %= n reduction.

Walk to the tail (learning n), link tail -> head to form a ring, then cut the ring after node n - k%n. All k steps collapse into a single cut, so the repeated tail-walks of Approach 1 disappear.

Each step walks to the second-to-last node and moves the tail to the front. Quadratic once k % n grows with n — fine at these test sizes, wasteful on LeetCode's n = 500 even with the k %= n reduction.

Walk to the tail (learning n), link tail -> head to form a ring, then cut the ring after node n - k%n. All k steps collapse into a single cut, so the repeated tail-walks of Approach 1 disappear.

Each step walks to the second-to-last node and moves the tail to the front. Quadratic once k % n grows with n — fine at these test sizes, wasteful on LeetCode's n = 500 even with the k % n cut.

Walk to the tail (learning n), link tail -> head to form a ring, then cut the ring after node n - k%n. All k steps collapse into a single cut, so the repeated tail-walks of Approach 1 disappear.

def rotateRight_array(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
    nodes = []
    node = head
    while node:
        nodes.append(node)
        node = node.next
    if not nodes:
        return None

    n = len(nodes)
    k %= n
    if k == 0:
        return head

    order = nodes[n - k:] + nodes[:n - k]
    for a, b in zip(order, order[1:]):
        a.next = b
    order[-1].next = None
    return order[0]
def rotateRight_two_pointers(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
    if not head:
        return None

    n, node = 0, head
    while node:
        n += 1
        node = node.next

    k %= n
    if k == 0:
        return head

    lead = head
    for _ in range(k):
        lead = lead.next
    lag = head
    while lead.next:             # advance both until lead hits the tail
        lead = lead.next
        lag = lag.next

    new_head = lag.next
    lag.next = None
    lead.next = head             # old tail hooks back onto old head
    return new_head
def rotateRight(self, head: Optional[ListNode], k: int) -> Optional[ListNode]:
    if not head or not head.next:
        return head

    n, tail = 1, head
    while tail.next:
        tail = tail.next
        n += 1

    k %= n
    if k == 0:
        return head

    tail.next = head             # close into a ring
    new_tail = head
    for _ in range(n - k - 1):   # new tail is the (n-k)-th node
        new_tail = new_tail.next
    new_head = new_tail.next
    new_tail.next = None         # cut the ring
    return new_head
ListNode* rotateRightOneStep(ListNode* head, int k) {
    if (!head || !head->next) return head;

    int n = 1;
    for (ListNode* p = head; p->next; p = p->next) ++n;

    for (int step = 0; step < k % n; ++step) {
        ListNode* p = head;
        while (p->next->next) p = p->next;  // second-to-last node
        ListNode* tail = p->next;
        p->next = nullptr;
        tail->next = head;                  // tail becomes the new head
        head = tail;
    }
    return head;
}
ListNode* rotateRight(ListNode* head, int k) {
    if (!head || !head->next) return head;

    int n = 1;
    ListNode* tail = head;
    while (tail->next) {
        tail = tail->next;
        ++n;
    }

    k %= n;
    if (k == 0) return head;

    tail->next = head;                 // close into a ring
    ListNode* newTail = head;
    for (int i = 0; i < n - k - 1; ++i)  // new tail is the (n-k)-th node
        newTail = newTail->next;
    ListNode* newHead = newTail->next;
    newTail->next = nullptr;           // cut the ring
    return newHead;
}
pub fn rotate_right_one_step(head: Option<Box<ListNode>>, k: i32) -> Option<Box<ListNode>> {
    if head.is_none() {
        return head;
    }

    let mut n = 0usize;
    let mut p = head.as_ref();
    while let Some(node) = p {
        n += 1;
        p = node.next.as_ref();
    }

    let mut head = head;
    for _ in 0..(k as usize) % n {
        // Walk to the second-to-last node and take the tail off it.
        let mut cut = head.as_mut().unwrap();
        while cut.next.as_ref().unwrap().next.is_some() {
            cut = cut.next.as_mut().unwrap();
        }
        let mut tail = cut.next.take().unwrap();
        tail.next = head; // tail becomes the new head
        head = Some(tail);
    }
    head
}
pub fn rotate_right(head: Option<Box<ListNode>>, k: i32) -> Option<Box<ListNode>> {
    if head.is_none() {
        return head;
    }

    let mut n = 0usize;
    let mut p = head.as_ref();
    while let Some(node) = p {
        n += 1;
        p = node.next.as_ref();
    }

    let k = (k as usize) % n;
    if k == 0 {
        return head;
    }

    // Cut after the (n-k)-th node.
    let mut head = head;
    let mut cut = head.as_mut().unwrap();
    for _ in 0..n - k - 1 {
        cut = cut.next.as_mut().unwrap();
    }
    let mut new_head = cut.next.take();

    // Walk to the tail of the second half and reattach the first.
    let mut tail = new_head.as_mut().unwrap();
    while tail.next.is_some() {
        tail = tail.next.as_mut().unwrap();
    }
    tail.next = head;
    new_head
}
function rotateRightOneStep(head: ListNode | null, k: number): ListNode | null {
    if (head === null || head.next === null) return head;

    let n = 1;
    for (let p = head; p.next !== null; p = p.next) n++;

    for (let step = 0; step < k % n; step++) {
        let prev: ListNode = head;
        while (prev.next!.next !== null) prev = prev.next!; // second-to-last
        const tail: ListNode = prev.next!;
        prev.next = null;
        tail.next = head; // tail becomes the new head
        head = tail;
    }
    return head;
}
function rotateRight(head: ListNode | null, k: number): ListNode | null {
    if (head === null || head.next === null) return head;

    let n = 1;
    let tail = head;
    while (tail.next !== null) {
        tail = tail.next;
        n++;
    }

    k %= n;
    if (k === 0) return head;

    tail.next = head;                    // close into a ring
    let newTail = head;
    for (let i = 0; i < n - k - 1; i++) { // new tail is the (n-k)-th node
        newTail = newTail.next!;
    }
    const newHead = newTail.next;
    newTail.next = null;                 // cut the ring
    return newHead;
}
func rotateRightOneStep(head *ListNode, k int) *ListNode {
	if head == nil || head.Next == nil {
		return head
	}

	n := 1
	for p := head; p.Next != nil; p = p.Next {
		n++
	}

	for step := 0; step < k%n; step++ {
		p := head
		for p.Next.Next != nil { // second-to-last node
			p = p.Next
		}
		tail := p.Next
		p.Next = nil
		tail.Next = head // tail becomes the new head
		head = tail
	}
	return head
}
func rotateRight(head *ListNode, k int) *ListNode {
	if head == nil || head.Next == nil {
		return head
	}

	n, tail := 1, head
	for tail.Next != nil {
		tail = tail.Next
		n++
	}

	k %= n
	if k == 0 {
		return head
	}

	tail.Next = head // close into a ring
	newTail := head
	for i := 0; i < n-k-1; i++ { // new tail is the (n-k)-th node
		newTail = newTail.Next
	}
	newHead := newTail.Next
	newTail.Next = nil // cut the ring
	return newHead
}
func rotateRightOneStep(_ head: ListNode?, _ k: Int) -> ListNode? {
    guard var head = head, head.next != nil else { return head }

    var n = 1
    var p = head
    while let next = p.next {
        p = next
        n += 1
    }

    for _ in 0..<(k % n) {
        var prev = head
        while prev.next!.next != nil { // second-to-last node
            prev = prev.next!
        }
        let tail = prev.next!
        prev.next = nil
        tail.next = head // tail becomes the new head
        head = tail
    }
    return head
}
func rotateRight(_ head: ListNode?, _ k: Int) -> ListNode? {
    guard let head = head, head.next != nil else { return head }

    var n = 1
    var tail = head
    while let next = tail.next {
        tail = next
        n += 1
    }

    let k = k % n
    if k == 0 { return head }

    tail.next = head            // close into a ring
    var newTail = head
    for _ in 0..<(n - k - 1) {  // new tail is the (n-k)-th node
        newTail = newTail.next!
    }
    let newHead = newTail.next
    newTail.next = nil          // cut the ring
    return newHead
}
Recommended Approach 1 of 3 · Array of nodesO(n) time · O(n) space

21. Partition List

Medium · LC 86

Given a linked list and a value x, rearrange it so every node less than x comes before every node greater than or equal to x, preserving relative order inside each group. Thread each node in one pass onto one of two dummy-headed chains, one for the smaller values and one for the rest, then join them by pointing the first chain's tail at the second chain's head. The step people forget is terminating the second chain: without setting its tail's next pointer to null, the old tail can still point into the middle of the result and form a cycle.

Collect values, order them as [< x] + [>= x], write them back into the existing nodes. Cheats a little (no relinking) but is a handy cross-check and legal when node identity doesn't matter.

Keep `ins`, the tail of the "< x" region at the front. Whenever the scan finds a < x node past that region, unlink it and splice it in right after `ins`. Real relinking in constant space — no value array — but the four-pointer splice is easy to get subtly wrong.

Thread every node onto a "less" chain or a "greater-or-equal" chain in one pass, then join them. Same bounds as the splice but with one obvious invariant instead of pointer surgery. Terminating the second chain is the step people forget — without it the old tail can point into the middle of the result and form a cycle.

Collect values, order them as [< x] + [>= x], write them back into the existing nodes. Cheats a little (no relinking) but is a handy cross-check and legal when node identity doesn't matter.

Thread each node onto a "less" or "greater-or-equal" chain in one pass, terminate the second chain, then join them. Real relinking with no value array. Terminating the second chain is the step people forget — without it the old tail can form a cycle.

Collect values, order them as [< x] + [>= x], write them back into the existing nodes. Cheats a little (no relinking) but is a handy cross-check and legal when node identity doesn't matter.

Pop each node off the input and push it onto a "less" or a "greater-or-equal" chain, then join them. Real relinking with no value buffer; taking each node's `next` terminates the chains as a side effect, and relative order inside each group is preserved.

Collect values, order them as [< x] + [>= x], write them back into the existing nodes. Cheats a little (no relinking) but is a handy cross-check and legal when node identity doesn't matter.

Thread each node onto a "less" or "greater-or-equal" chain in one pass, terminate the second chain, then join them. Real relinking with no value array. Terminating the second chain is the step people forget — without it the old tail can form a cycle.

Collect values, order them as [< x] + [>= x], write them back into the existing nodes. Cheats a little (no relinking) but is a handy cross-check and legal when node identity doesn't matter.

Thread each node onto a "less" or "greater-or-equal" chain in one pass, terminate the second chain, then join them. Real relinking with no value slice. Terminating the second chain is the step people forget — without it the old tail can form a cycle.

Collect values, order them as [< x] + [>= x], write them back into the existing nodes. Cheats a little (no relinking) but is a handy cross-check and legal when node identity doesn't matter.

Thread each node onto a "less" or "greater-or-equal" chain in one pass, terminate the second chain, then join them. Real relinking with no value array. Terminating the second chain is the step people forget — without it the old tail can form a cycle.

def partition_values(self, head: Optional[ListNode], x: int) -> Optional[ListNode]:
    vals = []
    node = head
    while node:
        vals.append(node.val)
        node = node.next

    ordered = [v for v in vals if v < x] + [v for v in vals if v >= x]

    node = head
    for v in ordered:
        node.val = v
        node = node.next
    return head
def partition_in_place(self, head: Optional[ListNode], x: int) -> Optional[ListNode]:
    dummy = ListNode(0, head)

    ins = dummy                          # end of the leading < x run
    while ins.next and ins.next.val < x:
        ins = ins.next

    prev = ins
    while prev.next:
        cur = prev.next
        if cur.val < x:                  # splice cur up behind ins
            prev.next = cur.next
            cur.next = ins.next
            ins.next = cur
            ins = cur
        else:
            prev = cur
    return dummy.next
def partition(self, head: Optional[ListNode], x: int) -> Optional[ListNode]:
    less = less_tail = ListNode()
    geq = geq_tail = ListNode()

    node = head
    while node:
        if node.val < x:
            less_tail.next = node
            less_tail = node
        else:
            geq_tail.next = node
            geq_tail = node
        node = node.next

    geq_tail.next = None        # terminate the second chain
    less_tail.next = geq.next   # join: [< x] then [>= x]
    return less.next
ListNode* partitionValues(ListNode* head, int x) {
    std::vector<int> ordered;
    for (ListNode* node = head; node; node = node->next)
        if (node->val < x) ordered.push_back(node->val);
    for (ListNode* node = head; node; node = node->next)
        if (node->val >= x) ordered.push_back(node->val);

    size_t i = 0;
    for (ListNode* node = head; node; node = node->next)
        node->val = ordered[i++];
    return head;
}
ListNode* partition(ListNode* head, int x) {
    ListNode less, geq;
    ListNode* lessTail = &less;
    ListNode* geqTail = &geq;

    for (ListNode* node = head; node; node = node->next) {
        if (node->val < x) {
            lessTail->next = node;
            lessTail = node;
        } else {
            geqTail->next = node;
            geqTail = node;
        }
    }

    geqTail->next = nullptr;    // terminate the second chain
    lessTail->next = geq.next;  // join: [< x] then [>= x]
    return less.next;
}
pub fn partition_values(head: Option<Box<ListNode>>, x: i32) -> Option<Box<ListNode>> {
    let mut vals = Vec::new();
    let mut p = head.as_ref();
    while let Some(node) = p {
        vals.push(node.val);
        p = node.next.as_ref();
    }

    let ordered: Vec<i32> = vals
        .iter()
        .copied()
        .filter(|&v| v < x)
        .chain(vals.iter().copied().filter(|&v| v >= x))
        .collect();

    let mut head = head;
    let mut p = head.as_mut();
    for v in ordered {
        let node = p.unwrap();
        node.val = v;
        p = node.next.as_mut();
    }
    head
}
pub fn partition(head: Option<Box<ListNode>>, x: i32) -> Option<Box<ListNode>> {
    let mut less = Box::new(ListNode::new(0));
    let mut geq = Box::new(ListNode::new(0));
    let mut less_tail = &mut less;
    let mut geq_tail = &mut geq;

    let mut cur = head;
    while let Some(mut node) = cur {
        cur = node.next.take();
        if node.val < x {
            less_tail.next = Some(node);
            less_tail = less_tail.next.as_mut().unwrap();
        } else {
            geq_tail.next = Some(node);
            geq_tail = geq_tail.next.as_mut().unwrap();
        }
    }

    less_tail.next = geq.next; // join: [< x] then [>= x]
    less.next
}
function partitionValues(head: ListNode | null, x: number): ListNode | null {
    const vals: number[] = [];
    for (let node = head; node !== null; node = node.next) vals.push(node.val);

    const ordered = vals.filter((v) => v < x).concat(vals.filter((v) => v >= x));

    let i = 0;
    for (let node = head; node !== null; node = node.next) node.val = ordered[i++];
    return head;
}
function partition(head: ListNode | null, x: number): ListNode | null {
    const less = new ListNode();
    const geq = new ListNode();
    let lessTail = less;
    let geqTail = geq;

    for (let node = head; node !== null; node = node.next) {
        if (node.val < x) {
            lessTail.next = node;
            lessTail = node;
        } else {
            geqTail.next = node;
            geqTail = node;
        }
    }

    geqTail.next = null;       // terminate the second chain
    lessTail.next = geq.next;  // join: [< x] then [>= x]
    return less.next;
}
func partitionValues(head *ListNode, x int) *ListNode {
	ordered := []int{}
	for node := head; node != nil; node = node.Next {
		if node.Val < x {
			ordered = append(ordered, node.Val)
		}
	}
	for node := head; node != nil; node = node.Next {
		if node.Val >= x {
			ordered = append(ordered, node.Val)
		}
	}

	i := 0
	for node := head; node != nil; node = node.Next {
		node.Val = ordered[i]
		i++
	}
	return head
}
func partition(head *ListNode, x int) *ListNode {
	less, geq := &ListNode{}, &ListNode{}
	lessTail, geqTail := less, geq

	for node := head; node != nil; node = node.Next {
		if node.Val < x {
			lessTail.Next = node
			lessTail = node
		} else {
			geqTail.Next = node
			geqTail = node
		}
	}

	geqTail.Next = nil       // terminate the second chain
	lessTail.Next = geq.Next // join: [< x] then [>= x]
	return less.Next
}
func partitionValues(_ head: ListNode?, _ x: Int) -> ListNode? {
    var vals: [Int] = []
    var node = head
    while let n = node {
        vals.append(n.val)
        node = n.next
    }

    let ordered = vals.filter { $0 < x } + vals.filter { $0 >= x }

    node = head
    for v in ordered {
        node!.val = v
        node = node!.next
    }
    return head
}
func partition(_ head: ListNode?, _ x: Int) -> ListNode? {
    let less = ListNode()
    let geq = ListNode()
    var lessTail = less
    var geqTail = geq

    var node = head
    while let cur = node {
        if cur.val < x {
            lessTail.next = cur
            lessTail = cur
        } else {
            geqTail.next = cur
            geqTail = cur
        }
        node = cur.next
    }

    geqTail.next = nil       // terminate the second chain
    lessTail.next = geq.next // join: [< x] then [>= x]
    return less.next
}
Recommended Approach 1 of 3 · Rewrite the valuesO(n) time · O(n) space

Binary Tree General

22. Symmetric Tree

Easy · LC 101

Given a binary tree, decide whether it is a mirror image of itself around its center. Recurse on pairs of subtrees with a helper that requires the two roots to match, the first subtree's left child to mirror the second's right, and vice versa. The trick is comparing two subtrees at once with that crossed recursion, handling the null cases first so that two missing nodes match while exactly one missing node fails.

Serialize each level with None placeholders for missing children and require every level to read the same reversed. Correct, but it buffers whole levels and the placeholder bookkeeping obscures the real pairing between mirror nodes.

Compares exactly the two nodes that must match — no level buffers or placeholders, and it bails at the first mismatch. Handy when the tree is deep enough to threaten the recursion limit.

Two subtrees mirror each other iff their roots match, the first's left mirrors the second's right, and vice versa. Same pairing as the queue, but the call stack does the bookkeeping.

Serialize each level with null placeholders for missing children and require every level to read the same reversed. Correct, but it buffers whole levels and the placeholder bookkeeping obscures the real pairing between mirror nodes.

Two subtrees mirror each other iff their roots match, the first's left mirrors the second's right, and vice versa. Compares exactly the two nodes that must match — no level buffers or placeholders — and bails at the first mismatch.

Serialize each level with None placeholders for missing children and require every level to read the same reversed. Correct, but it buffers whole levels and the placeholder bookkeeping obscures the real pairing between mirror nodes.

Two subtrees mirror each other iff their roots match, the first's left mirrors the second's right, and vice versa. Compares exactly the two nodes that must match — no level buffers or placeholders — and bails at the first mismatch.

Serialize each level with null placeholders for missing children and require every level to read the same reversed. Correct, but it buffers whole levels and the placeholder bookkeeping obscures the real pairing between mirror nodes.

Two subtrees mirror each other iff their roots match, the first's left mirrors the second's right, and vice versa. Compares exactly the two nodes that must match — no level buffers or placeholders — and bails at the first mismatch.

Serialize each level with nil placeholders for missing children and require every level to read the same reversed. Correct, but it buffers whole levels and the placeholder bookkeeping obscures the real pairing between mirror nodes.

Two subtrees mirror each other iff their roots match, the first's left mirrors the second's right, and vice versa. Compares exactly the two nodes that must match — no level buffers or placeholders — and bails at the first mismatch.

Serialize each level with nil placeholders for missing children and require every level to read the same reversed. Correct, but it buffers whole levels and the placeholder bookkeeping obscures the real pairing between mirror nodes.

Two subtrees mirror each other iff their roots match, the first's left mirrors the second's right, and vice versa. Compares exactly the two nodes that must match — no level buffers or placeholders — and bails at the first mismatch.

def isSymmetric_level_palindrome(self, root: Optional[TreeNode]) -> bool:
    if root is None:
        return True
    level = [root]
    while level:
        vals = []
        nxt = []
        for node in level:
            for child in (node.left, node.right):
                if child is not None:
                    vals.append(child.val)
                    nxt.append(child)
                else:
                    vals.append(None)
        if vals != vals[::-1]:
            return False
        level = nxt
    return True
def isSymmetric_iterative_queue(self, root: Optional[TreeNode]) -> bool:
    if root is None:
        return True
    queue = deque([(root.left, root.right)])
    while queue:
        a, b = queue.popleft()
        if a is None and b is None:
            continue
        if a is None or b is None or a.val != b.val:
            return False
        queue.append((a.left, b.right))
        queue.append((a.right, b.left))
    return True
def isSymmetric(self, root: Optional[TreeNode]) -> bool:
    def mirror(a: Optional[TreeNode], b: Optional[TreeNode]) -> bool:
        if a is None and b is None:
            return True
        if a is None or b is None or a.val != b.val:
            return False
        return mirror(a.left, b.right) and mirror(a.right, b.left)

    return root is None or mirror(root.left, root.right)
public:
    bool isSymmetricLevelPalindrome(TreeNode* root) {
        if (!root) return true;
        vector<TreeNode*> level = {root};
        while (!level.empty()) {
            vector<optional<int>> vals;
            vector<TreeNode*> next;
            for (TreeNode* node : level) {
                for (TreeNode* child : {node->left, node->right}) {
                    if (child) {
                        vals.push_back(child->val);
                        next.push_back(child);
                    } else {
                        vals.push_back(nullopt);
                    }
                }
            }
            const size_t m = vals.size();
            for (size_t l = 0; l < m / 2; ++l)
                if (vals[l] != vals[m - 1 - l]) return false;
            level = std::move(next);
        }
        return true;
    }
private:
    static bool mirror(TreeNode* a, TreeNode* b) {
        if (!a && !b) return true;
        if (!a || !b || a->val != b->val) return false;
        return mirror(a->left, b->right) && mirror(a->right, b->left);
    }

public:
    bool isSymmetric(TreeNode* root) {
        return !root || mirror(root->left, root->right);
    }
pub fn is_symmetric_level_palindrome(root: Option<Rc<RefCell<TreeNode>>>) -> bool {
    let mut level = match root {
        None => return true,
        Some(node) => vec![node],
    };
    while !level.is_empty() {
        let mut vals: Vec<Option<i32>> = Vec::new();
        let mut next = Vec::new();
        for node in &level {
            let node = node.borrow();
            for child in [&node.left, &node.right] {
                match child {
                    Some(c) => {
                        vals.push(Some(c.borrow().val));
                        next.push(Rc::clone(c));
                    }
                    None => vals.push(None),
                }
            }
        }
        let m = vals.len();
        for l in 0..m / 2 {
            if vals[l] != vals[m - 1 - l] {
                return false;
            }
        }
        level = next;
    }
    true
}
pub fn is_symmetric(root: Option<Rc<RefCell<TreeNode>>>) -> bool {
    fn mirror(a: &Option<Rc<RefCell<TreeNode>>>, b: &Option<Rc<RefCell<TreeNode>>>) -> bool {
        match (a, b) {
            (None, None) => true,
            (Some(x), Some(y)) => {
                let x = x.borrow();
                let y = y.borrow();
                x.val == y.val && mirror(&x.left, &y.right) && mirror(&x.right, &y.left)
            }
            _ => false,
        }
    }
    match root {
        None => true,
        Some(node) => {
            let node = node.borrow();
            mirror(&node.left, &node.right)
        }
    }
}
function isSymmetricLevelPalindrome(root: TreeNode | null): boolean {
  if (root === null) return true;
  let level: TreeNode[] = [root];
  while (level.length > 0) {
    const vals: (number | null)[] = [];
    const next: TreeNode[] = [];
    for (const node of level) {
      for (const child of [node.left, node.right]) {
        if (child !== null) {
          vals.push(child.val);
          next.push(child);
        } else {
          vals.push(null);
        }
      }
    }
    const m = vals.length;
    for (let l = 0; l < Math.floor(m / 2); l++) {
      if (vals[l] !== vals[m - 1 - l]) return false;
    }
    level = next;
  }
  return true;
}
function isSymmetric(root: TreeNode | null): boolean {
  const mirror = (a: TreeNode | null, b: TreeNode | null): boolean => {
    if (a === null && b === null) return true;
    if (a === null || b === null || a.val !== b.val) return false;
    return mirror(a.left, b.right) && mirror(a.right, b.left);
  };
  return root === null || mirror(root.left, root.right);
}
func isSymmetricLevelPalindrome(root *TreeNode) bool {
	if root == nil {
		return true
	}
	level := []*TreeNode{root}
	for len(level) > 0 {
		vals := []any{}
		next := []*TreeNode{}
		for _, node := range level {
			for _, child := range []*TreeNode{node.Left, node.Right} {
				if child != nil {
					vals = append(vals, child.Val)
					next = append(next, child)
				} else {
					vals = append(vals, nil)
				}
			}
		}
		for l, r := 0, len(vals)-1; l < r; l, r = l+1, r-1 {
			if vals[l] != vals[r] {
				return false
			}
		}
		level = next
	}
	return true
}
func isSymmetric(root *TreeNode) bool {
	if root == nil {
		return true
	}
	return mirror(root.Left, root.Right)
}

func mirror(a, b *TreeNode) bool {
	if a == nil && b == nil {
		return true
	}
	if a == nil || b == nil || a.Val != b.Val {
		return false
	}
	return mirror(a.Left, b.Right) && mirror(a.Right, b.Left)
}
func isSymmetricLevelPalindrome(_ root: TreeNode?) -> Bool {
    guard let root = root else { return true }
    var level: [TreeNode] = [root]
    while !level.isEmpty {
        var vals: [Int?] = []
        var next: [TreeNode] = []
        for node in level {
            for child in [node.left, node.right] {
                if let child = child {
                    vals.append(child.val)
                    next.append(child)
                } else {
                    vals.append(nil)
                }
            }
        }
        let m = vals.count
        for l in 0..<(m / 2) where vals[l] != vals[m - 1 - l] {
            return false
        }
        level = next
    }
    return true
}
func isSymmetric(_ root: TreeNode?) -> Bool {
    func mirror(_ a: TreeNode?, _ b: TreeNode?) -> Bool {
        if a == nil && b == nil { return true }
        guard let a = a, let b = b, a.val == b.val else { return false }
        return mirror(a.left, b.right) && mirror(a.right, b.left)
    }
    guard let root = root else { return true }
    return mirror(root.left, root.right)
}
Recommended Approach 1 of 3 · Level serialization palindromeO(n) time · O(n) space

23. Construct Binary Tree from Inorder and Postorder Traversal

Medium · LC 106

Given the inorder and postorder traversals of a binary tree with unique values, rebuild the original tree. Consume postorder from the back, where the last unused entry is always the current subtree's root, and use a hash map from value to inorder index to split the inorder range into left and right halves in constant time. The pitfall is the recursion order: postorder ends with left, right, root, so walking it backwards yields root then right then left, meaning the right subtree must be built before the left.

Find the root in inorder with a linear .index() scan, then recurse on freshly copied slices of both lists. Quadratic on skewed trees: fine at these test sizes, sluggish at LeetCode's 3000-node bound, and the slice copies burn memory for no benefit.

Walk postorder backwards (root, right, left), pushing as we go right. When the stack top equals the current inorder tail, that subtree's rightward run is done: pop back to the node whose LEFT child comes next. Linear time and leaner than a hashmap, but the invariant is subtle and easy to break under pressure.

The last unused postorder entry is always the current subtree's root; the hashmap finds its inorder position in O(1), splitting inorder into left/right index ranges — no slices, no scanning. Build the RIGHT subtree first — postorder ends ...left, right, root, so walking the array backwards yields root, right, left.

Scan the inorder range for the root value on every call. Quadratic when the tree is a chain — fine at these test sizes, sluggish at LeetCode's 3000-node bound — but it needs no extra bookkeeping.

The hashmap makes each split O(1): the last unused postorder entry is always the current subtree's root, and its inorder position divides the range. Build the RIGHT subtree first — postorder ends ...left, right, root, so walking backwards yields root, right, left.

Find the root (postorder's last element) in inorder with a linear scan, then recurse on the matching subslices. Quadratic when the tree is a chain — fine at these test sizes, sluggish at LeetCode's 3000-node bound — but it needs no extra bookkeeping.

The hashmap makes each split O(1): the last unused postorder entry is always the current subtree's root, and its inorder position divides the range. Build the RIGHT subtree first — postorder ends ...left, right, root, so walking backwards yields root, right, left.

Scan the inorder range for the root value on every call. Quadratic when the tree is a chain — fine at these test sizes, sluggish at LeetCode's 3000-node bound — but it needs no extra bookkeeping.

The hashmap makes each split O(1): the last unused postorder entry is always the current subtree's root, and its inorder position divides the range. Build the RIGHT subtree first — postorder ends ...left, right, root, so walking backwards yields root, right, left.

Scan the inorder range for the root value on every call. Quadratic when the tree is a chain — fine at these test sizes, sluggish at LeetCode's 3000-node bound — but it needs no extra bookkeeping.

The hashmap makes each split O(1): the last unused postorder entry is always the current subtree's root, and its inorder position divides the range. Build the RIGHT subtree first — postorder ends ...left, right, root, so walking backwards yields root, right, left.

Scan the inorder range for the root value on every call. Quadratic when the tree is a chain — fine at these test sizes, sluggish at LeetCode's 3000-node bound — but it needs no extra bookkeeping.

The hashmap makes each split O(1): the last unused postorder entry is always the current subtree's root, and its inorder position divides the range. Build the RIGHT subtree first — postorder ends ...left, right, root, so walking backwards yields root, right, left.

def buildTree_slicing(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
    if not inorder:
        return None
    val = postorder[-1]
    mid = inorder.index(val)
    node = TreeNode(val)
    node.left = self.buildTree_slicing(inorder[:mid], postorder[:mid])
    node.right = self.buildTree_slicing(inorder[mid + 1:], postorder[mid:-1])
    return node
def buildTree_iterative(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
    if not postorder:
        return None
    root = TreeNode(postorder[-1])
    stack = [root]
    in_idx = len(inorder) - 1
    for i in range(len(postorder) - 2, -1, -1):
        val = postorder[i]
        node = stack[-1]
        if node.val != inorder[in_idx]:
            node.right = TreeNode(val)
            stack.append(node.right)
        else:
            while stack and stack[-1].val == inorder[in_idx]:
                node = stack.pop()
                in_idx -= 1
            node.left = TreeNode(val)
            stack.append(node.left)
    return root
def buildTree(self, inorder: List[int], postorder: List[int]) -> Optional[TreeNode]:
    idx = {val: i for i, val in enumerate(inorder)}
    post = len(postorder) - 1

    def build(lo: int, hi: int) -> Optional[TreeNode]:
        nonlocal post
        if lo > hi:
            return None
        val = postorder[post]
        post -= 1
        node = TreeNode(val)
        mid = idx[val]
        node.right = build(mid + 1, hi)   # right before left!
        node.left = build(lo, mid - 1)
        return node

    return build(0, len(inorder) - 1)
    TreeNode* buildRangeLinear(const vector<int>& inorder, int ilo, int ihi,
                               const vector<int>& postorder, int plo, int phi) {
        if (ilo > ihi) return nullptr;
        int val = postorder[phi];
        int mid = ilo;
        while (inorder[mid] != val) ++mid;  // the linear scan
        int leftSize = mid - ilo;
        TreeNode* node = new TreeNode(val);
        node->left = buildRangeLinear(inorder, ilo, mid - 1,
                                      postorder, plo, plo + leftSize - 1);
        node->right = buildRangeLinear(inorder, mid + 1, ihi,
                                       postorder, plo + leftSize, phi - 1);
        return node;
    }

public:
    TreeNode* buildTreeLinearSearch(vector<int>& inorder, vector<int>& postorder) {
        return buildRangeLinear(inorder, 0, (int)inorder.size() - 1,
                                postorder, 0, (int)postorder.size() - 1);
    }
private:
    unordered_map<int, int> idx;  // value -> inorder position (values unique)
    int post = 0;                 // next unused postorder index, from the back

    TreeNode* build(const vector<int>& postorder, int lo, int hi) {
        if (lo > hi) return nullptr;
        int val = postorder[post--];
        TreeNode* node = new TreeNode(val);
        int mid = idx[val];
        node->right = build(postorder, mid + 1, hi);  // right before left!
        node->left = build(postorder, lo, mid - 1);
        return node;
    }

public:
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        idx.clear();
        for (int i = 0; i < (int)inorder.size(); ++i) idx[inorder[i]] = i;
        post = (int)postorder.size() - 1;
        return build(postorder, 0, (int)inorder.size() - 1);
    }
pub fn build_tree_linear_search(
    inorder: Vec<i32>,
    postorder: Vec<i32>,
) -> Option<Rc<RefCell<TreeNode>>> {
    fn build(inorder: &[i32], postorder: &[i32]) -> Option<Rc<RefCell<TreeNode>>> {
        let n = postorder.len();
        if n == 0 {
            return None;
        }
        let val = postorder[n - 1];
        let mid = inorder.iter().position(|&v| v == val).unwrap(); // the linear scan
        let node = Rc::new(RefCell::new(TreeNode::new(val)));
        node.borrow_mut().left = build(&inorder[..mid], &postorder[..mid]);
        node.borrow_mut().right = build(&inorder[mid + 1..], &postorder[mid..n - 1]);
        Some(node)
    }
    build(&inorder, &postorder)
}
pub fn build_tree(inorder: Vec<i32>, postorder: Vec<i32>) -> Option<Rc<RefCell<TreeNode>>> {
    // Recurse over the half-open inorder range [lo, hi); `post` counts
    // down through the postorder array from the back.
    fn build(
        idx: &HashMap<i32, usize>,
        postorder: &[i32],
        post: &mut usize,
        lo: usize,
        hi: usize,
    ) -> Option<Rc<RefCell<TreeNode>>> {
        if lo >= hi {
            return None;
        }
        *post -= 1;
        let val = postorder[*post];
        let mid = idx[&val];
        let node = Rc::new(RefCell::new(TreeNode::new(val)));
        node.borrow_mut().right = build(idx, postorder, post, mid + 1, hi); // right first!
        node.borrow_mut().left = build(idx, postorder, post, lo, mid);
        Some(node)
    }

    // value -> inorder position (values are unique)
    let idx: HashMap<i32, usize> = inorder.iter().enumerate().map(|(i, &v)| (v, i)).collect();
    let mut post = postorder.len();
    build(&idx, &postorder, &mut post, 0, inorder.len())
}
function buildTreeLinearSearch(inorder: number[], postorder: number[]): TreeNode | null {
  const build = (ilo: number, ihi: number, plo: number, phi: number): TreeNode | null => {
    if (ilo > ihi) return null;
    const val = postorder[phi];
    let mid = ilo;
    while (inorder[mid] !== val) mid++; // the linear scan
    const leftSize = mid - ilo;
    const node = new TreeNode(val);
    node.left = build(ilo, mid - 1, plo, plo + leftSize - 1);
    node.right = build(mid + 1, ihi, plo + leftSize, phi - 1);
    return node;
  };
  return build(0, inorder.length - 1, 0, postorder.length - 1);
}
function buildTree(inorder: number[], postorder: number[]): TreeNode | null {
  // value -> inorder position (values are unique)
  const idx = new Map<number, number>();
  inorder.forEach((v, i) => idx.set(v, i));
  let post = postorder.length - 1; // next unused postorder index

  const build = (lo: number, hi: number): TreeNode | null => {
    if (lo > hi) return null;
    const val = postorder[post--];
    const node = new TreeNode(val);
    const mid = idx.get(val)!;
    node.right = build(mid + 1, hi); // right before left!
    node.left = build(lo, mid - 1);
    return node;
  };
  return build(0, inorder.length - 1);
}
func buildTreeLinearSearch(inorder []int, postorder []int) *TreeNode {
	var build func(ilo, ihi, plo, phi int) *TreeNode
	build = func(ilo, ihi, plo, phi int) *TreeNode {
		if ilo > ihi {
			return nil
		}
		val := postorder[phi]
		mid := ilo
		for inorder[mid] != val { // the linear scan
			mid++
		}
		leftSize := mid - ilo
		node := &TreeNode{Val: val}
		node.Left = build(ilo, mid-1, plo, plo+leftSize-1)
		node.Right = build(mid+1, ihi, plo+leftSize, phi-1)
		return node
	}
	return build(0, len(inorder)-1, 0, len(postorder)-1)
}
func buildTree(inorder []int, postorder []int) *TreeNode {
	idx := make(map[int]int, len(inorder)) // value -> inorder position (values unique)
	for i, v := range inorder {
		idx[v] = i
	}
	post := len(postorder) - 1 // next unused postorder index, from the back

	var build func(lo, hi int) *TreeNode
	build = func(lo, hi int) *TreeNode {
		if lo > hi {
			return nil
		}
		val := postorder[post]
		post--
		node := &TreeNode{Val: val}
		mid := idx[val]
		node.Right = build(mid+1, hi) // right before left!
		node.Left = build(lo, mid-1)
		return node
	}
	return build(0, len(inorder)-1)
}
func buildTreeLinearSearch(_ inorder: [Int], _ postorder: [Int]) -> TreeNode? {
    func build(_ ilo: Int, _ ihi: Int, _ plo: Int, _ phi: Int) -> TreeNode? {
        if ilo > ihi { return nil }
        let val = postorder[phi]
        var mid = ilo
        while inorder[mid] != val { mid += 1 }  // the linear scan
        let leftSize = mid - ilo
        let node = TreeNode(val)
        node.left = build(ilo, mid - 1, plo, plo + leftSize - 1)
        node.right = build(mid + 1, ihi, plo + leftSize, phi - 1)
        return node
    }
    return build(0, inorder.count - 1, 0, postorder.count - 1)
}
func buildTree(_ inorder: [Int], _ postorder: [Int]) -> TreeNode? {
    // value -> inorder position (values are unique)
    var idx = [Int: Int](minimumCapacity: inorder.count)
    for (i, v) in inorder.enumerated() { idx[v] = i }
    var post = postorder.count - 1  // next unused postorder index

    func build(_ lo: Int, _ hi: Int) -> TreeNode? {
        if lo > hi { return nil }
        let val = postorder[post]
        post -= 1
        let node = TreeNode(val)
        let mid = idx[val]!
        node.right = build(mid + 1, hi)  // right before left!
        node.left = build(lo, mid - 1)
        return node
    }
    return build(0, inorder.count - 1)
}
Recommended Approach 1 of 3 · Recursion with list slicing (the intuitive first cut)O(n^2) worst case time · O(n^2) worst case (the slices) space

24. Populating Next Right Pointers in Each Node II

Medium · LC 117

Given a binary tree that need not be perfect, connect each node's next pointer to the node immediately to its right on the same level. Walk each level through the next pointers already installed, appending every child encountered onto a dummy-headed list; when the level is exhausted, the dummy's next is the head of the level below. The insight is that a fully linked level is a free traversal of that level, which eliminates the queue a plain breadth-first pass would need and drops the extra space to constant.

The obvious level-order pass, linking neighbors as they dequeue. Simple, but the queue costs O(w) — the follow-up asks for O(1).

The queue is dead weight: walk the current level via its next pointers, appending every child onto a dummy-headed list; dummy.next is then the next level's head.

The obvious level-order pass, linking neighbors as they dequeue. Simple, but the queue costs O(w) — the follow-up asks for O(1).

The queue is dead weight: walk the current level via its next pointers, appending every child onto a dummy-headed list — dummy.next is then the head of the level below.

The obvious level-order pass, linking neighbors as they dequeue. Simple, but the queue costs O(w) — the follow-up asks for O(1).

The queue is dead weight: walk the current level via its next pointers, appending every child onto a dummy-headed list — dummy.next is then the head of the level below.

The obvious level-order pass, linking neighbors as they dequeue. Simple, but the queue costs O(w) — the follow-up asks for O(1).

The queue is dead weight: walk the current level via its next pointers, appending every child onto a dummy-headed list — dummy.next is then the head of the level below.

The obvious level-order pass, linking neighbors as they dequeue. Simple, but the queue costs O(w) — the follow-up asks for O(1).

The queue is dead weight: walk the current level via its next pointers, appending every child onto a dummy-headed list — dummy.Next is then the head of the level below.

The obvious level-order pass, linking neighbors as they dequeue. Simple, but the queue costs O(w) — the follow-up asks for O(1).

The queue is dead weight: walk the current level via its next pointers, appending every child onto a dummy-headed list — dummy.next is then the head of the level below.

def connect_bfs(self, root: 'Node') -> 'Node':
    if not root:
        return None
    queue = deque([root])
    while queue:
        prev = None
        for _ in range(len(queue)):
            node = queue.popleft()
            if prev:
                prev.next = node
            prev = node
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
    return root
def connect(self, root: 'Node') -> 'Node':
    level = root
    while level:
        dummy = Node()
        tail = dummy
        node = level
        while node:
            if node.left:
                tail.next = node.left
                tail = tail.next
            if node.right:
                tail.next = node.right
                tail = tail.next
            node = node.next
        level = dummy.next
    return root
Node *connectBfs(Node *root) {
    if (!root) return nullptr;
    queue<Node *> q;
    q.push(root);
    while (!q.empty()) {
        size_t width = q.size();
        Node *prev = nullptr;
        for (size_t i = 0; i < width; ++i) {
            Node *node = q.front();
            q.pop();
            if (prev) prev->next = node;
            prev = node;
            if (node->left) q.push(node->left);
            if (node->right) q.push(node->right);
        }
    }
    return root;
}
Node *connect(Node *root) {
    Node *level = root;
    while (level) {
        Node dummy;
        Node *tail = &dummy;
        for (Node *node = level; node; node = node->next) {
            if (node->left) {
                tail->next = node->left;
                tail = tail->next;
            }
            if (node->right) {
                tail->next = node->right;
                tail = tail->next;
            }
        }
        level = dummy.next;
    }
    return root;
}
pub fn connect_bfs(root: Link) -> Link {
    let mut queue = VecDeque::new();
    if let Some(r) = root.clone() {
        queue.push_back(r);
    }
    while !queue.is_empty() {
        let width = queue.len();
        let mut prev: Link = None;
        for _ in 0..width {
            let node = queue.pop_front().unwrap();
            if let Some(p) = prev {
                p.borrow_mut().next = Some(Rc::clone(&node));
            }
            {
                let n = node.borrow();
                if let Some(left) = n.left.clone() {
                    queue.push_back(left);
                }
                if let Some(right) = n.right.clone() {
                    queue.push_back(right);
                }
            }
            prev = Some(node);
        }
    }
    root
}
pub fn connect(root: Link) -> Link {
    let mut level = root.clone();
    while level.is_some() {
        let dummy = Rc::new(RefCell::new(Node::new(0)));
        let mut tail = Rc::clone(&dummy);
        let mut cur = level.clone();
        while let Some(node) = cur {
            let n = node.borrow();
            if let Some(left) = n.left.clone() {
                tail.borrow_mut().next = Some(Rc::clone(&left));
                tail = left;
            }
            if let Some(right) = n.right.clone() {
                tail.borrow_mut().next = Some(Rc::clone(&right));
                tail = right;
            }
            cur = n.next.clone();
        }
        level = dummy.borrow().next.clone();
    }
    root
}
function connectBfs(root: _Node | null): _Node | null {
    if (!root) return null;
    const queue: _Node[] = [root];
    let head = 0;
    while (head < queue.length) {
        const width = queue.length - head;
        let prev: _Node | null = null;
        for (let i = 0; i < width; i++) {
            const node = queue[head++];
            if (prev) prev.next = node;
            prev = node;
            if (node.left) queue.push(node.left);
            if (node.right) queue.push(node.right);
        }
    }
    return root;
}
function connect(root: _Node | null): _Node | null {
    let level = root;
    while (level !== null) {
        const dummy = new _Node();
        let tail = dummy;
        for (let node: _Node | null = level; node !== null; node = node.next) {
            if (node.left) {
                tail.next = node.left;
                tail = node.left;
            }
            if (node.right) {
                tail.next = node.right;
                tail = node.right;
            }
        }
        level = dummy.next;
    }
    return root;
}
func connectBfs(root *Node) *Node {
	if root == nil {
		return nil
	}
	queue := []*Node{root}
	for len(queue) > 0 {
		width := len(queue)
		var prev *Node
		for i := 0; i < width; i++ {
			node := queue[0]
			queue = queue[1:]
			if prev != nil {
				prev.Next = node
			}
			prev = node
			if node.Left != nil {
				queue = append(queue, node.Left)
			}
			if node.Right != nil {
				queue = append(queue, node.Right)
			}
		}
	}
	return root
}
func connect(root *Node) *Node {
	level := root
	for level != nil {
		dummy := &Node{}
		tail := dummy
		for node := level; node != nil; node = node.Next {
			if node.Left != nil {
				tail.Next = node.Left
				tail = tail.Next
			}
			if node.Right != nil {
				tail.Next = node.Right
				tail = tail.Next
			}
		}
		level = dummy.Next
	}
	return root
}
func connectBfs(_ root: Node?) -> Node? {
    guard let root = root else { return nil }
    var queue = [root]
    var head = 0
    while head < queue.count {
        let width = queue.count - head
        var prev: Node? = nil
        for _ in 0..<width {
            let node = queue[head]
            head += 1
            prev?.next = node
            prev = node
            if let left = node.left { queue.append(left) }
            if let right = node.right { queue.append(right) }
        }
    }
    return root
}
func connect(_ root: Node?) -> Node? {
    var level = root
    while level != nil {
        let dummy = Node(0)
        var tail = dummy
        var node = level
        while let current = node {
            if let left = current.left {
                tail.next = left
                tail = left
            }
            if let right = current.right {
                tail.next = right
                tail = right
            }
            node = current.next
        }
        level = dummy.next
    }
    return root
}
Recommended Approach 1 of 2 · BFS with a queueO(n) time · O(w) space

25. Flatten Binary Tree to Linked List

Medium · LC 114

Given a binary tree, flatten it in place into a linked list that follows preorder order, using each node's right pointer with every left pointer set to null. Walk down the right spine, and whenever a node has a left child, find the rightmost node of that left subtree — the node's preorder predecessor — splice the current right subtree onto it, then hoist the left subtree into the right slot. The trick is that this Morris-style rewiring needs no stack or recursion at all: each edge is walked at most twice, so the whole pass stays O(n) time in O(1) space.

Classic preorder (push right, then left, so left pops first); each popped node's right becomes whatever is next on the stack. Simple, but the stack can hold a whole path's siblings at once.

Visit right, left, root — exactly preorder reversed — and point each node's right at the previously visited node. The list assembles back-to-front, so no subtree is ever overwritten before it has been visited. Drops the stack to recursion depth only.

For each node with a left child, splice the right subtree onto the rightmost node of the left subtree (the node's preorder predecessor), then hoist the left subtree into the right slot. Each edge is walked at most twice, so the total stays linear — and no auxiliary storage at all.

Record every node in preorder, then chain them along the right pointers. Two passes and a whole extra array of pointers — the clarity of the definition, at the cost of linear scratch space.

For each node with a left child, splice the right subtree onto the rightmost node of the left subtree (the node's preorder predecessor), then hoist the left subtree into the right slot. Each edge is walked at most twice, so the total stays linear — no list, no recursion, no auxiliary storage at all.

Record every node in preorder, then chain them along the right pointers. Two passes and a whole extra vector of Rc handles — the clarity of the definition, at the cost of linear scratch space.

For each node with a left child, splice the right subtree onto the rightmost node of the left subtree (the node's preorder predecessor), then hoist the left subtree into the right slot. Each edge is walked at most twice, so the total stays linear — no list, no recursion, no auxiliary storage at all.

Record every node in preorder, then chain them along the right pointers. Two passes and a whole extra array of references — the clarity of the definition, at the cost of linear scratch space.

For each node with a left child, splice the right subtree onto the rightmost node of the left subtree (the node's preorder predecessor), then hoist the left subtree into the right slot. Each edge is walked at most twice, so the total stays linear — no list, no recursion, no auxiliary storage at all.

Record every node in preorder, then chain them along the right pointers. Two passes and a whole extra slice of pointers — the clarity of the definition, at the cost of linear scratch space.

For each node with a left child, splice the right subtree onto the rightmost node of the left subtree (the node's preorder predecessor), then hoist the left subtree into the right slot. Each edge is walked at most twice, so the total stays linear — no list, no recursion, no auxiliary storage at all.

Record every node in preorder, then chain them along the right pointers. Two passes and a whole extra array of references — the clarity of the definition, at the cost of linear scratch space.

For each node with a left child, splice the right subtree onto the rightmost node of the left subtree (the node's preorder predecessor), then hoist the left subtree into the right slot. Each edge is walked at most twice, so the total stays linear — no list, no recursion, no auxiliary storage at all.

def flatten_stack(self, root: Optional[TreeNode]) -> None:
    if root is None:
        return
    stack = [root]
    while stack:
        node = stack.pop()
        if node.right:
            stack.append(node.right)
        if node.left:
            stack.append(node.left)
        node.left = None
        node.right = stack[-1] if stack else None
def flatten_reverse_preorder(self, root: Optional[TreeNode]) -> None:
    prev = None

    def visit(node: Optional[TreeNode]) -> None:
        nonlocal prev
        if node is None:
            return
        visit(node.right)
        visit(node.left)
        node.right = prev
        node.left = None
        prev = node

    visit(root)
def flatten(self, root: Optional[TreeNode]) -> None:
    node = root
    while node:
        if node.left:
            pred = node.left
            while pred.right:
                pred = pred.right
            pred.right = node.right
            node.right = node.left
            node.left = None
        node = node.right
    static void collectPreorder(TreeNode* node, vector<TreeNode*>& order) {
        if (!node) return;
        order.push_back(node);
        collectPreorder(node->left, order);
        collectPreorder(node->right, order);
    }

public:
    void flattenPreorderList(TreeNode* root) {
        vector<TreeNode*> order;
        collectPreorder(root, order);
        for (size_t i = 0; i < order.size(); ++i) {
            order[i]->left = nullptr;
            order[i]->right = i + 1 < order.size() ? order[i + 1] : nullptr;
        }
    }
void flatten(TreeNode* root) {
    for (TreeNode* node = root; node; node = node->right) {
        if (node->left) {
            TreeNode* pred = node->left;
            while (pred->right) pred = pred->right;
            pred->right = node->right;
            node->right = node->left;
            node->left = nullptr;
        }
    }
}
pub fn flatten_preorder_list(root: &mut Option<Rc<RefCell<TreeNode>>>) {
    fn collect(node: &Option<Rc<RefCell<TreeNode>>>, order: &mut Vec<Rc<RefCell<TreeNode>>>) {
        if let Some(n) = node {
            order.push(Rc::clone(n));
            let left = n.borrow().left.clone();
            let right = n.borrow().right.clone();
            collect(&left, order);
            collect(&right, order);
        }
    }
    let mut order = Vec::new();
    collect(root, &mut order);
    for i in 0..order.len() {
        let mut node = order[i].borrow_mut();
        node.left = None;
        node.right = if i + 1 < order.len() {
            Some(Rc::clone(&order[i + 1]))
        } else {
            None
        };
    }
}
pub fn flatten(root: &mut Option<Rc<RefCell<TreeNode>>>) {
    let mut node = root.clone();
    while let Some(n) = node {
        let left = n.borrow_mut().left.take();
        if let Some(l) = left {
            // Find the rightmost node of the left subtree.
            let mut pred = Rc::clone(&l);
            loop {
                let next = pred.borrow().right.clone();
                match next {
                    Some(r) => pred = r,
                    None => break,
                }
            }
            let right = n.borrow_mut().right.take();
            pred.borrow_mut().right = right;
            n.borrow_mut().right = Some(l);
        }
        node = n.borrow().right.clone();
    }
}
function flattenPreorderList(root: TreeNode | null): void {
  const order: TreeNode[] = [];
  const collect = (node: TreeNode | null): void => {
    if (node === null) return;
    order.push(node);
    collect(node.left);
    collect(node.right);
  };
  collect(root);
  for (let i = 0; i < order.length; i++) {
    order[i].left = null;
    order[i].right = i + 1 < order.length ? order[i + 1] : null;
  }
}
/**
 Do not return anything, modify root in-place instead.
 */
function flatten(root: TreeNode | null): void {
  for (let node = root; node !== null; node = node.right) {
    if (node.left !== null) {
      let pred = node.left;
      while (pred.right !== null) pred = pred.right;
      pred.right = node.right;
      node.right = node.left;
      node.left = null;
    }
  }
}
func flattenPreorderList(root *TreeNode) {
	order := []*TreeNode{}
	var collect func(node *TreeNode)
	collect = func(node *TreeNode) {
		if node == nil {
			return
		}
		order = append(order, node)
		collect(node.Left)
		collect(node.Right)
	}
	collect(root)
	for i, node := range order {
		node.Left = nil
		if i+1 < len(order) {
			node.Right = order[i+1]
		} else {
			node.Right = nil
		}
	}
}
func flatten(root *TreeNode) {
	for node := root; node != nil; node = node.Right {
		if node.Left != nil {
			pred := node.Left
			for pred.Right != nil {
				pred = pred.Right
			}
			pred.Right = node.Right
			node.Right = node.Left
			node.Left = nil
		}
	}
}
func flattenPreorderList(_ root: TreeNode?) {
    var order: [TreeNode] = []
    func collect(_ node: TreeNode?) {
        guard let node = node else { return }
        order.append(node)
        collect(node.left)
        collect(node.right)
    }
    collect(root)
    for (i, node) in order.enumerated() {
        node.left = nil
        node.right = i + 1 < order.count ? order[i + 1] : nil
    }
}
func flatten(_ root: TreeNode?) {
    var node = root
    while let current = node {
        if let left = current.left {
            var pred = left
            while let next = pred.right {
                pred = next
            }
            pred.right = current.right
            current.right = left
            current.left = nil
        }
        node = current.right
    }
}
Recommended Approach 1 of 3 · Iterative preorder with an explicit stackO(n) time · O(n) space

26. Path Sum

Easy · LC 112

Given a binary tree and a target, decide whether any root-to-leaf path's values sum exactly to the target. Recurse down the tree subtracting each node's value from the target, and at a leaf check that the remaining target equals the leaf's value. The pitfall is that the path must end at a leaf and values may be negative, so hitting the target mid-path proves nothing and no branch can be pruned for overshooting.

Collect the total of every root-to-leaf path into a list, then ask whether the target is in it. Does all the traversal work up front: no early exit, and the list of sums is pure overhead.

Same math with an explicit stack — each entry remembers how much of the target is left once its node is consumed. Stops at the first matching leaf instead of enumerating them all, and no recursion limit to worry about.

At a leaf the remaining target must equal the leaf's value; inner nodes just pass the reduced target to both children. Short-circuits on the first hit, and the stack never exceeds the tree's height.

Collect the total of every root-to-leaf path, then look for the target among them. Does all the traversal work up front: no early exit, and the list of sums is pure overhead.

At a leaf the remaining target must equal the leaf's value; inner nodes just pass the reduced target to both children. Short-circuits on the first hit and stores nothing beyond the call stack.

Collect the total of every root-to-leaf path, then look for the target among them. Does all the traversal work up front: no early exit, and the vector of sums is pure overhead.

At a LEAF the remaining target must equal the leaf's value; inner nodes just pass the reduced target to both children. Short-circuits on the first hit and stores nothing beyond the call stack.

Collect the total of every root-to-leaf path, then look for the target among them. Does all the traversal work up front: no early exit, and the array of sums is pure overhead.

At a LEAF the remaining target must equal the leaf's value; inner nodes just pass the reduced target to both children. Short-circuits on the first hit and stores nothing beyond the call stack.

Collect the total of every root-to-leaf path, then look for the target among them. Does all the traversal work up front: no early exit, and the list of sums is pure overhead.

At a LEAF the remaining target must equal the leaf's value; inner nodes just pass the reduced target to both children. Short-circuits on the first hit and stores nothing beyond the call stack.

Collect the total of every root-to-leaf path, then look for the target among them. Does all the traversal work up front: no early exit, and the array of sums is pure overhead.

At a LEAF the remaining target must equal the leaf's value; inner nodes just pass the reduced target to both children. Short-circuits on the first hit and stores nothing beyond the call stack.

def hasPathSum_all_sums(self, root: Optional[TreeNode], targetSum: int) -> bool:
    sums: List[int] = []

    def collect(node: Optional[TreeNode], acc: int) -> None:
        if node is None:
            return
        acc += node.val
        if node.left is None and node.right is None:
            sums.append(acc)
            return
        collect(node.left, acc)
        collect(node.right, acc)

    collect(root, 0)
    return targetSum in sums
def hasPathSum_iterative_stack(self, root: Optional[TreeNode], targetSum: int) -> bool:
    if root is None:
        return False
    stack = [(root, targetSum)]
    while stack:
        node, remaining = stack.pop()
        if node.left is None and node.right is None:
            if remaining == node.val:
                return True
            continue
        if node.right:
            stack.append((node.right, remaining - node.val))
        if node.left:
            stack.append((node.left, remaining - node.val))
    return False
def hasPathSum(self, root: Optional[TreeNode], targetSum: int) -> bool:
    if root is None:
        return False
    if root.left is None and root.right is None:
        return targetSum == root.val
    remaining = targetSum - root.val
    return (self.hasPathSum(root.left, remaining)
            or self.hasPathSum(root.right, remaining))
    static void collectSums(TreeNode* node, int acc, vector<int>& sums) {
        if (!node) return;
        acc += node->val;
        if (!node->left && !node->right) {
            sums.push_back(acc);
            return;
        }
        collectSums(node->left, acc, sums);
        collectSums(node->right, acc, sums);
    }

public:
    bool hasPathSumAllSums(TreeNode* root, int targetSum) {
        vector<int> sums;
        collectSums(root, 0, sums);
        for (int s : sums)
            if (s == targetSum) return true;
        return false;
    }
bool hasPathSum(TreeNode* root, int targetSum) {
    if (!root) return false;
    if (!root->left && !root->right) return targetSum == root->val;
    int remaining = targetSum - root->val;
    return hasPathSum(root->left, remaining) || hasPathSum(root->right, remaining);
}
pub fn has_path_sum_all_sums(root: Option<Rc<RefCell<TreeNode>>>, target_sum: i32) -> bool {
    fn collect(node: &Option<Rc<RefCell<TreeNode>>>, acc: i32, sums: &mut Vec<i32>) {
        if let Some(n) = node {
            let n = n.borrow();
            let acc = acc + n.val;
            if n.left.is_none() && n.right.is_none() {
                sums.push(acc);
            } else {
                collect(&n.left, acc, sums);
                collect(&n.right, acc, sums);
            }
        }
    }
    let mut sums = Vec::new();
    collect(&root, 0, &mut sums);
    sums.contains(&target_sum)
}
pub fn has_path_sum(root: Option<Rc<RefCell<TreeNode>>>, target_sum: i32) -> bool {
    match root {
        None => false,
        Some(node) => {
            let node = node.borrow();
            if node.left.is_none() && node.right.is_none() {
                return target_sum == node.val;
            }
            let remaining = target_sum - node.val;
            Self::has_path_sum(node.left.clone(), remaining)
                || Self::has_path_sum(node.right.clone(), remaining)
        }
    }
}
function hasPathSumAllSums(root: TreeNode | null, targetSum: number): boolean {
  const sums: number[] = [];
  const collect = (node: TreeNode | null, acc: number): void => {
    if (node === null) return;
    acc += node.val;
    if (node.left === null && node.right === null) {
      sums.push(acc);
      return;
    }
    collect(node.left, acc);
    collect(node.right, acc);
  };
  collect(root, 0);
  return sums.includes(targetSum);
}
function hasPathSum(root: TreeNode | null, targetSum: number): boolean {
  if (root === null) return false;
  if (root.left === null && root.right === null) return targetSum === root.val;
  const remaining = targetSum - root.val;
  return hasPathSum(root.left, remaining) || hasPathSum(root.right, remaining);
}
func hasPathSumAllSums(root *TreeNode, targetSum int) bool {
	sums := []int{}
	var collect func(node *TreeNode, acc int)
	collect = func(node *TreeNode, acc int) {
		if node == nil {
			return
		}
		acc += node.Val
		if node.Left == nil && node.Right == nil {
			sums = append(sums, acc)
			return
		}
		collect(node.Left, acc)
		collect(node.Right, acc)
	}
	collect(root, 0)
	for _, s := range sums {
		if s == targetSum {
			return true
		}
	}
	return false
}
func hasPathSum(root *TreeNode, targetSum int) bool {
	if root == nil {
		return false
	}
	if root.Left == nil && root.Right == nil {
		return targetSum == root.Val
	}
	remaining := targetSum - root.Val
	return hasPathSum(root.Left, remaining) || hasPathSum(root.Right, remaining)
}
func hasPathSumAllSums(_ root: TreeNode?, _ targetSum: Int) -> Bool {
    var sums: [Int] = []
    func collect(_ node: TreeNode?, _ acc: Int) {
        guard let node = node else { return }
        let acc = acc + node.val
        if node.left == nil && node.right == nil {
            sums.append(acc)
            return
        }
        collect(node.left, acc)
        collect(node.right, acc)
    }
    collect(root, 0)
    return sums.contains(targetSum)
}
func hasPathSum(_ root: TreeNode?, _ targetSum: Int) -> Bool {
    guard let root = root else { return false }
    if root.left == nil && root.right == nil {
        return targetSum == root.val
    }
    let remaining = targetSum - root.val
    return hasPathSum(root.left, remaining) || hasPathSum(root.right, remaining)
}
Recommended Approach 1 of 3 · Enumerate every root-to-leaf sumO(n) time · O(n) — one sum per leaf, plus the recursion space

27. Sum Root to Leaf Numbers

Medium · LC 129

Given a binary tree whose nodes each hold a single digit, treat every root-to-leaf path as a decimal number and return the sum of all of them. Recurse from the root carrying a running prefix of prefix times ten plus the current digit; a leaf returns the finished number, and inner nodes return the sum of their two children's results. The trick is that the arithmetic replaces any string building, so each node costs O(1) and the only storage is the O(h) recursion stack.

The most literal reading of the statement: walk to every leaf while building the digit string, then int() each path and add them up. Copies an O(h) string at every step and stores every path.

Replace the strings with the arithmetic prefix*10 + digit, so each node costs O(1) and nothing is stored beyond the frontier. Queue width is the tree's widest level, which can still be O(n).

Depth-first order shrinks the frontier from a level (O(w)) to a root-to-leaf path (O(h)); each stack entry pairs a node with the number spelled by the path above it.

Same complexity as the explicit stack, with the bookkeeping handed to the call stack: carry prefix*10 + digit down the tree; a leaf returns the finished number, inner nodes sum their children.

The most literal reading of the statement: walk to every leaf while building the digit string, then parse each path and add them up. Copies an O(h) string at every step and stores every path.

Replace the strings with the arithmetic prefix*10 + digit, so each node costs O(1) and only the current root-to-leaf path is stored; each stack entry pairs a node with the number spelled above it.

Same complexity as the explicit stack, with the bookkeeping handed to the call stack: carry prefix*10 + digit down the tree; a leaf returns the finished number, inner nodes sum their children.

The most literal reading of the statement: walk to every leaf while building the digit string, then parse each path and add them up. Copies an O(h) string at every step and stores every path.

Replace the strings with the arithmetic prefix*10 + digit, so each node costs O(1) and only the current root-to-leaf path is stored; each stack entry pairs a node with the number spelled above it.

Same complexity as the explicit stack, with the bookkeeping handed to the call stack: carry prefix*10 + digit down the tree; a leaf returns the finished number, inner nodes sum their children.

The most literal reading of the statement: walk to every leaf while building the digit string, then parse each path and add them up. Copies an O(h) string at every step and stores every path.

Replace the strings with the arithmetic prefix*10 + digit, so each node costs O(1) and only the current root-to-leaf path is stored; each stack entry pairs a node with the number spelled above it.

Same complexity as the explicit stack, with the bookkeeping handed to the call stack: carry prefix*10 + digit down the tree; a leaf returns the finished number, inner nodes sum their children.

The most literal reading of the statement: walk to every leaf while building the digit string, then parse each path and add them up. Copies an O(h) string at every step and stores every path.

Replace the strings with the arithmetic prefix*10 + digit, so each node costs O(1) and only the current root-to-leaf path is stored; each stack entry pairs a node with the number spelled above it.

Same complexity as the explicit stack, with the bookkeeping handed to the call stack: carry prefix*10 + digit down the tree; a leaf returns the finished number, inner nodes sum their children.

The most literal reading of the statement: walk to every leaf while building the digit string, then parse each path and add them up. Copies an O(h) string at every step and stores every path.

Replace the strings with the arithmetic prefix*10 + digit, so each node costs O(1) and only the current root-to-leaf path is stored; each stack entry pairs a node with the number spelled above it.

Same complexity as the explicit stack, with the bookkeeping handed to the call stack: carry prefix*10 + digit down the tree; a leaf returns the finished number, inner nodes sum their children.

def sumNumbers_path_strings(self, root: "TreeNode | None") -> int:
    paths: "list[str]" = []

    def walk(node: "TreeNode | None", digits: str) -> None:
        if node is None:
            return
        digits += str(node.val)
        if node.left is None and node.right is None:
            paths.append(digits)
            return
        walk(node.left, digits)
        walk(node.right, digits)

    walk(root, "")
    return sum(int(p) for p in paths)
def sumNumbers_bfs(self, root: "TreeNode | None") -> int:
    if root is None:
        return 0
    total = 0
    queue = deque([(root, 0)])
    while queue:
        node, prefix = queue.popleft()
        prefix = prefix * 10 + node.val
        if node.left is None and node.right is None:
            total += prefix
            continue
        if node.left is not None:
            queue.append((node.left, prefix))
        if node.right is not None:
            queue.append((node.right, prefix))
    return total
def sumNumbers_iterative(self, root: "TreeNode | None") -> int:
    if root is None:
        return 0
    total = 0
    stack = [(root, 0)]
    while stack:
        node, prefix = stack.pop()
        prefix = prefix * 10 + node.val
        if node.left is None and node.right is None:
            total += prefix
            continue
        if node.right is not None:
            stack.append((node.right, prefix))
        if node.left is not None:
            stack.append((node.left, prefix))
    return total
def sumNumbers(self, root: "TreeNode | None") -> int:
    def dfs(node: "TreeNode | None", prefix: int) -> int:
        if node is None:
            return 0
        prefix = prefix * 10 + node.val
        if node.left is None and node.right is None:
            return prefix
        return dfs(node.left, prefix) + dfs(node.right, prefix)

    return dfs(root, 0)
int sumNumbersPathStrings(TreeNode* root) {
    std::vector<std::string> paths;
    auto collect = [&paths](auto&& self, TreeNode* node, std::string digits) -> void {
        if (node == nullptr) return;
        digits += static_cast<char>('0' + node->val);
        if (node->left == nullptr && node->right == nullptr) {
            paths.push_back(std::move(digits));
            return;
        }
        self(self, node->left, digits);
        self(self, node->right, std::move(digits));
    };
    collect(collect, root, "");
    int total = 0;
    for (const std::string& digits : paths) total += std::stoi(digits);
    return total;
}
int sumNumbersIterative(TreeNode* root) {
    if (root == nullptr) return 0;
    int total = 0;
    std::stack<std::pair<TreeNode*, int>> stack;
    stack.push({root, 0});
    while (!stack.empty()) {
        auto [node, prefix] = stack.top();
        stack.pop();
        prefix = prefix * 10 + node->val;
        if (node->left == nullptr && node->right == nullptr) {
            total += prefix;
            continue;
        }
        if (node->right != nullptr) stack.push({node->right, prefix});
        if (node->left != nullptr) stack.push({node->left, prefix});
    }
    return total;
}
    int sumNumbers(TreeNode* root) {
        return dfs(root, 0);
    }

private:
    int dfs(TreeNode* node, int prefix) {
        if (node == nullptr) return 0;
        prefix = prefix * 10 + node->val;
        if (node->left == nullptr && node->right == nullptr) return prefix;
        return dfs(node->left, prefix) + dfs(node->right, prefix);
    }
pub fn sum_numbers_path_strings(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
    fn collect(node: &Option<Rc<RefCell<TreeNode>>>, digits: String, paths: &mut Vec<String>) {
        if let Some(rc) = node {
            let n = rc.borrow();
            let digits = format!("{}{}", digits, n.val);
            if n.left.is_none() && n.right.is_none() {
                paths.push(digits);
            } else {
                collect(&n.left, digits.clone(), paths);
                collect(&n.right, digits, paths);
            }
        }
    }
    let mut paths = Vec::new();
    collect(&root, String::new(), &mut paths);
    paths
        .iter()
        .map(|digits| digits.parse::<i32>().unwrap())
        .sum()
}
pub fn sum_numbers_iterative(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
    let mut total = 0;
    let mut stack = Vec::new();
    if let Some(rc) = root {
        stack.push((rc, 0));
    }
    while let Some((node, prefix)) = stack.pop() {
        let n = node.borrow();
        let prefix = prefix * 10 + n.val;
        if n.left.is_none() && n.right.is_none() {
            total += prefix;
            continue;
        }
        if let Some(right) = &n.right {
            stack.push((Rc::clone(right), prefix));
        }
        if let Some(left) = &n.left {
            stack.push((Rc::clone(left), prefix));
        }
    }
    total
}
pub fn sum_numbers(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
    fn dfs(node: &Option<Rc<RefCell<TreeNode>>>, prefix: i32) -> i32 {
        match node {
            None => 0,
            Some(rc) => {
                let n = rc.borrow();
                let prefix = prefix * 10 + n.val;
                if n.left.is_none() && n.right.is_none() {
                    prefix
                } else {
                    dfs(&n.left, prefix) + dfs(&n.right, prefix)
                }
            }
        }
    }
    dfs(&root, 0)
}
function sumNumbersPathStrings(root: TreeNode | null): number {
  const paths: string[] = [];
  const collect = (node: TreeNode | null, digits: string): void => {
    if (node === null) return;
    digits += String(node.val);
    if (node.left === null && node.right === null) {
      paths.push(digits);
      return;
    }
    collect(node.left, digits);
    collect(node.right, digits);
  };
  collect(root, "");
  return paths.reduce((total, digits) => total + Number(digits), 0);
}
function sumNumbersIterative(root: TreeNode | null): number {
  if (root === null) return 0;
  let total = 0;
  const stack: [TreeNode, number][] = [[root, 0]];
  while (stack.length > 0) {
    const [node, above] = stack.pop()!;
    const prefix = above * 10 + node.val;
    if (node.left === null && node.right === null) {
      total += prefix;
      continue;
    }
    if (node.right !== null) stack.push([node.right, prefix]);
    if (node.left !== null) stack.push([node.left, prefix]);
  }
  return total;
}
function sumNumbers(root: TreeNode | null): number {
  const dfs = (node: TreeNode | null, prefix: number): number => {
    if (node === null) return 0;
    prefix = prefix * 10 + node.val;
    if (node.left === null && node.right === null) return prefix;
    return dfs(node.left, prefix) + dfs(node.right, prefix);
  };
  return dfs(root, 0);
}
func sumNumbersPathStrings(root *TreeNode) int {
	var paths []string
	var collect func(node *TreeNode, digits string)
	collect = func(node *TreeNode, digits string) {
		if node == nil {
			return
		}
		digits += strconv.Itoa(node.Val)
		if node.Left == nil && node.Right == nil {
			paths = append(paths, digits)
			return
		}
		collect(node.Left, digits)
		collect(node.Right, digits)
	}
	collect(root, "")
	total := 0
	for _, digits := range paths {
		n, _ := strconv.Atoi(digits)
		total += n
	}
	return total
}
func sumNumbersIterative(root *TreeNode) int {
	if root == nil {
		return 0
	}
	type frame struct {
		node   *TreeNode
		prefix int
	}
	total := 0
	stack := []frame{{root, 0}}
	for len(stack) > 0 {
		f := stack[len(stack)-1]
		stack = stack[:len(stack)-1]
		prefix := f.prefix*10 + f.node.Val
		if f.node.Left == nil && f.node.Right == nil {
			total += prefix
			continue
		}
		if f.node.Right != nil {
			stack = append(stack, frame{f.node.Right, prefix})
		}
		if f.node.Left != nil {
			stack = append(stack, frame{f.node.Left, prefix})
		}
	}
	return total
}
func sumNumbers(root *TreeNode) int {
	return dfs(root, 0)
}

func dfs(node *TreeNode, prefix int) int {
	if node == nil {
		return 0
	}
	prefix = prefix*10 + node.Val
	if node.Left == nil && node.Right == nil {
		return prefix
	}
	return dfs(node.Left, prefix) + dfs(node.Right, prefix)
}
func sumNumbersPathStrings(_ root: TreeNode?) -> Int {
    var paths: [String] = []
    func collect(_ node: TreeNode?, _ digits: String) {
        guard let node = node else { return }
        let digits = digits + String(node.val)
        if node.left == nil && node.right == nil {
            paths.append(digits)
            return
        }
        collect(node.left, digits)
        collect(node.right, digits)
    }
    collect(root, "")
    return paths.reduce(0) { $0 + Int($1)! }
}
func sumNumbersIterative(_ root: TreeNode?) -> Int {
    guard let root = root else { return 0 }
    var total = 0
    var stack: [(node: TreeNode, prefix: Int)] = [(root, 0)]
    while let (node, prefix) = stack.popLast() {
        let value = prefix * 10 + node.val
        if node.left == nil && node.right == nil {
            total += value
            continue
        }
        if let right = node.right { stack.append((right, value)) }
        if let left = node.left { stack.append((left, value)) }
    }
    return total
}
func sumNumbers(_ root: TreeNode?) -> Int {
    return dfs(root, 0)
}

private func dfs(_ node: TreeNode?, _ prefix: Int) -> Int {
    guard let node = node else { return 0 }
    let prefix = prefix * 10 + node.val
    if node.left == nil && node.right == nil { return prefix }
    return dfs(node.left, prefix) + dfs(node.right, prefix)
}
Recommended Approach 1 of 4 · Collect every root-to-leaf digit stringO(n * h) time · O(n * h) space

28. Binary Search Tree Iterator

Medium · LC 173

Design an iterator over a binary search tree that returns values in ascending order through next and hasNext calls. Keep a stack holding a paused inorder traversal, seeded with the root's left spine so the smallest pending node sits on top; popping a node returns its value and pushes the left spine of its right subtree. The trick is that each node is pushed and popped exactly once, making both calls O(1) amortized while the stack never holds more than one root-to-leaf path, O(h) space.

One full inorder traversal in the constructor, then walk an index. Simplest to write, but wastes memory and does all work eagerly — the whole point of the follow-up is to avoid this O(n) footprint.

Lazy at last: let Python's generator machinery keep the paused traversal state for us, holding one value of lookahead so hasNext() can answer without advancing. Leans on a language feature most other languages lack — see the next rung for the portable version.

Pause a manual inorder traversal with an explicit stack: it holds every node whose value is still owed, smallest on top. Popping a node pushes the left spine of its right subtree, so each node is pushed and popped exactly once.

One full inorder traversal in the constructor, then walk an index. Simplest to write, but wastes memory and does all work eagerly — the whole point of the follow-up is to avoid this O(n) footprint.

Pause a manual inorder traversal: the stack holds every node whose value is still owed, smallest on top. next() pops one node and pushes the left spine of its right subtree; each node is pushed and popped exactly once, so the whole tree is never materialized.

One full inorder traversal in the constructor, then walk an index. Simplest to write, but wastes memory and does all work eagerly — the whole point of the follow-up is to avoid this O(n) footprint.

Pause a manual inorder traversal: the stack holds every node whose value is still owed, smallest on top. next() pops one node and pushes the left spine of its right subtree; each node is pushed and popped exactly once, so the whole tree is never materialized.

One full inorder traversal in the constructor, then walk an index. Simplest to write, but wastes memory and does all work eagerly — the whole point of the follow-up is to avoid this O(n) footprint.

Pause a manual inorder traversal: the stack holds every node whose value is still owed, smallest on top. next() pops one node and pushes the left spine of its right subtree; each node is pushed and popped exactly once, so the whole tree is never materialized.

One full inorder traversal in the constructor, then walk an index. Simplest to write, but wastes memory and does all work eagerly — the whole point of the follow-up is to avoid this O(n) footprint.

Pause a manual inorder traversal: the stack holds every node whose value is still owed, smallest on top. Next() pops one node and pushes the left spine of its right subtree; each node is pushed and popped exactly once, so the whole tree is never materialized.

One full inorder traversal in the constructor, then walk an index. Simplest to write, but wastes memory and does all work eagerly — the whole point of the follow-up is to avoid this O(n) footprint.

Pause a manual inorder traversal: the stack holds every node whose value is still owed, smallest on top. next() pops one node and pushes the left spine of its right subtree; each node is pushed and popped exactly once, so the whole tree is never materialized.

class BSTIterator_flatten:

    def __init__(self, root: "TreeNode | None"):
        self._values: "list[int]" = []
        self._index = 0

        def inorder(node: "TreeNode | None") -> None:
            if node is None:
                return
            inorder(node.left)
            self._values.append(node.val)
            inorder(node.right)

        inorder(root)

    def next(self) -> int:
        val = self._values[self._index]
        self._index += 1
        return val

    def hasNext(self) -> bool:
        return self._index < len(self._values)
class BSTIterator_generator:

    def __init__(self, root: "TreeNode | None"):
        def inorder(node: "TreeNode | None"):
            if node is not None:
                yield from inorder(node.left)
                yield node.val
                yield from inorder(node.right)

        self._gen = inorder(root)
        self._lookahead = next(self._gen, None)

    def next(self) -> int:
        val = self._lookahead
        self._lookahead = next(self._gen, None)
        assert val is not None  # LeetCode guarantees next() calls are valid
        return val

    def hasNext(self) -> bool:
        return self._lookahead is not None
class BSTIterator:

    def __init__(self, root: "TreeNode | None"):
        self._stack: "list[TreeNode]" = []
        self._push_left(root)

    def _push_left(self, node: "TreeNode | None") -> None:
        while node is not None:
            self._stack.append(node)
            node = node.left

    def next(self) -> int:
        node = self._stack.pop()
        self._push_left(node.right)
        return node.val

    def hasNext(self) -> bool:
        return bool(self._stack)
class BSTIteratorFlatten {
public:
    BSTIteratorFlatten(TreeNode* root) {
        inorder(root);
    }

    int next() {
        return values_[index_++];
    }

    bool hasNext() {
        return index_ < values_.size();
    }

private:
    void inorder(TreeNode* node) {
        if (node == nullptr) return;
        inorder(node->left);
        values_.push_back(node->val);
        inorder(node->right);
    }

    std::vector<int> values_;
    size_t index_ = 0;
};
class BSTIterator {
public:
    BSTIterator(TreeNode* root) {
        pushLeftSpine(root);
    }

    int next() {
        TreeNode* node = stack_.top();
        stack_.pop();
        pushLeftSpine(node->right);
        return node->val;
    }

    bool hasNext() {
        return !stack_.empty();
    }

private:
    void pushLeftSpine(TreeNode* node) {
        for (; node != nullptr; node = node->left) stack_.push(node);
    }

    std::stack<TreeNode*> stack_;
};
struct BSTIteratorFlatten {
    values: Vec<i32>,
    index: usize,
}

impl BSTIteratorFlatten {
    fn new(root: Option<Rc<RefCell<TreeNode>>>) -> Self {
        fn inorder(node: &Option<Rc<RefCell<TreeNode>>>, values: &mut Vec<i32>) {
            if let Some(rc) = node {
                let n = rc.borrow();
                inorder(&n.left, values);
                values.push(n.val);
                inorder(&n.right, values);
            }
        }
        let mut values = Vec::new();
        inorder(&root, &mut values);
        BSTIteratorFlatten { values, index: 0 }
    }

    fn next(&mut self) -> i32 {
        let val = self.values[self.index];
        self.index += 1;
        val
    }

    fn has_next(&self) -> bool {
        self.index < self.values.len()
    }
}
struct BSTIterator {
    stack: Vec<Rc<RefCell<TreeNode>>>,
}

impl BSTIterator {
    fn new(root: Option<Rc<RefCell<TreeNode>>>) -> Self {
        let mut it = BSTIterator { stack: Vec::new() };
        it.push_left_spine(root);
        it
    }

    fn next(&mut self) -> i32 {
        let node = self.stack.pop().expect("next() called on empty iterator");
        let (val, right) = {
            let n = node.borrow();
            (n.val, n.right.clone())
        };
        self.push_left_spine(right);
        val
    }

    fn has_next(&self) -> bool {
        !self.stack.is_empty()
    }

    fn push_left_spine(&mut self, mut node: Option<Rc<RefCell<TreeNode>>>) {
        while let Some(rc) = node {
            node = rc.borrow().left.clone();
            self.stack.push(rc);
        }
    }
}
class BSTIteratorFlatten {
  private values: number[] = [];
  private index = 0;

  constructor(root: TreeNode | null) {
    const inorder = (node: TreeNode | null): void => {
      if (node === null) return;
      inorder(node.left);
      this.values.push(node.val);
      inorder(node.right);
    };
    inorder(root);
  }

  next(): number {
    return this.values[this.index++];
  }

  hasNext(): boolean {
    return this.index < this.values.length;
  }
}
class BSTIterator {
  private stack: TreeNode[] = [];

  constructor(root: TreeNode | null) {
    this.pushLeftSpine(root);
  }

  next(): number {
    const node = this.stack.pop()!;
    this.pushLeftSpine(node.right);
    return node.val;
  }

  hasNext(): boolean {
    return this.stack.length > 0;
  }

  private pushLeftSpine(node: TreeNode | null): void {
    for (; node !== null; node = node.left) this.stack.push(node);
  }
}
type BSTIteratorFlatten struct {
	values []int
	index  int
}

func ConstructorFlatten(root *TreeNode) BSTIteratorFlatten {
	it := BSTIteratorFlatten{}
	it.inorder(root)
	return it
}

func (it *BSTIteratorFlatten) inorder(node *TreeNode) {
	if node == nil {
		return
	}
	it.inorder(node.Left)
	it.values = append(it.values, node.Val)
	it.inorder(node.Right)
}

func (it *BSTIteratorFlatten) Next() int {
	v := it.values[it.index]
	it.index++
	return v
}

func (it *BSTIteratorFlatten) HasNext() bool {
	return it.index < len(it.values)
}
type BSTIterator struct {
	stack []*TreeNode
}

func Constructor(root *TreeNode) BSTIterator {
	it := BSTIterator{}
	it.pushLeftSpine(root)
	return it
}

func (it *BSTIterator) pushLeftSpine(node *TreeNode) {
	for ; node != nil; node = node.Left {
		it.stack = append(it.stack, node)
	}
}

func (it *BSTIterator) Next() int {
	node := it.stack[len(it.stack)-1]
	it.stack = it.stack[:len(it.stack)-1]
	it.pushLeftSpine(node.Right)
	return node.Val
}

func (it *BSTIterator) HasNext() bool {
	return len(it.stack) > 0
}
class BSTIteratorFlatten {
    private var values: [Int] = []
    private var index = 0

    init(_ root: TreeNode?) {
        inorder(root)
    }

    func next() -> Int {
        let val = values[index]
        index += 1
        return val
    }

    func hasNext() -> Bool {
        return index < values.count
    }

    private func inorder(_ node: TreeNode?) {
        guard let node = node else { return }
        inorder(node.left)
        values.append(node.val)
        inorder(node.right)
    }
}
class BSTIterator {
    private var stack: [TreeNode] = []

    init(_ root: TreeNode?) {
        pushLeftSpine(root)
    }

    func next() -> Int {
        let node = stack.removeLast()
        pushLeftSpine(node.right)
        return node.val
    }

    func hasNext() -> Bool {
        return !stack.isEmpty
    }

    private func pushLeftSpine(_ node: TreeNode?) {
        var node = node
        while let current = node {
            stack.append(current)
            node = current.left
        }
    }
}
Recommended Approach 1 of 3 · Flatten to a list up frontO(n) constructor, O(1) next()/hasNext() time · O(n) space

29. Count Complete Tree Nodes

Medium · LC 222

Given a complete binary tree, count its nodes in better than linear time. At each subtree measure the heights of the leftmost and rightmost spines: if they are equal the subtree is perfect and contributes 2^h - 1 nodes with no traversal, otherwise recurse into both children. The trick is that completeness guarantees one child is always perfect, so only one path actually descends and the whole count runs in O(log^2 n).

The one-liner every binary tree supports: count self plus both subtrees. Correct, but misses the entire point of the constraint — the follow-up asks for less than O(n).

First sub-O(n) idea: the last level has between 1 and 2^d leaves, filled left to right. Whether leaf index i exists is a single O(d) root-to-leaf probe (each bit of i says go left or right), so binary-search the leaf count. Fast, but fiddly index bookkeeping.

Same O(log^2 n) with far simpler reasoning than the index probes: if the leftmost and rightmost spines have equal height the subtree is perfect — 2^h - 1 nodes, no traversal needed. Otherwise recurse; at every level one child is perfect, so only one path descends.

The one-liner every binary tree supports: count self plus both subtrees. Correct, but misses the entire point of the constraint — the follow-up asks for less than O(n).

If the leftmost and rightmost spines have equal height the subtree is perfect: 2^h - 1 nodes with no traversal. Otherwise recurse into both children — at every level one of them is perfect, so only one path keeps descending: O(log n) levels x O(log n) spine walks.

The one-liner every binary tree supports: count self plus both subtrees. Correct, but misses the entire point of the constraint — the follow-up asks for less than O(n).

If the leftmost and rightmost spines have equal height the subtree is perfect: 2^h - 1 nodes with no traversal. Otherwise recurse into both children — at every level one of them is perfect, so only one path keeps descending: O(log n) levels x O(log n) spine walks.

The one-liner every binary tree supports: count self plus both subtrees. Correct, but misses the entire point of the constraint — the follow-up asks for less than O(n).

If the leftmost and rightmost spines have equal height the subtree is perfect: 2^h - 1 nodes with no traversal. Otherwise recurse into both children — at every level one of them is perfect, so only one path keeps descending: O(log n) levels x O(log n) spine walks.

The one-liner every binary tree supports: count self plus both subtrees. Correct, but misses the entire point of the constraint — the follow-up asks for less than O(n).

If the leftmost and rightmost spines have equal height the subtree is perfect: 2^h - 1 nodes with no traversal. Otherwise recurse into both children — at every level one of them is perfect, so only one path keeps descending: O(log n) levels x O(log n) spine walks.

The one-liner every binary tree supports: count self plus both subtrees. Correct, but misses the entire point of the constraint — the follow-up asks for less than O(n).

If the leftmost and rightmost spines have equal height the subtree is perfect: 2^h - 1 nodes with no traversal. Otherwise recurse into both children — at every level one of them is perfect, so only one path keeps descending: O(log n) levels x O(log n) spine walks.

def countNodes_linear(self, root: "TreeNode | None") -> int:
    if root is None:
        return 0
    return 1 + self.countNodes_linear(root.left) + self.countNodes_linear(root.right)
def countNodes_binary_search(self, root: "TreeNode | None") -> int:
    if root is None:
        return 0

    depth = 0  # edges from the root down to the leftmost leaf
    node = root
    while node.left is not None:
        node = node.left
        depth += 1
    if depth == 0:
        return 1

    def exists(index: int) -> bool:
        """Probe for leaf `index` (0-based) on the last level."""
        lo, hi = 0, (1 << depth) - 1
        node = root
        for _ in range(depth):
            mid = (lo + hi) // 2
            if index <= mid:
                node = node.left
                hi = mid
            else:
                node = node.right
                lo = mid + 1
        return node is not None

    # Leaf 0 always exists; find how many leaves the last level holds.
    lo, hi = 1, (1 << depth) - 1
    while lo <= hi:
        mid = (lo + hi) // 2
        if exists(mid):
            lo = mid + 1
        else:
            hi = mid - 1
    return (1 << depth) - 1 + lo  # full levels + last-level leaves
def countNodes(self, root: "TreeNode | None") -> int:
    if root is None:
        return 0

    left_height = 0
    node = root
    while node is not None:
        left_height += 1
        node = node.left

    right_height = 0
    node = root
    while node is not None:
        right_height += 1
        node = node.right

    if left_height == right_height:  # perfect subtree
        return (1 << left_height) - 1
    return 1 + self.countNodes(root.left) + self.countNodes(root.right)
int countNodesLinear(TreeNode* root) {
    if (root == nullptr) return 0;
    return 1 + countNodesLinear(root->left) + countNodesLinear(root->right);
}
int countNodes(TreeNode* root) {
    if (root == nullptr) return 0;

    int leftHeight = 0;
    for (TreeNode* node = root; node != nullptr; node = node->left) ++leftHeight;
    int rightHeight = 0;
    for (TreeNode* node = root; node != nullptr; node = node->right) ++rightHeight;

    if (leftHeight == rightHeight)  // perfect subtree
        return (1 << leftHeight) - 1;
    return 1 + countNodes(root->left) + countNodes(root->right);
}
pub fn count_nodes_linear(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
    fn count(node: &Option<Rc<RefCell<TreeNode>>>) -> i32 {
        match node {
            None => 0,
            Some(rc) => {
                let n = rc.borrow();
                1 + count(&n.left) + count(&n.right)
            }
        }
    }
    count(&root)
}
pub fn count_nodes(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
    fn count(node: &Option<Rc<RefCell<TreeNode>>>) -> i32 {
        let rc = match node {
            None => return 0,
            Some(rc) => rc,
        };

        let mut left_height = 0;
        let mut walker = Some(Rc::clone(rc));
        while let Some(n) = walker {
            left_height += 1;
            walker = n.borrow().left.clone();
        }
        let mut right_height = 0;
        walker = Some(Rc::clone(rc));
        while let Some(n) = walker {
            right_height += 1;
            walker = n.borrow().right.clone();
        }

        if left_height == right_height {
            return (1 << left_height) - 1; // perfect subtree
        }
        let n = rc.borrow();
        1 + count(&n.left) + count(&n.right)
    }
    count(&root)
}
function countNodesLinear(root: TreeNode | null): number {
  if (root === null) return 0;
  return 1 + countNodesLinear(root.left) + countNodesLinear(root.right);
}
function countNodes(root: TreeNode | null): number {
  if (root === null) return 0;

  let leftHeight = 0;
  for (let node: TreeNode | null = root; node !== null; node = node.left) leftHeight++;
  let rightHeight = 0;
  for (let node: TreeNode | null = root; node !== null; node = node.right) rightHeight++;

  if (leftHeight === rightHeight) {
    return (1 << leftHeight) - 1; // perfect subtree
  }
  return 1 + countNodes(root.left) + countNodes(root.right);
}
func countNodesLinear(root *TreeNode) int {
	if root == nil {
		return 0
	}
	return 1 + countNodesLinear(root.Left) + countNodesLinear(root.Right)
}
func countNodes(root *TreeNode) int {
	if root == nil {
		return 0
	}

	leftHeight := 0
	for node := root; node != nil; node = node.Left {
		leftHeight++
	}
	rightHeight := 0
	for node := root; node != nil; node = node.Right {
		rightHeight++
	}

	if leftHeight == rightHeight { // perfect subtree
		return 1<<leftHeight - 1
	}
	return 1 + countNodes(root.Left) + countNodes(root.Right)
}
func countNodesLinear(_ root: TreeNode?) -> Int {
    guard let root = root else { return 0 }
    return 1 + countNodesLinear(root.left) + countNodesLinear(root.right)
}
func countNodes(_ root: TreeNode?) -> Int {
    guard let root = root else { return 0 }

    var leftHeight = 0
    var node: TreeNode? = root
    while let current = node {
        leftHeight += 1
        node = current.left
    }
    var rightHeight = 0
    node = root
    while let current = node {
        rightHeight += 1
        node = current.right
    }

    if leftHeight == rightHeight {  // perfect subtree
        return (1 << leftHeight) - 1
    }
    return 1 + countNodes(root.left) + countNodes(root.right)
}
Recommended Approach 1 of 3 · Plain recursion (ignores completeness)O(n) time · O(log n) space

30. Lowest Common Ancestor of a Binary Tree

Medium · LC 236

Given a binary tree and two nodes p and q, find their lowest common ancestor, where a node may count as its own ancestor. Recurse post-order so each subtree reports the target it contains or null; the first node whose left and right calls both report something is the split point, while a single report is simply passed upward. The trick is stopping the recursion as soon as it reaches p or q, which handles the case where one target sits below the other without ever searching deeper.

The most concrete mental model: record every node's parent, collect p's ancestor chain into a set, then walk up from q until the chains meet. What you would do with an explicit parent field (see LC 1650), at the cost of an O(n) map plus an O(h) set.

Find the full path to each target with a backtracking DFS, then the LCA is the last node the two paths share. Drops the O(n) parent map to O(h) path storage; still two passes over the tree.

One pass, no stored paths: a subtree reports the target (or LCA) it contains, else None. The first node whose left AND right both report is the split point; if only one side reports, pass that answer up (covers the ancestor case, because the search stops at p or q without going deeper).

Find the full path to each target with a backtracking DFS, then the LCA is the last node the two paths share. Very easy to reason about, but it walks the tree twice and stores both paths.

One pass, no stored paths: each subtree reports the target (or LCA) it contains, else null. The first node whose left AND right both report is the split point; if only one side reports, that answer bubbles up — this also covers the "q below p" case because the search stops at p or q.

Find the full path to each target with a backtracking DFS, then the LCA is the last node the two paths share. Very easy to reason about, but it walks the tree twice and stores both paths.

One pass, no stored paths: each subtree reports the target (or LCA) it contains, else None. The first node whose left AND right both report is the split point; if only one side reports, that answer bubbles up — this also covers the "q below p" case because the search stops at p or q.

Find the full path to each target with a backtracking DFS, then the LCA is the last node the two paths share. Very easy to reason about, but it walks the tree twice and stores both paths.

One pass, no stored paths: each subtree reports the target (or LCA) it contains, else null. The first node whose left AND right both report is the split point; if only one side reports, that answer bubbles up — this also covers the "q below p" case because the search stops at p or q.

Find the full path to each target with a backtracking DFS, then the LCA is the last node the two paths share. Very easy to reason about, but it walks the tree twice and stores both paths.

One pass, no stored paths: each subtree reports the target (or LCA) it contains, else nil. The first node whose left AND right both report is the split point; if only one side reports, that answer bubbles up — this also covers the "q below p" case because the search stops at p or q.

Find the full path to each target with a backtracking DFS, then the LCA is the last node the two paths share. Very easy to reason about, but it walks the tree twice and stores both paths.

One pass, no stored paths: each subtree reports the target (or LCA) it contains, else nil. The first node whose left AND right both report is the split point; if only one side reports, that answer bubbles up — this also covers the "q below p" case because the search stops at p or q.

def lowestCommonAncestor_parent_map(self, root: "TreeNode | None",
                                    p: "TreeNode", q: "TreeNode") -> "TreeNode | None":
    parent: "dict[TreeNode, TreeNode | None]" = {root: None}
    stack = [root]
    while p not in parent or q not in parent:
        node = stack.pop()
        for child in (node.left, node.right):
            if child is not None:
                parent[child] = node
                stack.append(child)

    ancestors = set()
    node: "TreeNode | None" = p
    while node is not None:
        ancestors.add(node)
        node = parent[node]

    node = q
    while node not in ancestors:
        node = parent[node]
    return node
def lowestCommonAncestor_paths(self, root: "TreeNode | None",
                               p: "TreeNode", q: "TreeNode") -> "TreeNode | None":
    def path_to(target: "TreeNode") -> "list[TreeNode]":
        path: "list[TreeNode]" = []

        def dfs(node: "TreeNode | None") -> bool:
            if node is None:
                return False
            path.append(node)
            if node is target or dfs(node.left) or dfs(node.right):
                return True
            path.pop()
            return False

        dfs(root)
        return path

    lca = None
    for a, b in zip(path_to(p), path_to(q)):
        if a is b:
            lca = a
        else:
            break
    return lca
def lowestCommonAncestor(self, root: "TreeNode | None", p: "TreeNode",
                         q: "TreeNode") -> "TreeNode | None":
    if root is None or root is p or root is q:
        return root
    left = self.lowestCommonAncestor(root.left, p, q)
    right = self.lowestCommonAncestor(root.right, p, q)
    if left is not None and right is not None:
        return root  # p and q found in different subtrees: split point
    return left if left is not None else right
TreeNode* lowestCommonAncestorPaths(TreeNode* root, TreeNode* p, TreeNode* q) {
    auto pathTo = [root](auto&& self, TreeNode* node, TreeNode* target,
                         std::vector<TreeNode*>& path) -> bool {
        if (node == nullptr) return false;
        path.push_back(node);
        if (node == target || self(self, node->left, target, path) ||
            self(self, node->right, target, path)) {
            return true;
        }
        path.pop_back();
        return false;
    };
    std::vector<TreeNode*> pathP, pathQ;
    pathTo(pathTo, root, p, pathP);
    pathTo(pathTo, root, q, pathQ);
    TreeNode* lca = nullptr;
    for (size_t i = 0; i < pathP.size() && i < pathQ.size() && pathP[i] == pathQ[i]; ++i) {
        lca = pathP[i];
    }
    return lca;
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    if (root == nullptr || root == p || root == q) return root;
    TreeNode* left = lowestCommonAncestor(root->left, p, q);
    TreeNode* right = lowestCommonAncestor(root->right, p, q);
    if (left != nullptr && right != nullptr) return root;  // split point
    return left != nullptr ? left : right;
}
pub fn lowest_common_ancestor_paths(
    root: Option<Rc<RefCell<TreeNode>>>,
    p: Option<Rc<RefCell<TreeNode>>>,
    q: Option<Rc<RefCell<TreeNode>>>,
) -> Option<Rc<RefCell<TreeNode>>> {
    fn path_to(
        node: &Option<Rc<RefCell<TreeNode>>>,
        target: i32,
        path: &mut Vec<Rc<RefCell<TreeNode>>>,
    ) -> bool {
        let rc = match node {
            None => return false,
            Some(rc) => rc,
        };
        path.push(Rc::clone(rc));
        let n = rc.borrow();
        if n.val == target || path_to(&n.left, target, path) || path_to(&n.right, target, path)
        {
            return true;
        }
        path.pop();
        false
    }

    let p_val = p.as_ref()?.borrow().val;
    let q_val = q.as_ref()?.borrow().val;
    let mut path_p = Vec::new();
    let mut path_q = Vec::new();
    path_to(&root, p_val, &mut path_p);
    path_to(&root, q_val, &mut path_q);

    let mut lca = None;
    for (a, b) in path_p.iter().zip(path_q.iter()) {
        if Rc::ptr_eq(a, b) {
            lca = Some(Rc::clone(a));
        } else {
            break;
        }
    }
    lca
}
pub fn lowest_common_ancestor(
    root: Option<Rc<RefCell<TreeNode>>>,
    p: Option<Rc<RefCell<TreeNode>>>,
    q: Option<Rc<RefCell<TreeNode>>>,
) -> Option<Rc<RefCell<TreeNode>>> {
    let p_val = p.as_ref()?.borrow().val;
    let q_val = q.as_ref()?.borrow().val;

    fn dfs(
        node: &Option<Rc<RefCell<TreeNode>>>,
        p_val: i32,
        q_val: i32,
    ) -> Option<Rc<RefCell<TreeNode>>> {
        let rc = node.as_ref()?;
        let n = rc.borrow();
        if n.val == p_val || n.val == q_val {
            return Some(Rc::clone(rc));
        }
        let left = dfs(&n.left, p_val, q_val);
        let right = dfs(&n.right, p_val, q_val);
        match (left, right) {
            // p and q found in different subtrees: split point
            (Some(_), Some(_)) => Some(Rc::clone(rc)),
            (left, right) => left.or(right),
        }
    }
    dfs(&root, p_val, q_val)
}
function lowestCommonAncestorPaths(
  root: TreeNode | null,
  p: TreeNode | null,
  q: TreeNode | null
): TreeNode | null {
  const pathTo = (target: TreeNode | null): TreeNode[] => {
    const path: TreeNode[] = [];
    const dfs = (node: TreeNode | null): boolean => {
      if (node === null) return false;
      path.push(node);
      if (node === target || dfs(node.left) || dfs(node.right)) return true;
      path.pop();
      return false;
    };
    dfs(root);
    return path;
  };

  const pathP = pathTo(p);
  const pathQ = pathTo(q);
  let lca: TreeNode | null = null;
  for (let i = 0; i < pathP.length && i < pathQ.length && pathP[i] === pathQ[i]; i++) {
    lca = pathP[i];
  }
  return lca;
}
function lowestCommonAncestor(
  root: TreeNode | null,
  p: TreeNode | null,
  q: TreeNode | null
): TreeNode | null {
  if (root === null || root === p || root === q) return root;
  const left = lowestCommonAncestor(root.left, p, q);
  const right = lowestCommonAncestor(root.right, p, q);
  if (left !== null && right !== null) {
    return root; // p and q found in different subtrees: split point
  }
  return left !== null ? left : right;
}
func lowestCommonAncestorPaths(root, p, q *TreeNode) *TreeNode {
	pathTo := func(target *TreeNode) []*TreeNode {
		var path []*TreeNode
		var dfs func(node *TreeNode) bool
		dfs = func(node *TreeNode) bool {
			if node == nil {
				return false
			}
			path = append(path, node)
			if node == target || dfs(node.Left) || dfs(node.Right) {
				return true
			}
			path = path[:len(path)-1]
			return false
		}
		dfs(root)
		return path
	}

	pathP := pathTo(p)
	pathQ := pathTo(q)
	var lca *TreeNode
	for i := 0; i < len(pathP) && i < len(pathQ) && pathP[i] == pathQ[i]; i++ {
		lca = pathP[i]
	}
	return lca
}
func lowestCommonAncestor(root, p, q *TreeNode) *TreeNode {
	if root == nil || root == p || root == q {
		return root
	}
	left := lowestCommonAncestor(root.Left, p, q)
	right := lowestCommonAncestor(root.Right, p, q)
	if left != nil && right != nil {
		return root // p and q found in different subtrees: split point
	}
	if left != nil {
		return left
	}
	return right
}
func lowestCommonAncestorPaths(_ root: TreeNode?, _ p: TreeNode?, _ q: TreeNode?) -> TreeNode? {
    func pathTo(_ target: TreeNode?) -> [TreeNode] {
        var path: [TreeNode] = []
        func dfs(_ node: TreeNode?) -> Bool {
            guard let node = node else { return false }
            path.append(node)
            if node === target || dfs(node.left) || dfs(node.right) { return true }
            path.removeLast()
            return false
        }
        _ = dfs(root)
        return path
    }

    let pathP = pathTo(p)
    let pathQ = pathTo(q)
    var lca: TreeNode? = nil
    for (a, b) in zip(pathP, pathQ) {
        if a === b { lca = a } else { break }
    }
    return lca
}
func lowestCommonAncestor(_ root: TreeNode?, _ p: TreeNode?, _ q: TreeNode?) -> TreeNode? {
    guard let root = root else { return nil }
    if root === p || root === q { return root }
    let left = lowestCommonAncestor(root.left, p, q)
    let right = lowestCommonAncestor(root.right, p, q)
    if left != nil && right != nil {
        return root  // p and q found in different subtrees: split point
    }
    return left ?? right
}
Recommended Approach 1 of 3 · Parent pointers + ancestor setO(n) time · O(n) space

Binary Tree BFS

31. Average of Levels in Binary Tree

Easy · LC 637

Given a binary tree, return the average value of the nodes on each level from top to bottom. Run a breadth-first traversal where each pass drains exactly one level: record the queue's current length, pop that many nodes while summing their values and enqueueing children, then divide the sum by the width. The trick is snapshotting the level width before the inner loop, which cleanly separates levels inside a single queue — and with no recursion there is no depth hazard on skewed trees.

One pass, no queue: bucket every value by its depth and divide at the end. Recursion depth equals tree height, though — a fully skewed 10^4-node tree would trip Python's ~1000-frame limit.

The queue holds exactly one level at a time, so each level's sum and width fall out of the traversal for free — and with no recursion there is no depth hazard on skewed trees.

One pass, no queue: bucket every value by its depth and divide at the end. Recursion depth equals tree height, though — a fully skewed tree leans on the call stack.

The queue holds exactly one level at a time, so each level's sum and width fall out of the traversal for free — and with no recursion there is no depth hazard on skewed trees.

One pass, no queue: bucket every value by its depth and divide at the end. Recursion depth equals tree height, though — a fully skewed tree leans on the call stack.

The queue holds exactly one level at a time, so each level's sum and width fall out of the traversal for free — and with no recursion there is no depth hazard on skewed trees.

One pass, no queue: bucket every value by its depth and divide at the end. Recursion depth equals tree height, though — a fully skewed tree leans on the call stack.

The current array holds exactly one level at a time, so each level's sum and width fall out of the traversal for free — and with no recursion there is no depth hazard on skewed trees.

One pass, no queue: bucket every value by its depth and divide at the end. Recursion depth equals tree height, though — a fully skewed tree leans on the call stack.

The queue holds exactly one level at a time, so each level's sum and width fall out of the traversal for free — and with no recursion there is no depth hazard on skewed trees.

One pass, no queue: bucket every value by its depth and divide at the end. Recursion depth equals tree height, though — a fully skewed tree leans on the call stack.

The current array holds exactly one level at a time, so each level's sum and width fall out of the traversal for free — and with no recursion there is no depth hazard on skewed trees.

def averageOfLevels_dfs(self, root: Optional[TreeNode]) -> List[float]:
    sums: List[int] = []
    counts: List[int] = []

    def visit(node: Optional[TreeNode], depth: int) -> None:
        if not node:
            return
        if depth == len(sums):
            sums.append(0)
            counts.append(0)
        sums[depth] += node.val
        counts[depth] += 1
        visit(node.left, depth + 1)
        visit(node.right, depth + 1)

    visit(root, 0)
    return [s / c for s, c in zip(sums, counts)]
def averageOfLevels(self, root: Optional[TreeNode]) -> List[float]:
    averages: List[float] = []
    queue = deque([root] if root else [])
    while queue:
        width = len(queue)
        total = 0
        for _ in range(width):
            node = queue.popleft()
            total += node.val
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        averages.append(total / width)
    return averages
vector<double> averageOfLevelsDfs(TreeNode *root) {
    vector<double> sums;
    vector<int> counts;
    function<void(TreeNode *, size_t)> visit = [&](TreeNode *node,
                                                   size_t depth) {
        if (!node) return;
        if (depth == sums.size()) {
            sums.push_back(0.0);
            counts.push_back(0);
        }
        sums[depth] += node->val;
        ++counts[depth];
        visit(node->left, depth + 1);
        visit(node->right, depth + 1);
    };
    visit(root, 0);
    vector<double> averages;
    averages.reserve(sums.size());
    for (size_t d = 0; d < sums.size(); ++d)
        averages.push_back(sums[d] / counts[d]);
    return averages;
}
vector<double> averageOfLevels(TreeNode *root) {
    vector<double> averages;
    queue<TreeNode *> q;
    if (root) q.push(root);
    while (!q.empty()) {
        size_t width = q.size();
        double total = 0.0;
        for (size_t i = 0; i < width; ++i) {
            TreeNode *node = q.front();
            q.pop();
            total += node->val;
            if (node->left) q.push(node->left);
            if (node->right) q.push(node->right);
        }
        averages.push_back(total / static_cast<double>(width));
    }
    return averages;
}
pub fn average_of_levels_dfs(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<f64> {
    fn visit(
        node: &Option<Rc<RefCell<TreeNode>>>,
        depth: usize,
        sums: &mut Vec<f64>,
        counts: &mut Vec<u32>,
    ) {
        if let Some(rc) = node {
            let n = rc.borrow();
            if depth == sums.len() {
                sums.push(0.0);
                counts.push(0);
            }
            sums[depth] += f64::from(n.val);
            counts[depth] += 1;
            visit(&n.left, depth + 1, sums, counts);
            visit(&n.right, depth + 1, sums, counts);
        }
    }

    let mut sums = Vec::new();
    let mut counts = Vec::new();
    visit(&root, 0, &mut sums, &mut counts);
    sums.iter()
        .zip(&counts)
        .map(|(s, c)| s / f64::from(*c))
        .collect()
}
pub fn average_of_levels(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<f64> {
    let mut averages = Vec::new();
    let mut queue = VecDeque::new();
    if let Some(r) = root {
        queue.push_back(r);
    }
    while !queue.is_empty() {
        let width = queue.len();
        let mut total = 0.0;
        for _ in 0..width {
            let node = queue.pop_front().unwrap();
            let n = node.borrow();
            total += f64::from(n.val);
            if let Some(left) = n.left.clone() {
                queue.push_back(left);
            }
            if let Some(right) = n.right.clone() {
                queue.push_back(right);
            }
        }
        averages.push(total / width as f64);
    }
    averages
}
function averageOfLevelsDfs(root: TreeNode | null): number[] {
    const sums: number[] = [];
    const counts: number[] = [];
    const visit = (node: TreeNode | null, depth: number): void => {
        if (!node) return;
        if (depth === sums.length) {
            sums.push(0);
            counts.push(0);
        }
        sums[depth] += node.val;
        counts[depth] += 1;
        visit(node.left, depth + 1);
        visit(node.right, depth + 1);
    };
    visit(root, 0);
    return sums.map((s, d) => s / counts[d]);
}
function averageOfLevels(root: TreeNode | null): number[] {
    const averages: number[] = [];
    let level: TreeNode[] = root ? [root] : [];
    while (level.length > 0) {
        let total = 0;
        const next: TreeNode[] = [];
        for (const node of level) {
            total += node.val;
            if (node.left) next.push(node.left);
            if (node.right) next.push(node.right);
        }
        averages.push(total / level.length);
        level = next;
    }
    return averages;
}
func averageOfLevelsDfs(root *TreeNode) []float64 {
	sums := []float64{}
	counts := []int{}
	var visit func(node *TreeNode, depth int)
	visit = func(node *TreeNode, depth int) {
		if node == nil {
			return
		}
		if depth == len(sums) {
			sums = append(sums, 0)
			counts = append(counts, 0)
		}
		sums[depth] += float64(node.Val)
		counts[depth]++
		visit(node.Left, depth+1)
		visit(node.Right, depth+1)
	}
	visit(root, 0)
	averages := make([]float64, 0, len(sums))
	for d := range sums {
		averages = append(averages, sums[d]/float64(counts[d]))
	}
	return averages
}
func averageOfLevels(root *TreeNode) []float64 {
	averages := []float64{}
	queue := []*TreeNode{}
	if root != nil {
		queue = append(queue, root)
	}
	for len(queue) > 0 {
		width := len(queue)
		total := 0.0
		for i := 0; i < width; i++ {
			node := queue[0]
			queue = queue[1:]
			total += float64(node.Val)
			if node.Left != nil {
				queue = append(queue, node.Left)
			}
			if node.Right != nil {
				queue = append(queue, node.Right)
			}
		}
		averages = append(averages, total/float64(width))
	}
	return averages
}
func averageOfLevelsDfs(_ root: TreeNode?) -> [Double] {
    var sums: [Double] = []
    var counts: [Int] = []
    func visit(_ node: TreeNode?, _ depth: Int) {
        guard let node = node else { return }
        if depth == sums.count {
            sums.append(0)
            counts.append(0)
        }
        sums[depth] += Double(node.val)
        counts[depth] += 1
        visit(node.left, depth + 1)
        visit(node.right, depth + 1)
    }
    visit(root, 0)
    return zip(sums, counts).map { $0 / Double($1) }
}
func averageOfLevels(_ root: TreeNode?) -> [Double] {
    var averages: [Double] = []
    var queue: [TreeNode] = []
    if let root = root { queue.append(root) }
    while !queue.isEmpty {
        var total = 0.0
        var nextQueue: [TreeNode] = []
        for node in queue {
            total += Double(node.val)
            if let left = node.left { nextQueue.append(left) }
            if let right = node.right { nextQueue.append(right) }
        }
        averages.append(total / Double(queue.count))
        queue = nextQueue
    }
    return averages
}
Recommended Approach 1 of 2 · DFS tallying sum and count per depthO(n) time · O(h) recursion plus one tally slot per level space

32. Binary Tree Zigzag Level Order Traversal

Medium · LC 103

Given a binary tree, return its level order traversal with the direction alternating: left to right on the first level, right to left on the next, and so on. Do a plain breadth-first traversal collecting each level into a list, and reverse that list before appending it whenever the number of completed levels is odd. The trick is that the after-the-fact reverse is cheaper than it looks — each node is flipped at most once across the whole tree, so it beats deque-based constructions in both simplicity and constants.

Preorder DFS visits left before right, so each depth bucket fills in left-to-right order; odd depths prepend instead of append. Recursion depth equals tree height — a fully skewed 2000-node tree would trip Python's ~1000-frame limit.

Iterative, so no recursion-depth hazard; appendleft on right-to-left levels writes each level already flipped, skipping any second pass. Costs a deque and a list(level) copy per level.

Same bounds, fewer moving parts: plain list appends beat deque constants, no per-level copy, and the flip touches each node at most once across the whole tree — the cleanest version.

Preorder DFS visits left before right, so each depth bucket fills in left-to-right order; a final pass flips the odd buckets. Recursion depth equals tree height — a skewed tree leans on the call stack, and the flip is a whole second pass.

Iterative, so no recursion hazard, and no reversal pass either: the queue length gives each level's width up front, so values on right-to-left levels land directly at index width-1-i. The mirror arithmetic is easy to get wrong, though.

Same bounds with the simplest loop body: plain appends, then one reverse per odd level — that flip touches each node at most once across the whole tree, so nothing is lost versus the mirror arithmetic and there is no index to fumble.

Preorder DFS visits left before right, so each depth bucket fills in left-to-right order; a final pass flips the odd buckets. Recursion depth equals tree height — a skewed tree leans on the call stack, and the flip is a whole second pass.

Iterative, so no recursion hazard, and no reversal pass either: the queue length gives each level's width up front, so values on right-to-left levels land directly at index width-1-i. The mirror arithmetic is easy to get wrong, though.

Same bounds with the simplest loop body: plain pushes, then one reverse per odd level — that flip touches each node at most once across the whole tree, so nothing is lost versus the mirror arithmetic and there is no index to fumble.

Preorder DFS visits left before right, so each depth bucket fills in left-to-right order; a final pass flips the odd buckets. Recursion depth equals tree height — a skewed tree leans on the call stack, and the flip is a whole second pass.

Iterative, so no recursion hazard, and no reversal pass either: the level array's width is known up front, so values on right-to-left levels land directly at index width-1-i. The mirror arithmetic is easy to get wrong, though.

Same bounds with the simplest loop body: plain pushes, then one reverse per odd level — that flip touches each node at most once across the whole tree, so nothing is lost versus the mirror arithmetic and there is no index to fumble.

Preorder DFS visits left before right, so each depth bucket fills in left-to-right order; a final pass flips the odd buckets. Recursion depth equals tree height — a skewed tree leans on the call stack, and the flip is a whole second pass.

Iterative, so no recursion hazard, and no reversal pass either: the queue length gives each level's width up front, so values on right-to-left levels land directly at index width-1-i. The mirror arithmetic is easy to get wrong, though.

Same bounds with the simplest loop body: plain appends, then one reverse per odd level — that flip touches each node at most once across the whole tree, so nothing is lost versus the mirror arithmetic and there is no index to fumble.

Preorder DFS visits left before right, so each depth bucket fills in left-to-right order; a final pass flips the odd buckets. Recursion depth equals tree height — a skewed tree leans on the call stack, and the flip is a whole second pass.

Iterative, so no recursion hazard, and no reversal pass either: the level array's width is known up front, so values on right-to-left levels land directly at index width-1-i. The mirror arithmetic is easy to get wrong, though.

Same bounds with the simplest loop body: plain appends, then one reverse per odd level — that flip touches each node at most once across the whole tree, so nothing is lost versus the mirror arithmetic and there is no index to fumble.

def zigzagLevelOrder_dfs(self, root: Optional[TreeNode]) -> List[List[int]]:
    levels: List[deque] = []

    def visit(node: Optional[TreeNode], depth: int) -> None:
        if not node:
            return
        if depth == len(levels):
            levels.append(deque())
        if depth % 2:
            levels[depth].appendleft(node.val)
        else:
            levels[depth].append(node.val)
        visit(node.left, depth + 1)
        visit(node.right, depth + 1)

    visit(root, 0)
    return [list(level) for level in levels]
def zigzagLevelOrder_deque(self, root: Optional[TreeNode]) -> List[List[int]]:
    result: List[List[int]] = []
    queue = deque([root] if root else [])
    left_to_right = True
    while queue:
        level: deque = deque()
        for _ in range(len(queue)):
            node = queue.popleft()
            if left_to_right:
                level.append(node.val)
            else:
                level.appendleft(node.val)
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        result.append(list(level))
        left_to_right = not left_to_right
    return result
def zigzagLevelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
    result: List[List[int]] = []
    queue = deque([root] if root else [])
    while queue:
        level = []
        for _ in range(len(queue)):
            node = queue.popleft()
            level.append(node.val)
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        if len(result) % 2:
            level.reverse()
        result.append(level)
    return result
vector<vector<int>> zigzagLevelOrderDfs(TreeNode *root) {
    vector<vector<int>> levels;
    function<void(TreeNode *, size_t)> visit = [&](TreeNode *node,
                                                   size_t depth) {
        if (!node) return;
        if (depth == levels.size()) levels.push_back({});
        levels[depth].push_back(node->val);
        visit(node->left, depth + 1);
        visit(node->right, depth + 1);
    };
    visit(root, 0);
    for (size_t d = 1; d < levels.size(); d += 2)
        reverse(levels[d].begin(), levels[d].end());
    return levels;
}
vector<vector<int>> zigzagLevelOrderMirror(TreeNode *root) {
    vector<vector<int>> result;
    queue<TreeNode *> q;
    if (root) q.push(root);
    bool leftToRight = true;
    while (!q.empty()) {
        size_t width = q.size();
        vector<int> level(width);
        for (size_t i = 0; i < width; ++i) {
            TreeNode *node = q.front();
            q.pop();
            level[leftToRight ? i : width - 1 - i] = node->val;
            if (node->left) q.push(node->left);
            if (node->right) q.push(node->right);
        }
        result.push_back(move(level));
        leftToRight = !leftToRight;
    }
    return result;
}
vector<vector<int>> zigzagLevelOrder(TreeNode *root) {
    vector<vector<int>> result;
    queue<TreeNode *> q;
    if (root) q.push(root);
    while (!q.empty()) {
        size_t width = q.size();
        vector<int> level;
        level.reserve(width);
        for (size_t i = 0; i < width; ++i) {
            TreeNode *node = q.front();
            q.pop();
            level.push_back(node->val);
            if (node->left) q.push(node->left);
            if (node->right) q.push(node->right);
        }
        if (result.size() % 2 == 1) reverse(level.begin(), level.end());
        result.push_back(move(level));
    }
    return result;
}
pub fn zigzag_level_order_dfs(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<Vec<i32>> {
    fn visit(
        node: &Option<Rc<RefCell<TreeNode>>>,
        depth: usize,
        levels: &mut Vec<Vec<i32>>,
    ) {
        if let Some(rc) = node {
            let n = rc.borrow();
            if depth == levels.len() {
                levels.push(Vec::new());
            }
            levels[depth].push(n.val);
            visit(&n.left, depth + 1, levels);
            visit(&n.right, depth + 1, levels);
        }
    }

    let mut levels = Vec::new();
    visit(&root, 0, &mut levels);
    for level in levels.iter_mut().skip(1).step_by(2) {
        level.reverse();
    }
    levels
}
pub fn zigzag_level_order_mirror(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<Vec<i32>> {
    let mut result: Vec<Vec<i32>> = Vec::new();
    let mut queue = VecDeque::new();
    if let Some(r) = root {
        queue.push_back(r);
    }
    let mut left_to_right = true;
    while !queue.is_empty() {
        let width = queue.len();
        let mut level = vec![0; width];
        for i in 0..width {
            let node = queue.pop_front().unwrap();
            let n = node.borrow();
            let slot = if left_to_right { i } else { width - 1 - i };
            level[slot] = n.val;
            if let Some(left) = n.left.clone() {
                queue.push_back(left);
            }
            if let Some(right) = n.right.clone() {
                queue.push_back(right);
            }
        }
        result.push(level);
        left_to_right = !left_to_right;
    }
    result
}
pub fn zigzag_level_order(root: Option<Rc<RefCell<TreeNode>>>) -> Vec<Vec<i32>> {
    let mut result: Vec<Vec<i32>> = Vec::new();
    let mut queue = VecDeque::new();
    if let Some(r) = root {
        queue.push_back(r);
    }
    while !queue.is_empty() {
        let width = queue.len();
        let mut level = Vec::with_capacity(width);
        for _ in 0..width {
            let node = queue.pop_front().unwrap();
            let n = node.borrow();
            level.push(n.val);
            if let Some(left) = n.left.clone() {
                queue.push_back(left);
            }
            if let Some(right) = n.right.clone() {
                queue.push_back(right);
            }
        }
        if result.len() % 2 == 1 {
            level.reverse();
        }
        result.push(level);
    }
    result
}
function zigzagLevelOrderDfs(root: TreeNode | null): number[][] {
    const levels: number[][] = [];
    const visit = (node: TreeNode | null, depth: number): void => {
        if (!node) return;
        if (depth === levels.length) levels.push([]);
        levels[depth].push(node.val);
        visit(node.left, depth + 1);
        visit(node.right, depth + 1);
    };
    visit(root, 0);
    for (let d = 1; d < levels.length; d += 2) {
        levels[d].reverse();
    }
    return levels;
}
function zigzagLevelOrderMirror(root: TreeNode | null): number[][] {
    const result: number[][] = [];
    let level: TreeNode[] = root ? [root] : [];
    let leftToRight = true;
    while (level.length > 0) {
        const width = level.length;
        const values: number[] = new Array(width);
        const next: TreeNode[] = [];
        for (let i = 0; i < width; i++) {
            const node = level[i];
            values[leftToRight ? i : width - 1 - i] = node.val;
            if (node.left) next.push(node.left);
            if (node.right) next.push(node.right);
        }
        result.push(values);
        level = next;
        leftToRight = !leftToRight;
    }
    return result;
}
function zigzagLevelOrder(root: TreeNode | null): number[][] {
    const result: number[][] = [];
    let level: TreeNode[] = root ? [root] : [];
    while (level.length > 0) {
        const values: number[] = [];
        const next: TreeNode[] = [];
        for (const node of level) {
            values.push(node.val);
            if (node.left) next.push(node.left);
            if (node.right) next.push(node.right);
        }
        if (result.length % 2 === 1) values.reverse();
        result.push(values);
        level = next;
    }
    return result;
}
func zigzagLevelOrderDfs(root *TreeNode) [][]int {
	levels := [][]int{}
	var visit func(node *TreeNode, depth int)
	visit = func(node *TreeNode, depth int) {
		if node == nil {
			return
		}
		if depth == len(levels) {
			levels = append(levels, []int{})
		}
		levels[depth] = append(levels[depth], node.Val)
		visit(node.Left, depth+1)
		visit(node.Right, depth+1)
	}
	visit(root, 0)
	for d := 1; d < len(levels); d += 2 {
		level := levels[d]
		for l, r := 0, len(level)-1; l < r; l, r = l+1, r-1 {
			level[l], level[r] = level[r], level[l]
		}
	}
	return levels
}
func zigzagLevelOrderMirror(root *TreeNode) [][]int {
	result := [][]int{}
	queue := []*TreeNode{}
	if root != nil {
		queue = append(queue, root)
	}
	leftToRight := true
	for len(queue) > 0 {
		width := len(queue)
		level := make([]int, width)
		for i := 0; i < width; i++ {
			node := queue[0]
			queue = queue[1:]
			if leftToRight {
				level[i] = node.Val
			} else {
				level[width-1-i] = node.Val
			}
			if node.Left != nil {
				queue = append(queue, node.Left)
			}
			if node.Right != nil {
				queue = append(queue, node.Right)
			}
		}
		result = append(result, level)
		leftToRight = !leftToRight
	}
	return result
}
func zigzagLevelOrder(root *TreeNode) [][]int {
	result := [][]int{}
	queue := []*TreeNode{}
	if root != nil {
		queue = append(queue, root)
	}
	for len(queue) > 0 {
		width := len(queue)
		level := make([]int, 0, width)
		for i := 0; i < width; i++ {
			node := queue[0]
			queue = queue[1:]
			level = append(level, node.Val)
			if node.Left != nil {
				queue = append(queue, node.Left)
			}
			if node.Right != nil {
				queue = append(queue, node.Right)
			}
		}
		if len(result)%2 == 1 {
			for l, r := 0, len(level)-1; l < r; l, r = l+1, r-1 {
				level[l], level[r] = level[r], level[l]
			}
		}
		result = append(result, level)
	}
	return result
}
func zigzagLevelOrderDfs(_ root: TreeNode?) -> [[Int]] {
    var levels: [[Int]] = []
    func visit(_ node: TreeNode?, _ depth: Int) {
        guard let node = node else { return }
        if depth == levels.count {
            levels.append([])
        }
        levels[depth].append(node.val)
        visit(node.left, depth + 1)
        visit(node.right, depth + 1)
    }
    visit(root, 0)
    for d in stride(from: 1, to: levels.count, by: 2) {
        levels[d].reverse()
    }
    return levels
}
func zigzagLevelOrderMirror(_ root: TreeNode?) -> [[Int]] {
    var result: [[Int]] = []
    var queue: [TreeNode] = []
    if let root = root { queue.append(root) }
    var leftToRight = true
    while !queue.isEmpty {
        let width = queue.count
        var values = [Int](repeating: 0, count: width)
        var nextQueue: [TreeNode] = []
        for (i, node) in queue.enumerated() {
            values[leftToRight ? i : width - 1 - i] = node.val
            if let left = node.left { nextQueue.append(left) }
            if let right = node.right { nextQueue.append(right) }
        }
        result.append(values)
        queue = nextQueue
        leftToRight.toggle()
    }
    return result
}
func zigzagLevelOrder(_ root: TreeNode?) -> [[Int]] {
    var result: [[Int]] = []
    var queue: [TreeNode] = []
    if let root = root { queue.append(root) }
    while !queue.isEmpty {
        var level: [Int] = []
        var nextQueue: [TreeNode] = []
        for node in queue {
            level.append(node.val)
            if let left = node.left { nextQueue.append(left) }
            if let right = node.right { nextQueue.append(right) }
        }
        if result.count % 2 == 1 { level.reverse() }
        result.append(level)
        queue = nextQueue
    }
    return result
}
Recommended Approach 1 of 3 · DFS bucketing values by depthO(n) time · O(h) recursion (plus the output) space

Binary Search Tree

33. Minimum Absolute Difference in BST

Easy · LC 530

Given a binary search tree, return the minimum absolute difference between the values of any two nodes. Run a Morris inorder traversal, which visits values in sorted order by temporarily threading each inorder predecessor's right pointer back to the current node, and track the smallest gap between consecutive values. The insight is that in sorted order the answer must occur between adjacent neighbors, and the threading removes both recursion and stack, leaving an O(n) walk in O(1) space with all links restored.

Ignores the BST property entirely — this works on any binary tree. But sorting recreates an order the tree already stores; the rungs below read it out directly and drop the log factor.

Inorder visits BST values in sorted order, so only the previous value need be remembered — no list, no sort. Leans on the interpreter's call stack, though: a skewed 10^4-node tree would trip Python's ~1000-frame recursion limit.

The same walk with the recursion swapped for an explicit stack — identical bounds, but no recursion-depth worries on a skewed 10^4-node tree.

The same sorted walk with the stack gone too: temporarily thread each node's inorder predecessor's right pointer back to it, so the traversal needs no extra memory at all (links are restored).

Ignores the BST property entirely — this works on any binary tree. But sorting recreates an order the tree already stores; the rungs below read it out directly and drop the log factor.

Inorder visits BST values in sorted order, so only the previous value need be remembered — no vector, no sort. Recursion depth equals tree height, though, so a skewed tree leans on the call stack.

The same walk with the recursion swapped for an explicit stack — identical bounds, but no recursion-depth worries on a skewed 10^4-node tree.

Ignores the BST property entirely — this works on any binary tree. But sorting recreates an order the tree already stores; the inorder rung below reads it out directly and drops the log factor.

Inorder visits BST values in sorted order, so only the previous value need be remembered — no buffer, no sort, and the log factor is gone.

Ignores the BST property entirely — this works on any binary tree. But sorting recreates an order the tree already stores; the rungs below read it out directly and drop the log factor.

Inorder visits BST values in sorted order, so only the previous value need be remembered — no array, no sort. Recursion depth equals tree height, though, so a skewed tree leans on the call stack.

The same walk with the recursion swapped for an explicit stack — identical bounds, but no recursion-depth worries on a skewed 10^4-node tree.

Ignores the BST property entirely — this works on any binary tree. But sorting recreates an order the tree already stores; the rungs below read it out directly and drop the log factor.

Inorder visits BST values in sorted order, so only the previous value need be remembered — no slice, no sort. Recursion depth equals tree height, though, so a skewed tree leans on the call stack.

The same walk with the recursion swapped for an explicit stack — identical bounds, but no recursion-depth worries on a skewed 10^4-node tree.

Ignores the BST property entirely — this works on any binary tree. But sorting recreates an order the tree already stores; the rungs below read it out directly and drop the log factor.

Inorder visits BST values in sorted order, so only the previous value need be remembered — no array, no sort. Recursion depth equals tree height, though, so a skewed tree leans on the call stack.

The same walk with the recursion swapped for an explicit stack — identical bounds, but no recursion-depth worries on a skewed 10^4-node tree.

def getMinimumDifference_sorted(self, root: Optional[TreeNode]) -> int:
    values = []
    stack = [root]
    while stack:
        node = stack.pop()
        if node:
            values.append(node.val)
            stack.append(node.left)
            stack.append(node.right)
    values.sort()
    return min(b - a for a, b in zip(values, values[1:]))
def getMinimumDifference_recursive(self, root: Optional[TreeNode]) -> int:
    prev = None
    best = float("inf")

    def inorder(node: Optional[TreeNode]) -> None:
        nonlocal prev, best
        if not node:
            return
        inorder(node.left)
        if prev is not None:
            best = min(best, node.val - prev)
        prev = node.val
        inorder(node.right)

    inorder(root)
    return best
def getMinimumDifference_stack(self, root: Optional[TreeNode]) -> int:
    stack = []
    node = root
    prev = None
    best = float("inf")
    while stack or node:
        while node:
            stack.append(node)
            node = node.left
        node = stack.pop()
        if prev is not None:
            best = min(best, node.val - prev)
        prev = node.val
        node = node.right
    return best  # n >= 2, so at least one gap was measured
def getMinimumDifference(self, root: Optional[TreeNode]) -> int:
    prev = None
    best = float("inf")
    node = root
    while node:
        if node.left:
            pred = node.left
            while pred.right and pred.right is not node:
                pred = pred.right
            if pred.right is None:  # first visit: thread and dive left
                pred.right = node
                node = node.left
                continue
            pred.right = None  # second visit: unthread, fall through
        if prev is not None:
            best = min(best, node.val - prev)
        prev = node.val
        node = node.right
    return best
int getMinimumDifferenceSorted(TreeNode *root) {
    vector<int> vals;
    function<void(TreeNode *)> collect = [&](TreeNode *node) {
        if (!node) return;
        vals.push_back(node->val);
        collect(node->left);
        collect(node->right);
    };
    collect(root);
    sort(vals.begin(), vals.end());
    int best = INT_MAX;
    for (size_t i = 1; i < vals.size(); ++i)
        best = min(best, vals[i] - vals[i - 1]);
    return best;
}
int getMinimumDifferenceRecursive(TreeNode *root) {
    int best = INT_MAX;
    int prev = -1;  // values are >= 0 per the constraints
    function<void(TreeNode *)> inorder = [&](TreeNode *node) {
        if (!node) return;
        inorder(node->left);
        if (prev >= 0) best = min(best, node->val - prev);
        prev = node->val;
        inorder(node->right);
    };
    inorder(root);
    return best;
}
int getMinimumDifference(TreeNode *root) {
    stack<TreeNode *> st;
    TreeNode *node = root;
    int best = INT_MAX;
    bool hasPrev = false;
    int prev = 0;
    while (!st.empty() || node) {
        while (node) {
            st.push(node);
            node = node->left;
        }
        node = st.top();
        st.pop();
        if (hasPrev) best = min(best, node->val - prev);
        prev = node->val;
        hasPrev = true;
        node = node->right;
    }
    return best;  // n >= 2, so at least one gap was measured
}
pub fn get_minimum_difference_sorted(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
    fn collect(node: &Option<Rc<RefCell<TreeNode>>>, vals: &mut Vec<i32>) {
        if let Some(rc) = node {
            let n = rc.borrow();
            vals.push(n.val);
            collect(&n.left, vals);
            collect(&n.right, vals);
        }
    }

    let mut vals = Vec::new();
    collect(&root, &mut vals);
    vals.sort_unstable();
    vals.windows(2)
        .map(|w| w[1] - w[0])
        .min()
        .unwrap_or(i32::MAX)
}
pub fn get_minimum_difference(root: Option<Rc<RefCell<TreeNode>>>) -> i32 {
    fn inorder(
        node: &Option<Rc<RefCell<TreeNode>>>,
        prev: &mut Option<i32>,
        best: &mut i32,
    ) {
        if let Some(rc) = node {
            let n = rc.borrow();
            inorder(&n.left, prev, best);
            if let Some(p) = *prev {
                *best = (*best).min(n.val - p);
            }
            *prev = Some(n.val);
            inorder(&n.right, prev, best);
        }
    }

    let mut prev = None;
    let mut best = i32::MAX;
    inorder(&root, &mut prev, &mut best);
    best // n >= 2, so at least one gap was measured
}
function getMinimumDifferenceSorted(root: TreeNode | null): number {
    const vals: number[] = [];
    const stack: (TreeNode | null)[] = [root];
    while (stack.length > 0) {
        const node = stack.pop()!;
        if (node) {
            vals.push(node.val);
            stack.push(node.left, node.right);
        }
    }
    vals.sort((a, b) => a - b);
    let best = Infinity;
    for (let i = 1; i < vals.length; i++) {
        best = Math.min(best, vals[i] - vals[i - 1]);
    }
    return best;
}
function getMinimumDifferenceRecursive(root: TreeNode | null): number {
    let best = Infinity;
    let prev: number | null = null;
    const inorder = (node: TreeNode | null): void => {
        if (!node) return;
        inorder(node.left);
        if (prev !== null) best = Math.min(best, node.val - prev);
        prev = node.val;
        inorder(node.right);
    };
    inorder(root);
    return best;
}
function getMinimumDifference(root: TreeNode | null): number {
    const stack: TreeNode[] = [];
    let node = root;
    let prev: number | null = null;
    let best = Infinity;
    while (stack.length > 0 || node !== null) {
        while (node !== null) {
            stack.push(node);
            node = node.left;
        }
        const top = stack.pop() as TreeNode;
        if (prev !== null) best = Math.min(best, top.val - prev);
        prev = top.val;
        node = top.right;
    }
    return best; // n >= 2, so at least one gap was measured
}
func getMinimumDifferenceSorted(root *TreeNode) int {
	vals := []int{}
	stack := []*TreeNode{root}
	for len(stack) > 0 {
		node := stack[len(stack)-1]
		stack = stack[:len(stack)-1]
		if node != nil {
			vals = append(vals, node.Val)
			stack = append(stack, node.Left, node.Right)
		}
	}
	sort.Ints(vals)
	best := math.MaxInt32
	for i := 1; i < len(vals); i++ {
		if vals[i]-vals[i-1] < best {
			best = vals[i] - vals[i-1]
		}
	}
	return best
}
func getMinimumDifferenceRecursive(root *TreeNode) int {
	best := math.MaxInt32
	prev := -1 // values are >= 0 per the constraints
	var inorder func(node *TreeNode)
	inorder = func(node *TreeNode) {
		if node == nil {
			return
		}
		inorder(node.Left)
		if prev >= 0 && node.Val-prev < best {
			best = node.Val - prev
		}
		prev = node.Val
		inorder(node.Right)
	}
	inorder(root)
	return best
}
func getMinimumDifference(root *TreeNode) int {
	stack := []*TreeNode{}
	node := root
	best := math.MaxInt32
	prev := -1 // values are >= 0 per the constraints
	for len(stack) > 0 || node != nil {
		for node != nil {
			stack = append(stack, node)
			node = node.Left
		}
		node = stack[len(stack)-1]
		stack = stack[:len(stack)-1]
		if prev >= 0 && node.Val-prev < best {
			best = node.Val - prev
		}
		prev = node.Val
		node = node.Right
	}
	return best // n >= 2, so at least one gap was measured
}
func getMinimumDifferenceSorted(_ root: TreeNode?) -> Int {
    var vals: [Int] = []
    var stack: [TreeNode?] = [root]
    while let node = stack.popLast() {
        if let node = node {
            vals.append(node.val)
            stack.append(node.left)
            stack.append(node.right)
        }
    }
    vals.sort()
    var best = Int.max
    for i in 1..<vals.count {
        best = min(best, vals[i] - vals[i - 1])
    }
    return best
}
func getMinimumDifferenceRecursive(_ root: TreeNode?) -> Int {
    var best = Int.max
    var prev: Int? = nil
    func inorder(_ node: TreeNode?) {
        guard let node = node else { return }
        inorder(node.left)
        if let p = prev {
            best = min(best, node.val - p)
        }
        prev = node.val
        inorder(node.right)
    }
    inorder(root)
    return best
}
func getMinimumDifference(_ root: TreeNode?) -> Int {
    var stack: [TreeNode] = []
    var node = root
    var prev: Int? = nil
    var best = Int.max
    while !stack.isEmpty || node != nil {
        while let current = node {
            stack.append(current)
            node = current.left
        }
        let top = stack.removeLast()
        if let p = prev {
            best = min(best, top.val - p)
        }
        prev = top.val
        node = top.right
    }
    return best  // n >= 2, so at least one gap was measured
}
Recommended Approach 1 of 4 · collect every value, sort, scan adjacent gapsO(n log n) time · O(n) space

Graph BFS

34. Snakes and Ladders

Medium · LC 909

Given a snakes-and-ladders board labeled boustrophedon style, return the fewest die rolls needed to reach the final square, or -1 if it cannot be reached. Since every roll costs the same, run a breadth-first search from square 1 with a distance map: pop a square, try each of the six destinations, decode the label into board coordinates, follow any snake or ladder found there, and enqueue unseen squares at distance plus one. The pitfalls are the label decoding — rows alternate direction and count from the bottom — and the rule that a snake or ladder is taken exactly once, never chained.

No queue, no visit order — just sweep every square and relax dist[next] = dist[cur] + 1 until nothing changes. Shortest distances are exactly the fixpoint of that relaxation, which is the correctness argument all the later rungs lean on. Each sweep is O(n^2); n <= 20 keeps it instant despite the awful bound.

Replaces Approach 1's blind sweeping with a priority queue that settles each square once, in distance order — the general shortest-path tool this problem reduces to. Overkill here (every edge costs 1), which is what the next rung exploits.

Unit-weight edges make Approach 2's heap unnecessary: expanding whole frontiers level by level already settles squares in distance order, dropping the log factor. The loop depth IS the distance, so no per-node distance storage is needed.

Same traversal as Approach 3 with one deque instead of a rebuilt frontier set per level, and it records the roll count for every square it touches — not just the target. Each square is enqueued at most once; with unit-cost edges the first time the target is popped its distance is already optimal.

No queue, no visit order — sweep every square and relax dist[next] = dist[cur] + 1 until nothing changes. Shortest distances are exactly the fixpoint of that relaxation, which is the correctness argument BFS leans on too. Each sweep is O(n^2); n <= 20 keeps it instant despite the awful bound.

Replaces Approach 1's repeated sweeps with a queue that reaches the fixpoint in a single pass: each square is enqueued at most once, and with unit-cost edges the first time the target is popped its distance is already optimal.

No queue, no visit order — sweep every square and relax dist[next] = dist[cur] + 1 until nothing changes. Shortest distances are exactly the fixpoint of that relaxation, which is the correctness argument BFS leans on too. Each sweep is O(n^2); n <= 20 keeps it instant despite the awful bound.

Replaces Approach 1's repeated sweeps with a queue that reaches the fixpoint in a single pass: each square is enqueued at most once, and with unit-cost edges the first time the target is popped its distance is already optimal.

No queue, no visit order — sweep every square and relax dist[next] = dist[cur] + 1 until nothing changes. Shortest distances are exactly the fixpoint of that relaxation, which is the correctness argument BFS leans on too. Each sweep is O(n^2); n <= 20 keeps it instant despite the awful bound.

Replaces Approach 1's repeated sweeps with a queue that reaches the fixpoint in a single pass: each square is enqueued at most once, and with unit-cost edges the first time the target is popped its distance is already optimal.

No queue, no visit order — sweep every square and relax dist[next] = dist[cur] + 1 until nothing changes. Shortest distances are exactly the fixpoint of that relaxation, which is the correctness argument BFS leans on too. Each sweep is O(n^2); n <= 20 keeps it instant despite the awful bound.

Replaces Approach 1's repeated sweeps with a queue that reaches the fixpoint in a single pass: each square is enqueued at most once, and with unit-cost edges the first time the target is popped its distance is already optimal.

No queue, no visit order — sweep every square and relax dist[next] = dist[cur] + 1 until nothing changes. Shortest distances are exactly the fixpoint of that relaxation, which is the correctness argument BFS leans on too. Each sweep is O(n^2); n <= 20 keeps it instant despite the awful bound.

Replaces Approach 1's repeated sweeps with a queue that reaches the fixpoint in a single pass: each square is enqueued at most once, and with unit-cost edges the first time the target is popped its distance is already optimal.

def snakesAndLadders_bellman_ford(self, board: List[List[int]]) -> int:
    n = len(board)
    target = n * n

    unreached = float("inf")
    dist = [unreached] * (target + 1)
    dist[1] = 0
    changed = True
    while changed:
        changed = False
        for cur in range(1, target + 1):
            if dist[cur] == unreached:
                continue
            for roll in range(cur + 1, min(cur + 6, target) + 1):
                jump = self._square_value(board, roll)
                nxt = jump if jump != -1 else roll
                if dist[cur] + 1 < dist[nxt]:
                    dist[nxt] = dist[cur] + 1
                    changed = True
    return -1 if dist[target] == unreached else dist[target]
def snakesAndLadders_dijkstra(self, board: List[List[int]]) -> int:
    n = len(board)
    target = n * n

    best = {1: 0}
    heap = [(0, 1)]
    while heap:
        d, cur = heappop(heap)
        if cur == target:
            return d
        if d > best.get(cur, float("inf")):
            continue
        for roll in range(cur + 1, min(cur + 6, target) + 1):
            jump = self._square_value(board, roll)
            nxt = jump if jump != -1 else roll
            if d + 1 < best.get(nxt, float("inf")):
                best[nxt] = d + 1
                heappush(heap, (d + 1, nxt))
    return -1
def snakesAndLadders_level_bfs(self, board: List[List[int]]) -> int:
    n = len(board)
    target = n * n

    frontier = {1}
    visited = {1}
    rolls = 0
    while frontier:
        if target in frontier:
            return rolls
        nxt_frontier = set()
        for cur in frontier:
            for roll in range(cur + 1, min(cur + 6, target) + 1):
                jump = self._square_value(board, roll)
                nxt = jump if jump != -1 else roll
                if nxt not in visited:
                    visited.add(nxt)
                    nxt_frontier.add(nxt)
        frontier = nxt_frontier
        rolls += 1
    return -1
def snakesAndLadders(self, board: List[List[int]]) -> int:
    n = len(board)
    target = n * n

    dist = {1: 0}
    queue = deque([1])
    while queue:
        cur = queue.popleft()
        if cur == target:
            return dist[cur]
        for roll in range(cur + 1, min(cur + 6, target) + 1):
            jump = self._square_value(board, roll)
            nxt = jump if jump != -1 else roll   # take snake/ladder once
            if nxt not in dist:
                dist[nxt] = dist[cur] + 1
                queue.append(nxt)
    return -1
int snakesAndLaddersBellmanFord(vector<vector<int>>& board) {
    const int n = static_cast<int>(board.size());
    const int target = n * n;

    auto squareValue = [&](int label) {
        int rowFromBottom = (label - 1) / n;
        int offset = (label - 1) % n;
        int row = n - 1 - rowFromBottom;
        int col = (rowFromBottom % 2 == 0) ? offset : n - 1 - offset;
        return board[row][col];
    };

    const int unreached = target + 1;  // no path uses more than target rolls
    vector<int> dist(target + 1, unreached);
    dist[1] = 0;
    bool changed = true;
    while (changed) {
        changed = false;
        for (int cur = 1; cur <= target; ++cur) {
            if (dist[cur] == unreached) continue;
            for (int roll = cur + 1; roll <= std::min(cur + 6, target); ++roll) {
                int jump = squareValue(roll);
                int nxt = (jump != -1) ? jump : roll;
                if (dist[cur] + 1 < dist[nxt]) {
                    dist[nxt] = dist[cur] + 1;
                    changed = true;
                }
            }
        }
    }
    return dist[target] == unreached ? -1 : dist[target];
}
int snakesAndLadders(vector<vector<int>>& board) {
    const int n = static_cast<int>(board.size());
    const int target = n * n;

    auto squareValue = [&](int label) {
        int rowFromBottom = (label - 1) / n;
        int offset = (label - 1) % n;
        int row = n - 1 - rowFromBottom;
        int col = (rowFromBottom % 2 == 0) ? offset : n - 1 - offset;
        return board[row][col];
    };

    vector<int> dist(target + 1, -1);
    dist[1] = 0;
    queue<int> pending;
    pending.push(1);
    while (!pending.empty()) {
        int cur = pending.front();
        pending.pop();
        if (cur == target) return dist[cur];
        for (int roll = cur + 1; roll <= std::min(cur + 6, target); ++roll) {
            int jump = squareValue(roll);
            int nxt = (jump != -1) ? jump : roll;  // take snake/ladder once
            if (dist[nxt] == -1) {
                dist[nxt] = dist[cur] + 1;
                pending.push(nxt);
            }
        }
    }
    return -1;
}
pub fn snakes_and_ladders_bellman_ford(board: Vec<Vec<i32>>) -> i32 {
    let n = board.len();
    let target = n * n;

    let square_value = |label: usize| -> i32 {
        let row_from_bottom = (label - 1) / n;
        let offset = (label - 1) % n;
        let row = n - 1 - row_from_bottom;
        let col = if row_from_bottom % 2 == 0 {
            offset
        } else {
            n - 1 - offset
        };
        board[row][col]
    };

    let unreached = target as i32 + 1; // no path uses more than target rolls
    let mut dist = vec![unreached; target + 1];
    dist[1] = 0;
    let mut changed = true;
    while changed {
        changed = false;
        for cur in 1..=target {
            if dist[cur] == unreached {
                continue;
            }
            for roll in cur + 1..=(cur + 6).min(target) {
                let jump = square_value(roll);
                let next = if jump != -1 { jump as usize } else { roll };
                if dist[cur] + 1 < dist[next] {
                    dist[next] = dist[cur] + 1;
                    changed = true;
                }
            }
        }
    }
    if dist[target] == unreached {
        -1
    } else {
        dist[target]
    }
}
pub fn snakes_and_ladders(board: Vec<Vec<i32>>) -> i32 {
    let n = board.len();
    let target = n * n;

    let square_value = |label: usize| -> i32 {
        let row_from_bottom = (label - 1) / n;
        let offset = (label - 1) % n;
        let row = n - 1 - row_from_bottom;
        let col = if row_from_bottom % 2 == 0 {
            offset
        } else {
            n - 1 - offset
        };
        board[row][col]
    };

    let mut dist = vec![-1; target + 1];
    dist[1] = 0;
    let mut queue = VecDeque::from([1usize]);
    while let Some(cur) = queue.pop_front() {
        if cur == target {
            return dist[cur];
        }
        for roll in cur + 1..=(cur + 6).min(target) {
            let jump = square_value(roll);
            let next = if jump != -1 { jump as usize } else { roll };
            if dist[next] == -1 {
                dist[next] = dist[cur] + 1;
                queue.push_back(next);
            }
        }
    }
    -1
}
function snakesAndLaddersBellmanFord(board: number[][]): number {
    const n = board.length;
    const target = n * n;

    const squareValue = (label: number): number => {
        const rowFromBottom = Math.floor((label - 1) / n);
        const offset = (label - 1) % n;
        const row = n - 1 - rowFromBottom;
        const col = rowFromBottom % 2 === 0 ? offset : n - 1 - offset;
        return board[row][col];
    };

    const unreached = target + 1; // no path uses more than target rolls
    const dist = new Array<number>(target + 1).fill(unreached);
    dist[1] = 0;
    let changed = true;
    while (changed) {
        changed = false;
        for (let cur = 1; cur <= target; cur++) {
            if (dist[cur] === unreached) continue;
            for (let roll = cur + 1; roll <= Math.min(cur + 6, target); roll++) {
                const jump = squareValue(roll);
                const next = jump !== -1 ? jump : roll;
                if (dist[cur] + 1 < dist[next]) {
                    dist[next] = dist[cur] + 1;
                    changed = true;
                }
            }
        }
    }
    return dist[target] === unreached ? -1 : dist[target];
}
function snakesAndLadders(board: number[][]): number {
    const n = board.length;
    const target = n * n;

    const squareValue = (label: number): number => {
        const rowFromBottom = Math.floor((label - 1) / n);
        const offset = (label - 1) % n;
        const row = n - 1 - rowFromBottom;
        const col = rowFromBottom % 2 === 0 ? offset : n - 1 - offset;
        return board[row][col];
    };

    const dist = new Array<number>(target + 1).fill(-1);
    dist[1] = 0;
    const queue: number[] = [1];
    let head = 0;
    while (head < queue.length) {
        const cur = queue[head++];
        if (cur === target) return dist[cur];
        for (let roll = cur + 1; roll <= Math.min(cur + 6, target); roll++) {
            const jump = squareValue(roll);
            const next = jump !== -1 ? jump : roll; // take snake/ladder once
            if (dist[next] === -1) {
                dist[next] = dist[cur] + 1;
                queue.push(next);
            }
        }
    }
    return -1;
}
func snakesAndLaddersBellmanFord(board [][]int) int {
	n := len(board)
	target := n * n

	squareValue := func(label int) int {
		rowFromBottom := (label - 1) / n
		offset := (label - 1) % n
		row := n - 1 - rowFromBottom
		col := offset
		if rowFromBottom%2 == 1 {
			col = n - 1 - offset
		}
		return board[row][col]
	}

	unreached := target + 1 // no path uses more than target rolls
	dist := make([]int, target+1)
	for i := range dist {
		dist[i] = unreached
	}
	dist[1] = 0
	for changed := true; changed; {
		changed = false
		for cur := 1; cur <= target; cur++ {
			if dist[cur] == unreached {
				continue
			}
			for roll := cur + 1; roll <= cur+6 && roll <= target; roll++ {
				next := roll
				if jump := squareValue(roll); jump != -1 {
					next = jump
				}
				if dist[cur]+1 < dist[next] {
					dist[next] = dist[cur] + 1
					changed = true
				}
			}
		}
	}
	if dist[target] == unreached {
		return -1
	}
	return dist[target]
}
func snakesAndLadders(board [][]int) int {
	n := len(board)
	target := n * n

	squareValue := func(label int) int {
		rowFromBottom := (label - 1) / n
		offset := (label - 1) % n
		row := n - 1 - rowFromBottom
		col := offset
		if rowFromBottom%2 == 1 {
			col = n - 1 - offset
		}
		return board[row][col]
	}

	dist := make([]int, target+1)
	for i := range dist {
		dist[i] = -1
	}
	dist[1] = 0
	queue := []int{1}
	for len(queue) > 0 {
		cur := queue[0]
		queue = queue[1:]
		if cur == target {
			return dist[cur]
		}
		for roll := cur + 1; roll <= cur+6 && roll <= target; roll++ {
			next := roll
			if jump := squareValue(roll); jump != -1 {
				next = jump // take snake/ladder exactly once
			}
			if dist[next] == -1 {
				dist[next] = dist[cur] + 1
				queue = append(queue, next)
			}
		}
	}
	return -1
}
func snakesAndLaddersBellmanFord(_ board: [[Int]]) -> Int {
    let n = board.count
    let target = n * n

    func squareValue(_ label: Int) -> Int {
        let rowFromBottom = (label - 1) / n
        let offset = (label - 1) % n
        let row = n - 1 - rowFromBottom
        let col = rowFromBottom % 2 == 0 ? offset : n - 1 - offset
        return board[row][col]
    }

    let unreached = target + 1  // no path uses more than target rolls
    var dist = [Int](repeating: unreached, count: target + 1)
    dist[1] = 0
    var changed = true
    while changed {
        changed = false
        for cur in 1...target where dist[cur] != unreached {
            for roll in stride(from: cur + 1, through: min(cur + 6, target), by: 1) {
                let jump = squareValue(roll)
                let next = jump != -1 ? jump : roll
                if dist[cur] + 1 < dist[next] {
                    dist[next] = dist[cur] + 1
                    changed = true
                }
            }
        }
    }
    return dist[target] == unreached ? -1 : dist[target]
}
func snakesAndLadders(_ board: [[Int]]) -> Int {
    let n = board.count
    let target = n * n

    func squareValue(_ label: Int) -> Int {
        let rowFromBottom = (label - 1) / n
        let offset = (label - 1) % n
        let row = n - 1 - rowFromBottom
        let col = rowFromBottom % 2 == 0 ? offset : n - 1 - offset
        return board[row][col]
    }

    var dist = [Int](repeating: -1, count: target + 1)
    dist[1] = 0
    var queue = [1]
    var head = 0
    while head < queue.count {
        let cur = queue[head]
        head += 1
        if cur == target { return dist[cur] }
        for roll in stride(from: cur + 1, through: min(cur + 6, target), by: 1) {
            let jump = squareValue(roll)
            let next = jump != -1 ? jump : roll  // take snake/ladder once
            if dist[next] == -1 {
                dist[next] = dist[cur] + 1
                queue.append(next)
            }
        }
    }
    return -1
}
Recommended Approach 1 of 4 · Bellman-Ford-style relaxation to a fixpointO(n^4) worst case time · O(n^2) space

35. Minimum Genetic Mutation

Medium · LC 433

Given a start gene, an end gene, and a bank of valid genes, return the minimum number of single-character mutations that turns the start into the end, where every intermediate gene must appear in the bank. Since each mutation costs the same, run a breadth-first search from the start gene: for each gene popped, generate every string one character away over the alphabet ACGT, and enqueue those that are in the bank set and not yet seen. The trick is filtering candidates through the bank set instead of scanning the bank for neighbors, which keeps each step to 24 cheap membership tests and never lets the search leave valid genes.

Try every sequence of distinct bank genes that steps one character at a time, and keep the shallowest depth that reaches endGene. A shortest mutation path never revisits a gene, so enumerating all simple paths is exhaustive-but-correct; pruning at the best depth found so far and the 10-gene bank cap keep the blowup tame.

Collapses Approach 1's factorial blowup: BFS visits genes in distance order, so the first arrival at endGene is already optimal and each gene is expanded once. Neighbors are found by scanning the bank for genes exactly one character away — simple to reason about, fine for a tiny bank.

Two upgrades over Approach 2: neighbors are generated as the 3L one-char edits and filtered through a bank set (O(L) per expansion instead of an O(B) scan), and the search grows from both ends, meeting in the middle — the classic way to cut the search depth in half on word ladders.

Single-direction version of Approach 3's neighbor generation: each bank gene is enqueued once and expanded into its 3L candidate mutations. With depth capped at 8 the two-ended trick buys almost nothing here, and the plain queue is simpler, has no special cases, and yields the mutation count for every reachable gene.

Try every sequence of distinct bank genes that steps one character at a time, and keep the shallowest depth that reaches endGene. A shortest mutation path never revisits a gene, so enumerating all simple paths is exhaustive-but-correct; pruning at the best depth found so far and the 10-gene bank cap keep the blowup tame.

Collapses Approach 1's factorial blowup: BFS visits genes in distance order, so the first arrival at endGene is already optimal. Each popped gene expands into its 3L one-char edits, filtered through the bank set, so each bank gene is enqueued at most once and the search never leaves valid genes.

Try every sequence of distinct bank genes that steps one character at a time, and keep the shallowest depth that reaches endGene. A shortest mutation path never revisits a gene, so enumerating all simple paths is exhaustive-but-correct; pruning at the best depth found so far and the 10-gene bank cap keep the blowup tame.

Collapses Approach 1's factorial blowup: BFS visits genes in distance order, so the first arrival at endGene is already optimal. Each popped gene expands into its 3L one-char edits, filtered through the bank set, so each bank gene is enqueued at most once and the search never leaves valid genes.

Try every sequence of distinct bank genes that steps one character at a time, and keep the shallowest depth that reaches endGene. A shortest mutation path never revisits a gene, so enumerating all simple paths is exhaustive-but-correct; pruning at the best depth found so far and the 10-gene bank cap keep the blowup tame.

Collapses Approach 1's factorial blowup: BFS visits genes in distance order, so the first arrival at endGene is already optimal. Each popped gene expands into its 3L one-char edits, filtered through the bank set, so each bank gene is enqueued at most once and the search never leaves valid genes.

Try every sequence of distinct bank genes that steps one character at a time, and keep the shallowest depth that reaches endGene. A shortest mutation path never revisits a gene, so enumerating all simple paths is exhaustive-but-correct; pruning at the best depth found so far and the 10-gene bank cap keep the blowup tame.

Collapses Approach 1's factorial blowup: BFS visits genes in distance order, so the first arrival at endGene is already optimal. Each popped gene expands into its 3L one-char edits, filtered through the bank set, so each bank gene is enqueued at most once and the search never leaves valid genes.

Try every sequence of distinct bank genes that steps one character at a time, and keep the shallowest depth that reaches endGene. A shortest mutation path never revisits a gene, so enumerating all simple paths is exhaustive-but-correct; pruning at the best depth found so far and the 10-gene bank cap keep the blowup tame.

Collapses Approach 1's factorial blowup: BFS visits genes in distance order, so the first arrival at endGene is already optimal. Each popped gene expands into its 3L one-char edits, filtered through the bank set, so each bank gene is enqueued at most once and the search never leaves valid genes.

def minMutation_dfs_backtracking(self, startGene: str, endGene: str,
                                 bank: List[str]) -> int:
    def one_apart(a: str, b: str) -> bool:
        return sum(x != y for x, y in zip(a, b)) == 1

    best = len(bank) + 1            # any real answer uses <= B mutations
    used = [False] * len(bank)

    def dfs(gene: str, depth: int) -> None:
        nonlocal best
        if depth >= best:           # cannot improve on this branch
            return
        if gene == endGene:
            best = depth
            return
        for i, candidate in enumerate(bank):
            if not used[i] and one_apart(gene, candidate):
                used[i] = True
                dfs(candidate, depth + 1)
                used[i] = False

    dfs(startGene, 0)
    return best if best <= len(bank) else -1
def minMutation_bank_graph(self, startGene: str, endGene: str,
                           bank: List[str]) -> int:
    def one_apart(a: str, b: str) -> bool:
        return sum(x != y for x, y in zip(a, b)) == 1

    queue = deque([(startGene, 0)])
    seen = {startGene}
    while queue:
        gene, steps = queue.popleft()
        if gene == endGene:
            return steps
        for candidate in bank:
            if candidate not in seen and one_apart(gene, candidate):
                seen.add(candidate)
                queue.append((candidate, steps + 1))
    return -1
def minMutation_bidirectional(self, startGene: str, endGene: str,
                              bank: List[str]) -> int:
    if startGene == endGene:
        return 0
    bank_set = set(bank)
    if endGene not in bank_set:
        return -1

    frontier = {startGene}
    other = {endGene}
    visited = {startGene, endGene}
    steps = 0
    while frontier and other:
        if len(frontier) > len(other):        # always expand the smaller side
            frontier, other = other, frontier
        steps += 1
        nxt_frontier = set()
        for gene in frontier:
            for i in range(len(gene)):
                for ch in "ACGT":
                    if ch == gene[i]:
                        continue
                    mutated = gene[:i] + ch + gene[i + 1:]
                    if mutated in other:      # the two searches met
                        return steps
                    if mutated in bank_set and mutated not in visited:
                        visited.add(mutated)
                        nxt_frontier.add(mutated)
        frontier = nxt_frontier
    return -1
def minMutation(self, startGene: str, endGene: str, bank: List[str]) -> int:
    bank_set = set(bank)
    queue = deque([(startGene, 0)])
    seen = {startGene}
    while queue:
        gene, steps = queue.popleft()
        if gene == endGene:
            return steps
        for i in range(len(gene)):
            for ch in "ACGT":
                if ch == gene[i]:
                    continue
                mutated = gene[:i] + ch + gene[i + 1:]
                if mutated in bank_set and mutated not in seen:
                    seen.add(mutated)
                    queue.append((mutated, steps + 1))
    return -1
int minMutationDfsBacktracking(string startGene, string endGene,
                               vector<string>& bank) {
    auto oneApart = [](const string& a, const string& b) {
        int diff = 0;
        for (size_t i = 0; i < a.size(); ++i) diff += a[i] != b[i];
        return diff == 1;
    };

    const int B = static_cast<int>(bank.size());
    int best = B + 1;  // any real answer uses <= B mutations
    vector<bool> used(bank.size(), false);

    std::function<void(const string&, int)> dfs = [&](const string& gene,
                                                      int depth) {
        if (depth >= best) return;  // cannot improve on this branch
        if (gene == endGene) {
            best = depth;
            return;
        }
        for (size_t i = 0; i < bank.size(); ++i) {
            if (!used[i] && oneApart(gene, bank[i])) {
                used[i] = true;
                dfs(bank[i], depth + 1);
                used[i] = false;
            }
        }
    };
    dfs(startGene, 0);
    return best <= B ? best : -1;
}
int minMutation(string startGene, string endGene, vector<string>& bank) {
    std::unordered_set<string> bankSet(bank.begin(), bank.end());
    std::unordered_set<string> seen{startGene};
    std::queue<std::pair<string, int>> pending;
    pending.push({startGene, 0});
    const string alphabet = "ACGT";

    while (!pending.empty()) {
        auto [gene, steps] = pending.front();
        pending.pop();
        if (gene == endGene) return steps;
        for (size_t i = 0; i < gene.size(); ++i) {
            char original = gene[i];
            for (char ch : alphabet) {
                if (ch == original) continue;
                gene[i] = ch;
                if (bankSet.count(gene) && !seen.count(gene)) {
                    seen.insert(gene);
                    pending.push({gene, steps + 1});
                }
            }
            gene[i] = original;
        }
    }
    return -1;
}
pub fn min_mutation_dfs_backtracking(
    start_gene: String,
    end_gene: String,
    bank: Vec<String>,
) -> i32 {
    fn one_apart(a: &[u8], b: &[u8]) -> bool {
        a.iter().zip(b).filter(|(x, y)| x != y).count() == 1
    }

    fn dfs(
        gene: &[u8],
        depth: i32,
        end: &[u8],
        bank: &[Vec<u8>],
        used: &mut [bool],
        best: &mut i32,
    ) {
        if depth >= *best {
            return; // cannot improve on this branch
        }
        if gene == end {
            *best = depth;
            return;
        }
        for i in 0..bank.len() {
            if !used[i] && one_apart(gene, &bank[i]) {
                used[i] = true;
                dfs(&bank[i], depth + 1, end, bank, used, best);
                used[i] = false;
            }
        }
    }

    let bank: Vec<Vec<u8>> = bank.into_iter().map(String::into_bytes).collect();
    let start = start_gene.into_bytes();
    let end = end_gene.into_bytes();
    let mut used = vec![false; bank.len()];
    let mut best = bank.len() as i32 + 1; // any real answer uses <= B mutations
    dfs(&start, 0, &end, &bank, &mut used, &mut best);
    if best <= bank.len() as i32 {
        best
    } else {
        -1
    }
}
pub fn min_mutation(start_gene: String, end_gene: String, bank: Vec<String>) -> i32 {
    let bank_set: HashSet<Vec<u8>> = bank.into_iter().map(String::into_bytes).collect();
    let start = start_gene.into_bytes();
    let end = end_gene.into_bytes();

    let mut seen = HashSet::from([start.clone()]);
    let mut queue = VecDeque::from([(start, 0)]);
    while let Some((gene, steps)) = queue.pop_front() {
        if gene == end {
            return steps;
        }
        for i in 0..gene.len() {
            for &ch in b"ACGT" {
                if ch == gene[i] {
                    continue;
                }
                let mut mutated = gene.clone();
                mutated[i] = ch;
                if bank_set.contains(&mutated) && !seen.contains(&mutated) {
                    seen.insert(mutated.clone());
                    queue.push_back((mutated, steps + 1));
                }
            }
        }
    }
    -1
}
function minMutationDfsBacktracking(startGene: string, endGene: string, bank: string[]): number {
    const oneApart = (a: string, b: string): boolean => {
        let diff = 0;
        for (let i = 0; i < a.length; i++) {
            if (a[i] !== b[i]) diff++;
        }
        return diff === 1;
    };

    let best = bank.length + 1; // any real answer uses <= B mutations
    const used = new Array<boolean>(bank.length).fill(false);

    const dfs = (gene: string, depth: number): void => {
        if (depth >= best) return; // cannot improve on this branch
        if (gene === endGene) {
            best = depth;
            return;
        }
        for (let i = 0; i < bank.length; i++) {
            if (!used[i] && oneApart(gene, bank[i])) {
                used[i] = true;
                dfs(bank[i], depth + 1);
                used[i] = false;
            }
        }
    };
    dfs(startGene, 0);
    return best <= bank.length ? best : -1;
}
function minMutation(startGene: string, endGene: string, bank: string[]): number {
    const bankSet = new Set(bank);
    const alphabet = ['A', 'C', 'G', 'T'];

    const seen = new Set<string>([startGene]);
    const queue: Array<[string, number]> = [[startGene, 0]];
    let head = 0;
    while (head < queue.length) {
        const [gene, steps] = queue[head++];
        if (gene === endGene) return steps;
        const chars = gene.split('');
        for (let i = 0; i < chars.length; i++) {
            const original = chars[i];
            for (const ch of alphabet) {
                if (ch === original) continue;
                chars[i] = ch;
                const mutated = chars.join('');
                if (bankSet.has(mutated) && !seen.has(mutated)) {
                    seen.add(mutated);
                    queue.push([mutated, steps + 1]);
                }
            }
            chars[i] = original;
        }
    }
    return -1;
}
func minMutationDfsBacktracking(startGene string, endGene string, bank []string) int {
	oneApart := func(a, b string) bool {
		diff := 0
		for i := 0; i < len(a); i++ {
			if a[i] != b[i] {
				diff++
			}
		}
		return diff == 1
	}

	best := len(bank) + 1 // any real answer uses <= B mutations
	used := make([]bool, len(bank))

	var dfs func(gene string, depth int)
	dfs = func(gene string, depth int) {
		if depth >= best { // cannot improve on this branch
			return
		}
		if gene == endGene {
			best = depth
			return
		}
		for i, candidate := range bank {
			if !used[i] && oneApart(gene, candidate) {
				used[i] = true
				dfs(candidate, depth+1)
				used[i] = false
			}
		}
	}
	dfs(startGene, 0)
	if best <= len(bank) {
		return best
	}
	return -1
}
func minMutation(startGene string, endGene string, bank []string) int {
	bankSet := make(map[string]bool, len(bank))
	for _, gene := range bank {
		bankSet[gene] = true
	}

	type item struct {
		gene  string
		steps int
	}
	seen := map[string]bool{startGene: true}
	queue := []item{{startGene, 0}}
	alphabet := []byte{'A', 'C', 'G', 'T'}

	for len(queue) > 0 {
		cur := queue[0]
		queue = queue[1:]
		if cur.gene == endGene {
			return cur.steps
		}
		bytes := []byte(cur.gene)
		for i, original := range bytes {
			for _, ch := range alphabet {
				if ch == original {
					continue
				}
				bytes[i] = ch
				mutated := string(bytes)
				if bankSet[mutated] && !seen[mutated] {
					seen[mutated] = true
					queue = append(queue, item{mutated, cur.steps + 1})
				}
			}
			bytes[i] = original
		}
	}
	return -1
}
func minMutationDfsBacktracking(_ startGene: String, _ endGene: String,
                                _ bank: [String]) -> Int {
    func oneApart(_ a: [Character], _ b: [Character]) -> Bool {
        var diff = 0
        for i in 0..<a.count where a[i] != b[i] {
            diff += 1
        }
        return diff == 1
    }

    let bankChars = bank.map { Array($0) }
    let endChars = Array(endGene)
    var best = bank.count + 1  // any real answer uses <= B mutations
    var used = [Bool](repeating: false, count: bank.count)

    func dfs(_ gene: [Character], _ depth: Int) {
        if depth >= best { return }  // cannot improve on this branch
        if gene == endChars {
            best = depth
            return
        }
        for i in 0..<bankChars.count where !used[i] && oneApart(gene, bankChars[i]) {
            used[i] = true
            dfs(bankChars[i], depth + 1)
            used[i] = false
        }
    }
    dfs(Array(startGene), 0)
    return best <= bank.count ? best : -1
}
func minMutation(_ startGene: String, _ endGene: String, _ bank: [String]) -> Int {
    let bankSet = Set(bank)
    let alphabet: [Character] = ["A", "C", "G", "T"]

    var seen: Set<String> = [startGene]
    var queue: [(gene: String, steps: Int)] = [(startGene, 0)]
    var head = 0
    while head < queue.count {
        let (gene, steps) = queue[head]
        head += 1
        if gene == endGene { return steps }
        var chars = Array(gene)
        for i in 0..<chars.count {
            let original = chars[i]
            for ch in alphabet where ch != original {
                chars[i] = ch
                let mutated = String(chars)
                if bankSet.contains(mutated) && !seen.contains(mutated) {
                    seen.insert(mutated)
                    queue.append((mutated, steps + 1))
                }
            }
            chars[i] = original
        }
    }
    return -1
}
Recommended Approach 1 of 4 · exhaustive DFS with backtrackingO(B! * B * L) worst case time · O(B) space

Divide and Conquer

36. Convert Sorted Array to Binary Search Tree

Easy · LC 108

Given an integer array sorted in ascending order, convert it into a height-balanced binary search tree. Recurse on index ranges: make the middle element of the range the root, then build the left subtree from the left half and the right subtree from the right half. The trick is that picking the middle splits every range as evenly as possible, so subtree sizes differ by at most one at each node and balance falls out for free, while the array being exactly the tree's inorder traversal guarantees the ordering property.

The version you write when slicing feels free: root = middle, halves = nums[:mid] and nums[mid+1:]. Correct and height-balanced, but every level of the tree copies the whole array again.

Drops Approach 1's copying by describing each half as an index range instead of a new list, and replaces recursion with an explicit stack of placeholder nodes filled as their range is popped — useful when stack depth is a worry.

Removes the need for random access entirely: recurse on sizes only and consume values with a running cursor during an inorder walk — the trick that generalizes to sorted linked lists (LC 109) where indexing is not available.

When the input really is an array, none of the machinery above is needed: root = middle of [lo, hi], then build each half the same way. Same bounds as Approaches 2-3 with the least code — the balanced split caps recursion depth at ~log2(n) frames (about 14 at the n <= 10^4 constraint), so recursion is safe here.

The version you write when slicing feels free: root = middle, halves = fresh vectors. Correct and height-balanced, but every level of the tree copies the whole array again.

Drops Approach 1's copying by describing each half as an index range [lo, hi] into the original array: the middle of the range becomes the root and each half is built the same way.

The version you write when slicing feels free: root = middle, halves = freshly allocated Vecs. Correct and height-balanced, but every level of the tree copies the whole array again.

Drops Approach 1's copying: a borrowed &[i32] names each half without allocating, so splitting is free — Rust's zero-cost equivalent of recursing on index ranges.

The version you write when slicing feels free: root = middle, halves = nums.slice(...) copies. Correct and height-balanced, but every level of the tree copies the whole array again.

Drops Approach 1's copying by describing each half as an index range [lo, hi] into the original array: the middle of the range becomes the root and each half is built the same way.

The version you write when slicing feels free: root = middle, halves = fresh slices (copied here, since Go subslices would share the backing array anyway). Correct and height-balanced, but every level of the tree copies the whole array again.

Drops Approach 1's copying by describing each half as an index range [lo, hi] into the original array: the middle of the range becomes the root and each half is built the same way.

The version you write when slicing feels free: root = middle, halves = fresh Arrays materialized from the slices. Correct and height-balanced, but every level of the tree copies the whole array again.

Drops Approach 1's copying by describing each half as an index range [lo, hi] into the original array: the middle of the range becomes the root and each half is built the same way.

def sortedArrayToBST_slices(self, nums: List[int]) -> Optional[TreeNode]:
    if not nums:
        return None
    mid = len(nums) // 2
    node = TreeNode(nums[mid])
    node.left = self.sortedArrayToBST_slices(nums[:mid])
    node.right = self.sortedArrayToBST_slices(nums[mid + 1:])
    return node
def sortedArrayToBST_iterative(self, nums: List[int]) -> Optional[TreeNode]:
    if not nums:
        return None
    root = TreeNode()
    stack = [(root, 0, len(nums) - 1)]
    while stack:
        node, lo, hi = stack.pop()
        mid = (lo + hi) // 2
        node.val = nums[mid]
        if lo < mid:
            node.left = TreeNode()
            stack.append((node.left, lo, mid - 1))
        if mid < hi:
            node.right = TreeNode()
            stack.append((node.right, mid + 1, hi))
    return root
def sortedArrayToBST_inorder_sim(self, nums: List[int]) -> Optional[TreeNode]:
    cursor = 0

    def build(lo: int, hi: int) -> Optional[TreeNode]:
        nonlocal cursor
        if lo > hi:
            return None
        mid = (lo + hi) // 2
        left = build(lo, mid - 1)
        node = TreeNode(nums[cursor])
        cursor += 1
        node.left = left
        node.right = build(mid + 1, hi)
        return node

    return build(0, len(nums) - 1)
def sortedArrayToBST(self, nums: List[int]) -> Optional[TreeNode]:
    def build(lo: int, hi: int) -> Optional[TreeNode]:
        if lo > hi:
            return None
        mid = (lo + hi) // 2
        node = TreeNode(nums[mid])
        node.left = build(lo, mid - 1)
        node.right = build(mid + 1, hi)
        return node

    return build(0, len(nums) - 1)
TreeNode* sortedArrayToBSTSlices(vector<int>& nums) {
    if (nums.empty()) return nullptr;
    size_t mid = nums.size() / 2;
    TreeNode* node = new TreeNode(nums[mid]);
    vector<int> left(nums.begin(), nums.begin() + mid);
    vector<int> right(nums.begin() + mid + 1, nums.end());
    node->left = sortedArrayToBSTSlices(left);
    node->right = sortedArrayToBSTSlices(right);
    return node;
}
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        return build(nums, 0, static_cast<int>(nums.size()) - 1);
    }

private:
    TreeNode* build(const vector<int>& nums, int lo, int hi) {
        if (lo > hi) return nullptr;
        int mid = lo + (hi - lo) / 2;
        TreeNode* node = new TreeNode(nums[mid]);
        node->left = build(nums, lo, mid - 1);
        node->right = build(nums, mid + 1, hi);
        return node;
    }
pub fn sorted_array_to_bst_slices(nums: Vec<i32>) -> Option<Rc<RefCell<TreeNode>>> {
    if nums.is_empty() {
        return None;
    }
    let mid = nums.len() / 2;
    let node = Rc::new(RefCell::new(TreeNode::new(nums[mid])));
    node.borrow_mut().left = Self::sorted_array_to_bst_slices(nums[..mid].to_vec());
    node.borrow_mut().right = Self::sorted_array_to_bst_slices(nums[mid + 1..].to_vec());
    Some(node)
}
pub fn sorted_array_to_bst(nums: Vec<i32>) -> Option<Rc<RefCell<TreeNode>>> {
    fn build(nums: &[i32]) -> Option<Rc<RefCell<TreeNode>>> {
        if nums.is_empty() {
            return None;
        }
        let mid = nums.len() / 2;
        let node = Rc::new(RefCell::new(TreeNode::new(nums[mid])));
        node.borrow_mut().left = build(&nums[..mid]);
        node.borrow_mut().right = build(&nums[mid + 1..]);
        Some(node)
    }
    build(&nums)
}
function sortedArrayToBSTSlices(nums: number[]): TreeNode | null {
    if (nums.length === 0) return null;
    const mid = Math.floor(nums.length / 2);
    return new TreeNode(
        nums[mid],
        sortedArrayToBSTSlices(nums.slice(0, mid)),
        sortedArrayToBSTSlices(nums.slice(mid + 1)),
    );
}
function sortedArrayToBST(nums: number[]): TreeNode | null {
    const build = (lo: number, hi: number): TreeNode | null => {
        if (lo > hi) return null;
        const mid = lo + Math.floor((hi - lo) / 2);
        return new TreeNode(nums[mid], build(lo, mid - 1), build(mid + 1, hi));
    };
    return build(0, nums.length - 1);
}
func sortedArrayToBSTSlices(nums []int) *TreeNode {
	if len(nums) == 0 {
		return nil
	}
	mid := len(nums) / 2
	left := append([]int(nil), nums[:mid]...)
	right := append([]int(nil), nums[mid+1:]...)
	return &TreeNode{
		Val:   nums[mid],
		Left:  sortedArrayToBSTSlices(left),
		Right: sortedArrayToBSTSlices(right),
	}
}
func sortedArrayToBST(nums []int) *TreeNode {
	var build func(lo, hi int) *TreeNode
	build = func(lo, hi int) *TreeNode {
		if lo > hi {
			return nil
		}
		mid := lo + (hi-lo)/2
		return &TreeNode{
			Val:   nums[mid],
			Left:  build(lo, mid-1),
			Right: build(mid+1, hi),
		}
	}
	return build(0, len(nums)-1)
}
func sortedArrayToBSTSlices(_ nums: [Int]) -> TreeNode? {
    if nums.isEmpty { return nil }
    let mid = nums.count / 2
    let node = TreeNode(nums[mid])
    node.left = sortedArrayToBSTSlices(Array(nums[..<mid]))
    node.right = sortedArrayToBSTSlices(Array(nums[(mid + 1)...]))
    return node
}
func sortedArrayToBST(_ nums: [Int]) -> TreeNode? {
    func build(_ lo: Int, _ hi: Int) -> TreeNode? {
        if lo > hi { return nil }
        let mid = lo + (hi - lo) / 2
        return TreeNode(nums[mid], build(lo, mid - 1), build(mid + 1, hi))
    }
    return build(0, nums.count - 1)
}
Recommended Approach 1 of 4 · pick the middle, slice, and recurse on copiesO(n log n) time · O(n log n) across all slices space

37. Sort List

Medium · LC 148

Given the head of a linked list, sort it in ascending order. Bottom-up merge sort does it in passes: count the length once, then repeatedly split off runs of width one, two, four and so on, merging each adjacent pair back in place behind a dummy node. The trick is that iterating over doubling widths needs no recursion at all, turning the usual O(log n) call stack of top-down merge sort into the true O(1) extra space the follow-up asks for.

Copy values out, sort() them, write them back. Beats everything in wall-clock Python (Timsort in C) but uses linear extra memory and dodges the linked-list manipulation the problem is really about.

Split at the middle with slow/fast pointers (fast starts one ahead so a 2-node list splits 1+1), sort each half, merge. Real list surgery with only logarithmic extra space — the classic answer, but the recursion stack still isn't the O(1) the follow-up wants.

Merge runs of width 1, 2, 4, ... in passes over the list. Truly constant extra space — the full answer to the follow-up ("O(n log n) time, O(1) memory?"), with no recursion stack at all.

Copy values into a vector, std::sort, write them back. Simple and fast in practice, but it uses linear extra memory and dodges the linked-list manipulation the problem is really about.

Split at the middle with slow/fast pointers (fast starts one ahead so a 2-node list splits 1+1), sort each half, merge. Real list surgery: the value buffer of Approach 1 shrinks to a logarithmic recursion stack.

Copy values into a Vec, sort it, write the values back. Simple and fast in practice, but it uses linear extra memory and dodges the linked-list manipulation the problem is really about.

Count the length once, then recursively split after n/2 nodes (the ownership-friendly equivalent of the slow/fast split), sort each half, and merge. Real list surgery: the value buffer of Approach 1 shrinks to a logarithmic recursion stack.

Copy values into an array, sort it numerically, write them back. Simple and fast in practice, but it uses linear extra memory and dodges the linked-list manipulation the problem is really about.

Split at the middle with slow/fast pointers (fast starts one ahead so a 2-node list splits 1+1), sort each half, merge. Real list surgery: the value array of Approach 1 shrinks to a logarithmic recursion stack.

Copy values into a slice, sort.Ints, write them back. Simple and fast in practice, but it uses linear extra memory and dodges the linked-list manipulation the problem is really about.

Split at the middle with slow/fast pointers (fast starts one ahead so a 2-node list splits 1+1), sort each half, merge. Real list surgery: the value slice of Approach 1 shrinks to a logarithmic recursion stack.

Copy values into an array, sort it, write the values back. Simple and fast in practice, but it uses linear extra memory and dodges the linked-list manipulation the problem is really about.

Split at the middle with slow/fast pointers (fast starts one ahead so a 2-node list splits 1+1), sort each half, merge. Real list surgery: the value array of Approach 1 shrinks to a logarithmic recursion stack.

def sortList_values(self, head: Optional[ListNode]) -> Optional[ListNode]:
    vals = []
    node = head
    while node:
        vals.append(node.val)
        node = node.next
    vals.sort()

    node = head
    for v in vals:
        node.val = v
        node = node.next
    return head
def sortList_top_down(self, head: Optional[ListNode]) -> Optional[ListNode]:
    if not head or not head.next:
        return head

    slow, fast = head, head.next
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
    mid = slow.next
    slow.next = None            # cut the list in two

    return self._merge(self.sortList_top_down(head), self.sortList_top_down(mid))
def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
    n = 0
    node = head
    while node:
        n += 1
        node = node.next

    dummy = ListNode(0, head)
    width = 1
    while width < n:
        prev, cur = dummy, dummy.next
        while cur:
            left = cur
            right = self._split(left, width)
            cur = self._split(right, width)
            prev.next = self._merge(left, right)
            while prev.next:            # advance to the merged tail
                prev = prev.next
        width *= 2
    return dummy.next

# -- shared helpers ------------------------------------------------

def _split(self, head, count):
    """Detach the first `count` nodes; return the head of the remainder."""
    if not head:
        return None
    for _ in range(count - 1):
        if not head.next:
            break
        head = head.next
    rest = head.next
    head.next = None
    return rest

def _merge(self, a, b):
    """Merge two sorted lists; stable (ties keep `a`'s node first)."""
    dummy = tail = ListNode()
    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
ListNode* sortListValues(ListNode* head) {
    std::vector<int> vals;
    for (ListNode* node = head; node; node = node->next)
        vals.push_back(node->val);
    std::sort(vals.begin(), vals.end());

    size_t i = 0;
    for (ListNode* node = head; node; node = node->next)
        node->val = vals[i++];
    return head;
}
    ListNode* sortList(ListNode* head) {
        if (!head || !head->next) return head;

        ListNode* slow = head;
        ListNode* fast = head->next;
        while (fast && fast->next) {
            slow = slow->next;
            fast = fast->next->next;
        }
        ListNode* mid = slow->next;
        slow->next = nullptr;          // cut the list in two

        return merge(sortList(head), sortList(mid));
    }

private:
    // Merge two sorted lists; stable (ties keep `a`'s node first).
    ListNode* merge(ListNode* a, ListNode* b) {
        ListNode dummy;
        ListNode* tail = &dummy;
        while (a && 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 ? a : b;
        return dummy.next;
    }
pub fn sort_list_values(head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
    let mut vals = Vec::new();
    let mut p = head.as_ref();
    while let Some(node) = p {
        vals.push(node.val);
        p = node.next.as_ref();
    }
    vals.sort_unstable();

    let mut head = head;
    let mut p = head.as_mut();
    for v in vals {
        let node = p.unwrap();
        node.val = v;
        p = node.next.as_mut();
    }
    head
}
pub fn sort_list(head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
    let mut n = 0usize;
    let mut p = head.as_ref();
    while let Some(node) = p {
        n += 1;
        p = node.next.as_ref();
    }
    Self::merge_sort(head, n)
}

// Sort the first n nodes (the whole list) recursively.
fn merge_sort(head: Option<Box<ListNode>>, n: usize) -> Option<Box<ListNode>> {
    if n <= 1 {
        return head;
    }

    // Cut after n/2 nodes.
    let mut head = head;
    let mut cut = head.as_mut().unwrap();
    for _ in 0..n / 2 - 1 {
        cut = cut.next.as_mut().unwrap();
    }
    let right = cut.next.take();

    let left = Self::merge_sort(head, n / 2);
    let right = Self::merge_sort(right, n - n / 2);
    Self::merge(left, right)
}

// Merge two sorted lists; stable (ties keep `a`'s node first).
fn merge(mut a: Option<Box<ListNode>>, mut b: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
    let mut dummy = Box::new(ListNode::new(0));
    let mut tail = &mut dummy;
    loop {
        match (a.take(), b.take()) {
            (Some(mut x), Some(mut y)) => {
                if x.val <= y.val {
                    a = x.next.take();
                    b = Some(y);
                    tail.next = Some(x);
                } else {
                    b = y.next.take();
                    a = Some(x);
                    tail.next = Some(y);
                }
                tail = tail.next.as_mut().unwrap();
            }
            (Some(x), None) => {
                tail.next = Some(x);
                break;
            }
            (None, Some(y)) => {
                tail.next = Some(y);
                break;
            }
            (None, None) => break,
        }
    }
    dummy.next
}
function sortListValues(head: ListNode | null): ListNode | null {
    const vals: number[] = [];
    for (let node = head; node !== null; node = node.next) vals.push(node.val);
    vals.sort((a, b) => a - b);

    let i = 0;
    for (let node = head; node !== null; node = node.next) node.val = vals[i++];
    return head;
}
function sortList(head: ListNode | null): ListNode | null {
    if (head === null || head.next === null) return head;

    let slow = head;
    let fast: ListNode | null = head.next;
    while (fast !== null && fast.next !== null) {
        slow = slow.next!;
        fast = fast.next.next;
    }
    const mid = slow.next;
    slow.next = null;              // cut the list in two

    return merge(sortList(head), sortList(mid));
}

// Merge two sorted lists; stable (ties keep `a`'s node first).
function merge(a: ListNode | null, b: ListNode | null): ListNode | null {
    const dummy = new ListNode();
    let 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;
}
func sortListValues(head *ListNode) *ListNode {
	vals := []int{}
	for node := head; node != nil; node = node.Next {
		vals = append(vals, node.Val)
	}
	sort.Ints(vals)

	i := 0
	for node := head; node != nil; node = node.Next {
		node.Val = vals[i]
		i++
	}
	return head
}
func sortList(head *ListNode) *ListNode {
	if head == nil || head.Next == nil {
		return head
	}

	slow, fast := head, head.Next
	for fast != nil && fast.Next != nil {
		slow = slow.Next
		fast = fast.Next.Next
	}
	mid := slow.Next
	slow.Next = nil // cut the list in two

	return merge(sortList(head), sortList(mid))
}

// merge joins two sorted lists; stable (ties keep a's node first).
func merge(a, b *ListNode) *ListNode {
	dummy := &ListNode{}
	tail := dummy
	for a != nil && b != nil {
		if a.Val <= b.Val {
			tail.Next = a
			a = a.Next
		} else {
			tail.Next = b
			b = b.Next
		}
		tail = tail.Next
	}
	if a != nil {
		tail.Next = a
	} else {
		tail.Next = b
	}
	return dummy.Next
}
func sortListValues(_ head: ListNode?) -> ListNode? {
    var vals: [Int] = []
    var node = head
    while let n = node {
        vals.append(n.val)
        node = n.next
    }
    vals.sort()

    node = head
    for v in vals {
        node!.val = v
        node = node!.next
    }
    return head
}
func sortList(_ head: ListNode?) -> ListNode? {
    guard let head = head, head.next != nil else { return head }

    var slow = head
    var fast = head.next
    while let f = fast, let fn = f.next {
        slow = slow.next!
        fast = fn.next
    }
    let mid = slow.next
    slow.next = nil            // cut the list in two

    return merge(sortList(head), sortList(mid))
}

// Merge two sorted lists; stable (ties keep `a`'s node first).
private func merge(_ a: ListNode?, _ b: ListNode?) -> ListNode? {
    var a = a
    var b = b
    let dummy = ListNode()
    var tail = dummy
    while let x = a, let y = b {
        if x.val <= y.val {
            tail.next = x
            a = x.next
        } else {
            tail.next = y
            b = y.next
        }
        tail = tail.next!
    }
    tail.next = a ?? b
    return dummy.next
}
Recommended Approach 1 of 3 · Sort the values, rewrite the nodesO(n log n) time · O(n) space

Binary Search

38. Find Peak Element

Medium · LC 162

Given an array where no two adjacent elements are equal, return the index of any peak, an element strictly greater than both neighbors, with the borders treated as negative infinity. Binary search works even though the array is unsorted: if the middle element is smaller than its right neighbor the slope rises rightward and a peak must exist on that side, otherwise a peak lies at the middle or to its left, so half the range is discarded each step until one index survives. The subtlety is the asymmetric shrink, moving the low bound past the middle but the high bound only onto it, because the middle itself may be the peak.

The first index with nums[i] > nums[i + 1] is a peak: everything before it was strictly rising, so nums[i] also beats its left neighbor. A fully rising array peaks at the last index. Correct on every input, but ignores the O(log n) demand in the constraints.

Cuts Approach 1's O(n) walk to O(log n): comparing nums[mid] with nums[mid + 1] tells which half must contain a peak, so half the array is discarded per step — the recurrence the editorial presents first.

Same decision rule as Approach 2 with the recursion flattened into a loop, dropping the O(log n) call stack; when lo == hi the survivor is a peak.

The first index with nums[i] > nums[i + 1] is a peak: everything before it was strictly rising, so nums[i] also beats its left neighbor. A fully rising array peaks at the last index. Correct on every input, but ignores the O(log n) demand in the constraints.

Cuts Approach 1's O(n) walk to O(log n): if nums[mid] < nums[mid+1] the values rise to the right and a peak must exist on that side (the virtual -inf wall at each end stops the climb); otherwise mid itself may be the peak, so keep it.

The first index with nums[i] > nums[i + 1] is a peak: everything before it was strictly rising, so nums[i] also beats its left neighbor. A fully rising array peaks at the last index. Correct on every input, but ignores the O(log n) demand in the constraints.

Cuts Approach 1's O(n) walk to O(log n): if nums[mid] < nums[mid+1] the values rise to the right and a peak must exist on that side (the virtual -inf wall at each end stops the climb); otherwise mid itself may be the peak, so keep it.

The first index with nums[i] > nums[i + 1] is a peak: everything before it was strictly rising, so nums[i] also beats its left neighbor. A fully rising array peaks at the last index. Correct on every input, but ignores the O(log n) demand in the constraints.

Cuts Approach 1's O(n) walk to O(log n): if nums[mid] < nums[mid+1] the values rise to the right and a peak must exist on that side (the virtual -inf wall at each end stops the climb); otherwise mid itself may be the peak, so keep it.

The first index with nums[i] > nums[i+1] is a peak: everything before it was strictly rising, so nums[i] also beats its left neighbor. A fully rising array peaks at the last index. Correct on every input, but ignores the O(log n) demand in the constraints.

Cuts Approach 1's O(n) walk to O(log n): if nums[mid] < nums[mid+1] the values rise to the right and a peak must exist on that side (the virtual -inf wall at each end stops the climb); otherwise mid itself may be the peak, so keep it.

The first index with nums[i] > nums[i + 1] is a peak: everything before it was strictly rising, so nums[i] also beats its left neighbor. A fully rising array peaks at the last index. Correct on every input, but ignores the O(log n) demand in the constraints.

Cuts Approach 1's O(n) walk to O(log n): if nums[mid] < nums[mid+1] the values rise to the right and a peak must exist on that side (the virtual -inf wall at each end stops the climb); otherwise mid itself may be the peak, so keep it.

def findPeakElement_linear(self, nums: List[int]) -> int:
    for i in range(len(nums) - 1):
        if nums[i] > nums[i + 1]:
            return i
    return len(nums) - 1
def findPeakElement_recursive(self, nums: List[int]) -> int:
    def search(lo: int, hi: int) -> int:
        if lo == hi:
            return lo
        mid = (lo + hi) // 2
        if nums[mid] < nums[mid + 1]:
            return search(mid + 1, hi)
        return search(lo, mid)

    return search(0, len(nums) - 1)
def findPeakElement(self, nums: List[int]) -> int:
    lo, hi = 0, len(nums) - 1
    while lo < hi:
        mid = (lo + hi) // 2
        if nums[mid] < nums[mid + 1]:
            lo = mid + 1        # peak lies strictly right of mid
        else:
            hi = mid            # mid could itself be the peak
    return lo
int findPeakElementLinearScan(vector<int>& nums) {
    const int n = static_cast<int>(nums.size());
    for (int i = 0; i + 1 < n; ++i) {
        if (nums[i] > nums[i + 1]) return i;
    }
    return n - 1;
}
int findPeakElement(vector<int>& nums) {
    int lo = 0, hi = static_cast<int>(nums.size()) - 1;
    while (lo < hi) {
        int mid = lo + (hi - lo) / 2;
        if (nums[mid] < nums[mid + 1]) {
            lo = mid + 1;  // peak lies strictly right of mid
        } else {
            hi = mid;      // mid could itself be the peak
        }
    }
    return lo;
}
pub fn find_peak_element_linear_scan(nums: Vec<i32>) -> i32 {
    for i in 0..nums.len() - 1 {
        if nums[i] > nums[i + 1] {
            return i as i32;
        }
    }
    nums.len() as i32 - 1
}
pub fn find_peak_element(nums: Vec<i32>) -> i32 {
    let (mut lo, mut hi) = (0, nums.len() - 1);
    while lo < hi {
        let mid = lo + (hi - lo) / 2;
        if nums[mid] < nums[mid + 1] {
            lo = mid + 1; // peak lies strictly right of mid
        } else {
            hi = mid; // mid could itself be the peak
        }
    }
    lo as i32
}
function findPeakElementLinearScan(nums: number[]): number {
    for (let i = 0; i + 1 < nums.length; i++) {
        if (nums[i] > nums[i + 1]) return i;
    }
    return nums.length - 1;
}
function findPeakElement(nums: number[]): number {
    let lo = 0;
    let hi = nums.length - 1;
    while (lo < hi) {
        const mid = lo + Math.floor((hi - lo) / 2);
        if (nums[mid] < nums[mid + 1]) {
            lo = mid + 1; // peak lies strictly right of mid
        } else {
            hi = mid; // mid could itself be the peak
        }
    }
    return lo;
}
func findPeakElementLinearScan(nums []int) int {
	for i := 0; i+1 < len(nums); i++ {
		if nums[i] > nums[i+1] {
			return i
		}
	}
	return len(nums) - 1
}
func findPeakElement(nums []int) int {
	lo, hi := 0, len(nums)-1
	for lo < hi {
		mid := lo + (hi-lo)/2
		if nums[mid] < nums[mid+1] {
			lo = mid + 1 // peak lies strictly right of mid
		} else {
			hi = mid // mid could itself be the peak
		}
	}
	return lo
}
func findPeakElementLinearScan(_ nums: [Int]) -> Int {
    for i in 0..<(nums.count - 1) where nums[i] > nums[i + 1] {
        return i
    }
    return nums.count - 1
}
func findPeakElement(_ nums: [Int]) -> Int {
    var lo = 0
    var hi = nums.count - 1
    while lo < hi {
        let mid = lo + (hi - lo) / 2
        if nums[mid] < nums[mid + 1] {
            lo = mid + 1  // peak lies strictly right of mid
        } else {
            hi = mid      // mid could itself be the peak
        }
    }
    return lo
}
Recommended Approach 1 of 3 · linear scan for the first descentO(n) time · O(1) space

39. Find First and Last Position of Element in Sorted Array

Medium · LC 34

Given a sorted array and a target, return the first and last index at which the target appears, or a pair of negative ones, in logarithmic time. One lower-bound binary search, returning the first index whose value is at least the query, answers both questions: called on the target it finds the first occurrence, and called on the target plus one it lands just past the last. The trick is verifying that the first result actually holds the target before trusting it, since a lower bound happily returns an insertion point when the value is absent.

Ignores the sortedness entirely: note the first and last index seen holding the target. Trivially correct, but misses the required O(log n) bound.

bisect_left/bisect_right ARE lower/upper bound; outside an interview reach for these instead of hand-rolling the loop.

The loop bisect hides, written out: lower_bound(target) is the first index holding target (if any), and lower_bound(target + 1) - 1 is the last. One helper, two calls.

Ignores the sortedness entirely: note the first and last index seen holding the target. Trivially correct, but misses the required O(log n) bound.

lowerBound(target) is the first index holding target (if any), and lowerBound(target + 1) - 1 is the last. One helper, two calls.

Ignores the sortedness entirely: note the first and last index seen holding the target. Trivially correct, but misses the required O(log n) bound.

lower_bound(target) is the first index holding target (if any), and lower_bound(target + 1) - 1 is the last. One helper, two calls.

Ignores the sortedness entirely: note the first and last index seen holding the target. Trivially correct, but misses the required O(log n) bound.

lowerBound(target) is the first index holding target (if any), and lowerBound(target + 1) - 1 is the last. One helper, two calls.

Ignores the sortedness entirely: note the first and last index seen holding the target. Trivially correct, but misses the required O(log n) bound.

lowerBound(target) is the first index holding target (if any), and lowerBound(target+1)-1 is the last. One helper, two calls.

Ignores the sortedness entirely: note the first and last index seen holding the target. Trivially correct, but misses the required O(log n) bound.

lowerBound(target) is the first index holding target (if any), and lowerBound(target + 1) - 1 is the last. One helper, two calls.

def searchRange_linear(self, nums: list[int], target: int) -> list[int]:
    first = last = -1
    for i, val in enumerate(nums):
        if val == target:
            if first == -1:
                first = i
            last = i
    return [first, last]
def searchRange_bisect(self, nums: list[int], target: int) -> list[int]:
    first = bisect_left(nums, target)
    if first == len(nums) or nums[first] != target:
        return [-1, -1]
    return [first, bisect_right(nums, target) - 1]
def searchRange(self, nums: list[int], target: int) -> list[int]:
    first = self._lower_bound(nums, target)
    if first == len(nums) or nums[first] != target:
        return [-1, -1]
    last = self._lower_bound(nums, target + 1) - 1
    return [first, last]

@staticmethod
def _lower_bound(nums: list[int], target: int) -> int:
    """First index i with nums[i] >= target (len(nums) if none)."""
    lo, hi = 0, len(nums)
    while lo < hi:
        mid = (lo + hi) // 2
        if nums[mid] < target:
            lo = mid + 1
        else:
            hi = mid
    return lo
vector<int> searchRangeLinear(vector<int>& nums, int target) {
    int first = -1, last = -1;
    for (int i = 0; i < (int)nums.size(); ++i) {
        if (nums[i] == target) {
            if (first == -1)
                first = i;
            last = i;
        }
    }
    return {first, last};
}
    vector<int> searchRange(vector<int>& nums, int target) {
        int first = lowerBound(nums, target);
        if (first == (int)nums.size() || nums[first] != target)
            return {-1, -1};
        int last = lowerBound(nums, target + 1) - 1;
        return {first, last};
    }

private:
    // First index i with nums[i] >= target (nums.size() if none).
    static int lowerBound(const vector<int>& nums, int target) {
        int lo = 0, hi = (int)nums.size();
        while (lo < hi) {
            int mid = lo + (hi - lo) / 2;
            if (nums[mid] < target)
                lo = mid + 1;
            else
                hi = mid;
        }
        return lo;
    }
pub fn search_range_linear(nums: Vec<i32>, target: i32) -> Vec<i32> {
    let mut first = -1i32;
    let mut last = -1i32;
    for (i, &v) in nums.iter().enumerate() {
        if v == target {
            if first == -1 {
                first = i as i32;
            }
            last = i as i32;
        }
    }
    vec![first, last]
}
pub fn search_range(nums: Vec<i32>, target: i32) -> Vec<i32> {
    let first = Self::lower_bound(&nums, target as i64);
    if first == nums.len() || nums[first] != target {
        return vec![-1, -1];
    }
    let last = Self::lower_bound(&nums, target as i64 + 1) - 1;
    vec![first as i32, last as i32]
}

/// First index i with nums[i] >= target (nums.len() if none).
fn lower_bound(nums: &[i32], target: i64) -> usize {
    let (mut lo, mut hi) = (0usize, nums.len());
    while lo < hi {
        let mid = lo + (hi - lo) / 2;
        if (nums[mid] as i64) < target {
            lo = mid + 1;
        } else {
            hi = mid;
        }
    }
    lo
}
function searchRangeLinear(nums: number[], target: number): number[] {
    let first = -1;
    let last = -1;
    for (let i = 0; i < nums.length; i++) {
        if (nums[i] === target) {
            if (first === -1) {
                first = i;
            }
            last = i;
        }
    }
    return [first, last];
}
function searchRange(nums: number[], target: number): number[] {
    // First index i with nums[i] >= t (nums.length if none).
    const lowerBound = (t: number): number => {
        let lo = 0;
        let hi = nums.length;
        while (lo < hi) {
            const mid = (lo + hi) >>> 1;
            if (nums[mid] < t) {
                lo = mid + 1;
            } else {
                hi = mid;
            }
        }
        return lo;
    };

    const first = lowerBound(target);
    if (first === nums.length || nums[first] !== target) {
        return [-1, -1];
    }
    return [first, lowerBound(target + 1) - 1];
}
func searchRangeLinear(nums []int, target int) []int {
	first, last := -1, -1
	for i, v := range nums {
		if v == target {
			if first == -1 {
				first = i
			}
			last = i
		}
	}
	return []int{first, last}
}
func searchRange(nums []int, target int) []int {
	first := lowerBound(nums, target)
	if first == len(nums) || nums[first] != target {
		return []int{-1, -1}
	}
	last := lowerBound(nums, target+1) - 1
	return []int{first, last}
}

// lowerBound returns the first index i with nums[i] >= target (len(nums) if none).
func lowerBound(nums []int, target int) int {
	lo, hi := 0, len(nums)
	for lo < hi {
		mid := (lo + hi) / 2
		if nums[mid] < target {
			lo = mid + 1
		} else {
			hi = mid
		}
	}
	return lo
}
func searchRangeLinear(_ nums: [Int], _ target: Int) -> [Int] {
    var first = -1
    var last = -1
    for (i, v) in nums.enumerated() where v == target {
        if first == -1 {
            first = i
        }
        last = i
    }
    return [first, last]
}
func searchRange(_ nums: [Int], _ target: Int) -> [Int] {
    let first = lowerBound(nums, target)
    if first == nums.count || nums[first] != target {
        return [-1, -1]
    }
    return [first, lowerBound(nums, target + 1) - 1]
}

// First index i with nums[i] >= target (nums.count if none).
private func lowerBound(_ nums: [Int], _ target: Int) -> Int {
    var lo = 0
    var hi = nums.count
    while lo < hi {
        let mid = (lo + hi) / 2
        if nums[mid] < target {
            lo = mid + 1
        } else {
            hi = mid
        }
    }
    return lo
}
Recommended Approach 1 of 3 · Linear scan (baseline)O(n) time · O(1) space

Heap

40. Find K Pairs with Smallest Sums

Medium · LC 373

Given two sorted arrays and an integer k, return the k pairs taking one element from each array with the smallest sums. Picture the sums as a matrix whose rows and columns are sorted, and run a min-heap over its frontier: seed it with the first cell of at most the first k rows, then each time a pair is popped push only its right neighbor from the same row. The insight is that the heap never needs more than one cell per row, so when k is small only a few rows are ever seeded, beating a k-way merge that would prime every row up front.

nsmallest keeps a k-sized heap while scanning every pair. Ignores the sortedness entirely — fine at the m*n <= 30 sizes here, hopeless at LeetCode's m = n = 1e5 (1e10 pairs).

First use of the sortedness: row i of the implicit matrix nums1[i] + nums2[j] is a sorted stream, so merge the m streams and slice off the first k. Elegant, but seeds all m rows even when k is tiny.

Seed the heap with the first cell of the first min(k, m) rows only; each pop of (i, j) exposes its right neighbour (i, j + 1). The heap never holds more than one frontier cell per row, which drops the seeding cost of the k-way merge when k << m.

Ignores the sortedness entirely: materialize every pair, sort by sum, keep the first k. Fine at the m*n <= 30 sizes here, hopeless at LeetCode's m = n = 1e5 (1e10 pairs).

Row i of the implicit matrix nums1[i] + nums2[j] is sorted, so seed the heap with (i, 0) for the first min(k, m) rows and push (i, j + 1) after popping (i, j). Touches only O(k) cells instead of all m*n.

Ignores the sortedness entirely: materialize every pair, sort by sum, keep the first k. Fine at the m*n <= 30 sizes here, hopeless at LeetCode's m = n = 1e5 (1e10 pairs).

Row i of the implicit matrix nums1[i] + nums2[j] is sorted, so seed the heap with (i, 0) for the first min(k, m) rows and push (i, j + 1) after popping (i, j). Touches only O(k) cells instead of all m*n.

Ignores the sortedness entirely: materialize every pair, sort by sum, keep the first k. Fine at the m*n <= 30 sizes here, hopeless at LeetCode's m = n = 1e5 (1e10 pairs).

Row i of the implicit matrix nums1[i] + nums2[j] is sorted, so seed the heap with (i, 0) for the first min(k, m) rows and push (i, j + 1) after popping (i, j). Touches only O(k) cells instead of all m*n. JS has no stdlib priority queue, so a small array-backed binary min-heap of [sum, i, j] cells is inlined.

Ignores the sortedness entirely: materialize every pair, sort by sum, keep the first k. Fine at the m*n <= 30 sizes here, hopeless at LeetCode's m = n = 1e5 (1e10 pairs).

Row i of the implicit matrix nums1[i]+nums2[j] is sorted, so seed the heap with (i, 0) for the first min(k, m) rows and push (i, j+1) after popping (i, j). Touches only O(k) cells instead of all m*n.

Ignores the sortedness entirely: materialize every pair, sort by sum, keep the first k. Fine at the m*n <= 30 sizes here, hopeless at LeetCode's m = n = 1e5 (1e10 pairs).

Row i of the implicit matrix nums1[i] + nums2[j] is sorted, so seed the heap with (i, 0) for the first min(k, m) rows and push (i, j + 1) after popping (i, j). Touches only O(k) cells instead of all m*n. Swift has no stdlib priority queue, so a small array-backed binary min-heap is inlined.

def kSmallestPairs_brute(self, nums1: list[int], nums2: list[int],
                         k: int) -> list[list[int]]:
    return heapq.nsmallest(k, ([a, b] for a in nums1 for b in nums2),
                           key=sum)
def kSmallestPairs_merge(self, nums1: list[int], nums2: list[int],
                         k: int) -> list[list[int]]:
    def row(a: int):
        return ((a + b, a, b) for b in nums2)

    merged = heapq.merge(*(row(a) for a in nums1))
    return [[a, b] for _, a, b in islice(merged, k)]
def kSmallestPairs(self, nums1: list[int], nums2: list[int],
                   k: int) -> list[list[int]]:
    heap = [(nums1[i] + nums2[0], i, 0)
            for i in range(min(k, len(nums1)))]
    heapq.heapify(heap)

    result: list[list[int]] = []
    while heap and len(result) < k:
        _, i, j = heapq.heappop(heap)
        result.append([nums1[i], nums2[j]])
        if j + 1 < len(nums2):
            heapq.heappush(heap, (nums1[i] + nums2[j + 1], i, j + 1))
    return result
vector<vector<int>> kSmallestPairsBruteForce(vector<int>& nums1,
                                             vector<int>& nums2, int k) {
    vector<tuple<long long, int, int>> all;  // (sum, u, v)
    all.reserve(nums1.size() * nums2.size());
    for (int a : nums1)
        for (int b : nums2)
            all.emplace_back((long long)a + b, a, b);
    sort(all.begin(), all.end());

    vector<vector<int>> result;
    for (int i = 0; i < (int)all.size() && i < k; ++i)
        result.push_back({get<1>(all[i]), get<2>(all[i])});
    return result;
}
vector<vector<int>> kSmallestPairs(vector<int>& nums1, vector<int>& nums2,
                                   int k) {
    using Cell = tuple<long long, int, int>;  // (sum, i, j)
    priority_queue<Cell, vector<Cell>, greater<Cell>> heap;

    int m = (int)nums1.size(), n = (int)nums2.size();
    for (int i = 0; i < min(k, m); ++i)
        heap.emplace((long long)nums1[i] + nums2[0], i, 0);

    vector<vector<int>> result;
    while (!heap.empty() && (int)result.size() < k) {
        auto [sum, i, j] = heap.top();
        heap.pop();
        result.push_back({nums1[i], nums2[j]});
        if (j + 1 < n)
            heap.emplace((long long)nums1[i] + nums2[j + 1], i, j + 1);
    }
    return result;
}
pub fn k_smallest_pairs_brute_force(nums1: Vec<i32>, nums2: Vec<i32>, k: i32) -> Vec<Vec<i32>> {
    let mut all: Vec<(i64, i32, i32)> = Vec::with_capacity(nums1.len() * nums2.len());
    for &a in &nums1 {
        for &b in &nums2 {
            all.push((a as i64 + b as i64, a, b));
        }
    }
    all.sort_unstable();
    all.into_iter()
        .take(k as usize)
        .map(|(_, a, b)| vec![a, b])
        .collect()
}
pub fn k_smallest_pairs(nums1: Vec<i32>, nums2: Vec<i32>, k: i32) -> Vec<Vec<i32>> {
    let k = k as usize;
    let mut heap: BinaryHeap<Reverse<(i64, usize, usize)>> = BinaryHeap::new();
    for i in 0..nums1.len().min(k) {
        heap.push(Reverse((nums1[i] as i64 + nums2[0] as i64, i, 0)));
    }

    let mut result = Vec::new();
    while result.len() < k {
        let Some(Reverse((_, i, j))) = heap.pop() else {
            break;
        };
        result.push(vec![nums1[i], nums2[j]]);
        if j + 1 < nums2.len() {
            heap.push(Reverse((nums1[i] as i64 + nums2[j + 1] as i64, i, j + 1)));
        }
    }
    result
}
function kSmallestPairsBruteForce(
    nums1: number[],
    nums2: number[],
    k: number,
): number[][] {
    const all: number[][] = [];
    for (const a of nums1) {
        for (const b of nums2) {
            all.push([a, b]);
        }
    }
    all.sort((p, q) => p[0] + p[1] - (q[0] + q[1]));
    return all.slice(0, k);
}
class MinHeap {
    private items: [number, number, number][] = [];

    get size(): number {
        return this.items.length;
    }

    push(item: [number, number, number]): void {
        const items = this.items;
        items.push(item);
        let child = items.length - 1;
        while (child > 0) {
            const parent = (child - 1) >> 1;
            if (items[parent][0] <= items[child][0]) break;
            const tmp = items[parent];
            items[parent] = items[child];
            items[child] = tmp;
            child = parent;
        }
    }

    pop(): [number, number, number] {
        const items = this.items;
        const top = items[0];
        const last = items.pop() as [number, number, number];
        if (items.length > 0) {
            items[0] = last;
            let parent = 0;
            for (;;) {
                const left = 2 * parent + 1;
                if (left >= items.length) break;
                let smallest = left;
                const right = left + 1;
                if (right < items.length && items[right][0] < items[left][0]) {
                    smallest = right;
                }
                if (items[parent][0] <= items[smallest][0]) break;
                const tmp = items[parent];
                items[parent] = items[smallest];
                items[smallest] = tmp;
                parent = smallest;
            }
        }
        return top;
    }
}

function kSmallestPairs(nums1: number[], nums2: number[], k: number): number[][] {
    const heap = new MinHeap();
    for (let i = 0; i < nums1.length && i < k; i++) {
        heap.push([nums1[i] + nums2[0], i, 0]);
    }

    const result: number[][] = [];
    while (heap.size > 0 && result.length < k) {
        const [, i, j] = heap.pop();
        result.push([nums1[i], nums2[j]]);
        if (j + 1 < nums2.length) {
            heap.push([nums1[i] + nums2[j + 1], i, j + 1]);
        }
    }
    return result;
}
func kSmallestPairsBruteForce(nums1 []int, nums2 []int, k int) [][]int {
	all := make([][]int, 0, len(nums1)*len(nums2))
	for _, a := range nums1 {
		for _, b := range nums2 {
			all = append(all, []int{a, b})
		}
	}
	sort.Slice(all, func(i, j int) bool {
		return all[i][0]+all[i][1] < all[j][0]+all[j][1]
	})
	if k > len(all) {
		k = len(all)
	}
	return all[:k]
}
type cell struct{ sum, i, j int }

type cellHeap []cell

func (h cellHeap) Len() int           { return len(h) }
func (h cellHeap) Less(a, b int) bool { return h[a].sum < h[b].sum }
func (h cellHeap) Swap(a, b int)      { h[a], h[b] = h[b], h[a] }
func (h *cellHeap) Push(x any)        { *h = append(*h, x.(cell)) }
func (h *cellHeap) Pop() any {
	old := *h
	last := old[len(old)-1]
	*h = old[:len(old)-1]
	return last
}

func kSmallestPairs(nums1 []int, nums2 []int, k int) [][]int {
	frontier := &cellHeap{}
	for i := 0; i < len(nums1) && i < k; i++ {
		*frontier = append(*frontier, cell{nums1[i] + nums2[0], i, 0})
	}
	heap.Init(frontier)

	result := make([][]int, 0, k)
	for frontier.Len() > 0 && len(result) < k {
		top := heap.Pop(frontier).(cell)
		result = append(result, []int{nums1[top.i], nums2[top.j]})
		if top.j+1 < len(nums2) {
			heap.Push(frontier, cell{nums1[top.i] + nums2[top.j+1], top.i, top.j + 1})
		}
	}
	return result
}
func kSmallestPairsBruteForce(_ nums1: [Int], _ nums2: [Int], _ k: Int) -> [[Int]] {
    var all: [[Int]] = []
    all.reserveCapacity(nums1.count * nums2.count)
    for a in nums1 {
        for b in nums2 {
            all.append([a, b])
        }
    }
    all.sort { $0[0] + $0[1] < $1[0] + $1[1] }
    return Array(all.prefix(k))
}
private struct MinHeap {
    private var items: [(sum: Int, i: Int, j: Int)] = []

    var isEmpty: Bool { items.isEmpty }

    mutating func push(_ item: (sum: Int, i: Int, j: Int)) {
        items.append(item)
        var child = items.count - 1
        while child > 0 {
            let parent = (child - 1) / 2
            if items[parent].sum <= items[child].sum { break }
            items.swapAt(parent, child)
            child = parent
        }
    }

    mutating func pop() -> (sum: Int, i: Int, j: Int) {
        let top = items[0]
        items[0] = items[items.count - 1]
        items.removeLast()
        var parent = 0
        while true {
            let left = 2 * parent + 1
            if left >= items.count { break }
            var smallest = left
            let right = left + 1
            if right < items.count && items[right].sum < items[left].sum {
                smallest = right
            }
            if items[parent].sum <= items[smallest].sum { break }
            items.swapAt(parent, smallest)
            parent = smallest
        }
        return top
    }
}

func kSmallestPairs(_ nums1: [Int], _ nums2: [Int], _ k: Int) -> [[Int]] {
    var heap = MinHeap()
    for i in 0..<min(k, nums1.count) {
        heap.push((nums1[i] + nums2[0], i, 0))
    }

    var result: [[Int]] = []
    while !heap.isEmpty && result.count < k {
        let (_, i, j) = heap.pop()
        result.append([nums1[i], nums2[j]])
        if j + 1 < nums2.count {
            heap.push((nums1[i] + nums2[j + 1], i, j + 1))
        }
    }
    return result
}
Recommended Approach 1 of 3 · Brute force over all m*n pairsO(m n log k) time · O(k) (beyond the generator) space

Bit Manipulation

41. Single Number II

Medium · LC 137

Given an array where every number appears exactly three times except one that appears once, find that number in linear time and constant space. Two integer masks, ones and twos, act as a per-bit counter modulo three: each incoming number XORs into ones unless that bit is already recorded in twos, then into twos unless it just landed in ones, so any bit seen three times cancels back to zero and the lone number is left sitting in ones. The trick is recognizing the update as a two-bit state machine that cycles through zero, one, and two occurrences per bit position, run across all bit positions at once.

3 * sum(unique) counts every element thrice, so the difference from sum(nums) is exactly two copies of the single. Easiest to reason about, but the set costs O(n) space and misses the follow-up.

Drops the O(n) set: sum each of the 32 bit positions over the array; the lone number owns exactly the positions whose count is not divisible by 3. Bit 31 is the sign bit, so fold the raw result back into signed 32-bit range.

The 32x inner loop of Approach 2 collapsed into two masks: per bit position, count-mod-3 cycles 00 -> 01 -> 10 -> 00; `ones` and `twos` hold the low/high state bits across all positions at once, so any bit seen three times cancels and the lone number is left in `ones`. Python ints act as infinite two's complement, so negatives fall out correctly with no masking.

Count every value, return the one seen once. The obvious first answer, but the map costs O(n) space and misses the follow-up.

Drops the O(n) map: sum each of the 32 bit positions over the array; the lone number owns exactly the positions whose count is not divisible by 3. Assemble in unsigned so the sign bit needs no special case.

The 32x inner loop of Approach 2 collapsed into two masks: per bit position, count-mod-3 cycles 00 -> 01 -> 10 -> 00. ones/twos hold the low/high state bits for all 32 positions at once, so bits seen three times cancel and the lone number remains in ones. Bitwise ops are well defined on negatives.

Count every value, return the one seen once. The obvious first answer, but the map costs O(n) space and misses the follow-up.

Drops the O(n) map: sum each of the 32 bit positions over the array; the lone number owns exactly the positions whose count is not divisible by 3. Assemble in u32 so the sign bit needs no special case.

The 32x inner loop of Approach 2 collapsed into two masks: per bit position, count-mod-3 cycles 00 -> 01 -> 10 -> 00. ones/twos hold the low/high state bits for all 32 positions at once, so bits seen three times cancel and the lone number remains in ones. Bitwise ops are well defined on negatives.

Count every value, return the one seen once. The obvious first answer, but the map costs O(n) space and misses the follow-up.

Drops the O(n) map: sum each of the 32 bit positions over the array; the lone number owns exactly the positions whose count is not divisible by 3. |= with 1 << 31 lands on the sign bit, so the signed result assembles itself.

The 32x inner loop of Approach 2 collapsed into two masks: per bit position, count-mod-3 cycles 00 -> 01 -> 10 -> 00. ones/twos hold the low/high state bits for all 32 positions at once, so bits seen three times cancel and the lone number remains in ones. Negatives fall out correctly under 32-bit bitwise semantics.

Count every value, return the one seen once. The obvious first answer, but the map costs O(n) space and misses the follow-up.

Drops the O(n) map: sum each of the 32 bit positions over the array; the lone number owns exactly the positions whose count is not divisible by 3. Assemble in uint32, then sign-extend via int32.

The 32x inner loop of Approach 2 collapsed into two masks: per bit position, count-mod-3 cycles 00 -> 01 -> 10 -> 00. ones/twos hold the low/high state bits for every position at once, so bits seen three times cancel and the lone number remains in ones. Works for negatives: sign-extended bits follow the same automaton.

Count every value, return the one seen once. The obvious first answer, but the dictionary costs O(n) space and misses the follow-up.

Drops the O(n) dictionary: sum each of the 32 bit positions over the array; the lone number owns exactly the positions whose count is not divisible by 3. Bit 31 is the sign bit of the 32-bit value, so fold the raw result back into signed 32-bit range.

The 32x inner loop of Approach 2 collapsed into two masks: per bit position, count-mod-3 cycles 00 -> 01 -> 10 -> 00. ones/twos hold the low/high state bits for every position at once, so bits seen three times cancel and the lone number remains in ones. Sign-extended bits follow the same automaton, so negatives fall out correctly.

def singleNumber_math(self, nums: list[int]) -> int:
    return (3 * sum(set(nums)) - sum(nums)) // 2
def singleNumber_bit_count(self, nums: list[int]) -> int:
    result = 0
    for bit in range(32):
        count = sum((num >> bit) & 1 for num in nums)
        if count % 3:
            result |= 1 << bit
    return result - (1 << 32) if result >= (1 << 31) else result
def singleNumber(self, nums: list[int]) -> int:
    ones = twos = 0
    for num in nums:
        ones = (ones ^ num) & ~twos
        twos = (twos ^ num) & ~ones
    return ones
int singleNumberHashCount(vector<int>& nums) {
    unordered_map<int, int> counts;
    for (int num : nums)
        ++counts[num];
    for (const auto& [num, count] : counts)
        if (count == 1)
            return num;
    return 0;  // unreachable on valid input
}
int singleNumberBitCount(vector<int>& nums) {
    unsigned result = 0;
    for (int bit = 0; bit < 32; ++bit) {
        int count = 0;
        for (int num : nums)
            count += ((unsigned)num >> bit) & 1u;
        if (count % 3)
            result |= 1u << bit;
    }
    return (int)result;
}
int singleNumber(vector<int>& nums) {
    int ones = 0, twos = 0;
    for (int num : nums) {
        ones = (ones ^ num) & ~twos;
        twos = (twos ^ num) & ~ones;
    }
    return ones;
}
pub fn single_number_hash_count(nums: Vec<i32>) -> i32 {
    let mut counts: HashMap<i32, i32> = HashMap::new();
    for num in nums {
        *counts.entry(num).or_insert(0) += 1;
    }
    counts
        .into_iter()
        .find(|&(_, count)| count == 1)
        .map(|(num, _)| num)
        .unwrap_or(0) // unreachable on valid input
}
pub fn single_number_bit_count(nums: Vec<i32>) -> i32 {
    let mut result: u32 = 0;
    for bit in 0..32 {
        let count = nums.iter().filter(|&&num| (num >> bit) & 1 == 1).count();
        if count % 3 != 0 {
            result |= 1 << bit;
        }
    }
    result as i32
}
pub fn single_number(nums: Vec<i32>) -> i32 {
    let (mut ones, mut twos) = (0i32, 0i32);
    for num in nums {
        ones = (ones ^ num) & !twos;
        twos = (twos ^ num) & !ones;
    }
    ones
}
function singleNumberHashCount(nums: number[]): number {
    const counts = new Map<number, number>();
    for (const num of nums) {
        counts.set(num, (counts.get(num) ?? 0) + 1);
    }
    for (const [num, count] of counts) {
        if (count === 1) {
            return num;
        }
    }
    return 0; // unreachable on valid input
}
function singleNumberBitCount(nums: number[]): number {
    let result = 0;
    for (let bit = 0; bit < 32; bit++) {
        let count = 0;
        for (const num of nums) {
            count += (num >> bit) & 1;
        }
        if (count % 3 !== 0) {
            result |= 1 << bit;
        }
    }
    return result;
}
function singleNumber(nums: number[]): number {
    let ones = 0;
    let twos = 0;
    for (const num of nums) {
        ones = (ones ^ num) & ~twos;
        twos = (twos ^ num) & ~ones;
    }
    return ones;
}
func singleNumberHashCount(nums []int) int {
	counts := make(map[int]int, len(nums))
	for _, num := range nums {
		counts[num]++
	}
	for num, count := range counts {
		if count == 1 {
			return num
		}
	}
	return 0 // unreachable on valid input
}
func singleNumberBitCount(nums []int) int {
	var result uint32
	for bit := 0; bit < 32; bit++ {
		count := 0
		for _, num := range nums {
			count += int(uint32(num)>>bit) & 1
		}
		if count%3 != 0 {
			result |= 1 << bit
		}
	}
	return int(int32(result))
}
func singleNumber(nums []int) int {
	ones, twos := 0, 0
	for _, num := range nums {
		ones = (ones ^ num) &^ twos
		twos = (twos ^ num) &^ ones
	}
	return ones
}
func singleNumberHashCount(_ nums: [Int]) -> Int {
    var counts: [Int: Int] = [:]
    for num in nums {
        counts[num, default: 0] += 1
    }
    for (num, count) in counts where count == 1 {
        return num
    }
    return 0  // unreachable on valid input
}
func singleNumberBitCount(_ nums: [Int]) -> Int {
    var result = 0
    for bit in 0..<32 {
        var count = 0
        for num in nums {
            count += (num >> bit) & 1
        }
        if count % 3 != 0 {
            result |= 1 << bit
        }
    }
    if result >= (1 << 31) {
        result -= (1 << 32)
    }
    return result
}
func singleNumber(_ nums: [Int]) -> Int {
    var ones = 0
    var twos = 0
    for num in nums {
        ones = (ones ^ num) & ~twos
        twos = (twos ^ num) & ~ones
    }
    return ones
}
Recommended Approach 1 of 3 · Set arithmeticO(n) time · O(n) space

Math

42. Palindrome Number

Easy · LC 9

Given an integer, decide whether it reads the same forward and backward, ideally without converting it to a string. Reject negatives and any nonzero number ending in zero, then peel digits off the tail into a reversed accumulator until it catches up with the shrinking head, and compare the two halves, dropping the accumulator's last digit when the length is odd. Reversing only half means the accumulator can never overflow a 32-bit integer in stricter languages, and the ending-in-zero pre-check matters because such numbers would otherwise slip through the half-length comparison.

The obvious one-liner; costs an extra string copy. A leading '-' never equals the trailing digit, so negatives fall out naturally.

Drops the string copy: reverse every digit and compare. Fine in Python; in 32-bit languages the full reversal can overflow, which is why Approach 3 stops halfway.

Peel digits off the tail onto `reverted` until it catches up with the head, then compare (dropping the middle digit for odd lengths). No string, and the reversal can never overflow a 32-bit int elsewhere.

Convert, reverse, compare. Easy to write but allocates two strings. A leading '-' never equals the trailing digit, so negatives fall out naturally.

Peel digits off the tail onto `reverted` until it catches up with what remains of x, then compare (dropping the middle digit for odd lengths). No allocation, and reversing only half the digits means the 32-bit int can never overflow — a full reversal could.

Convert, reverse, compare. Easy to write but allocates two strings. A leading '-' never equals the trailing digit, so negatives fall out naturally.

Peel digits off the tail onto `reverted` until it catches up with what remains of x, then compare (dropping the middle digit for odd lengths). No allocation, and reversing only half the digits can never overflow i32 — a full reversal could.

Convert, reverse, compare. Easy to write but allocates an array and two strings. A leading '-' never equals the trailing digit, so negatives fall out naturally.

Peel digits off the tail onto `reverted` until it catches up with what remains of x, then compare (dropping the middle digit for odd lengths). No allocation, and reversing only half the digits is the habit that keeps 32-bit ports overflow-free — a full reversal could overflow there.

Convert, reverse the bytes, compare. Easy to write but allocates two strings. A leading '-' never equals the trailing digit, so negatives fall out naturally.

Peel digits off the tail onto reverted until it catches up with what remains of x, then compare (dropping the middle digit for odd lengths). No allocation, and reversing only half the digits can never overflow even a 32-bit int — a full reversal could.

Convert, reverse, compare. Easy to write but allocates two strings. A leading '-' never equals the trailing digit, so negatives fall out naturally.

Peel digits off the tail onto `reverted` until it catches up with what remains of x, then compare (dropping the middle digit for odd lengths). No allocation, and reversing only half the digits can never overflow even a 32-bit int — a full reversal could.

def isPalindrome_string(self, x: int) -> bool:
    s = str(x)
    return s == s[::-1]
def isPalindrome_full_reverse(self, x: int) -> bool:
    if x < 0:
        return False
    reversed_x, remaining = 0, x
    while remaining:
        reversed_x = reversed_x * 10 + remaining % 10
        remaining //= 10
    return reversed_x == x
def isPalindrome(self, x: int) -> bool:
    if x < 0 or (x % 10 == 0 and x != 0):
        return False
    reverted = 0
    while x > reverted:
        reverted = reverted * 10 + x % 10
        x //= 10
    return x == reverted or x == reverted // 10
bool isPalindromeString(int x) {
    string s = to_string(x);
    string reversed(s.rbegin(), s.rend());
    return s == reversed;
}
bool isPalindrome(int x) {
    if (x < 0 || (x % 10 == 0 && x != 0)) return false;
    int reverted = 0;
    while (x > reverted) {
        reverted = reverted * 10 + x % 10;
        x /= 10;
    }
    return x == reverted || x == reverted / 10;
}
pub fn is_palindrome_string(x: i32) -> bool {
    let s = x.to_string();
    let reversed: String = s.chars().rev().collect();
    s == reversed
}
pub fn is_palindrome(x: i32) -> bool {
    if x < 0 || (x % 10 == 0 && x != 0) {
        return false;
    }
    let mut x = x;
    let mut reverted = 0;
    while x > reverted {
        reverted = reverted * 10 + x % 10;
        x /= 10;
    }
    x == reverted || x == reverted / 10
}
function isPalindromeString(x: number): boolean {
    const s = String(x);
    return s === [...s].reverse().join("");
}
function isPalindrome(x: number): boolean {
    if (x < 0 || (x % 10 === 0 && x !== 0)) {
        return false;
    }
    let reverted = 0;
    while (x > reverted) {
        reverted = reverted * 10 + (x % 10);
        x = Math.floor(x / 10);
    }
    return x === reverted || x === Math.floor(reverted / 10);
}
func isPalindromeString(x int) bool {
	s := strconv.Itoa(x)
	b := []byte(s)
	for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
		b[i], b[j] = b[j], b[i]
	}
	return s == string(b)
}
func isPalindrome(x int) bool {
	if x < 0 || (x%10 == 0 && x != 0) {
		return false
	}
	reverted := 0
	for x > reverted {
		reverted = reverted*10 + x%10
		x /= 10
	}
	return x == reverted || x == reverted/10
}
func isPalindromeString(_ x: Int) -> Bool {
    let s = String(x)
    return s == String(s.reversed())
}
func isPalindrome(_ x: Int) -> Bool {
    if x < 0 || (x % 10 == 0 && x != 0) {
        return false
    }
    var head = x
    var reverted = 0
    while head > reverted {
        reverted = reverted * 10 + head % 10
        head /= 10
    }
    return head == reverted || head == reverted / 10
}
Recommended Approach 1 of 3 · String reversalO(d) time · O(d) space

43. Factorial Trailing Zeroes

Medium · LC 172

Given an integer n, count the trailing zeroes of n factorial without ever computing the factorial. Every trailing zero needs a factor of ten, and since twos vastly outnumber fives the answer is just the number of factors of five in the product, found by repeatedly floor-dividing n by five and summing the quotients. The elegance is that each successive quotient recounts the multiples of higher powers of five, so a multiple of twenty-five is credited twice and so on, giving the exact total in logarithmic time.

Only viable because Python integers are arbitrary precision; a slow but obviously-correct reference, and hopeless in 32/64-bit languages where n! overflows almost immediately.

Never materializes n!: walk 5, 10, 15, ... and add how many factors of 5 each contributes. Fixed-width safe, but still linear in n.

Count all multiples at once instead of visiting each: every term counts the multiples of that power of 5 up to n, so a multiple of 25 is counted twice, of 125 three times, and so on.

Walk 5, 10, 15, ... and add how many factors of 5 each contributes. Correct and overflow-free, but linear in n — fine at the n <= 10^4 constraint, wasteful next to the closed-form sum below.

Count all multiples at once instead of visiting each: every term counts the multiples of that power of 5 up to n, so a multiple of 25 is counted twice, of 125 three times, and so on.

Walk 5, 10, 15, ... and add how many factors of 5 each contributes. Correct and overflow-free, but linear in n — fine at the n <= 10^4 constraint, wasteful next to the closed-form sum below.

Count all multiples at once instead of visiting each: every term counts the multiples of that power of 5 up to n, so a multiple of 25 is counted twice, of 125 three times, and so on.

Walk 5, 10, 15, ... and add how many factors of 5 each contributes. Correct and overflow-free, but linear in n — fine at the n <= 10^4 constraint, wasteful next to the closed-form sum below.

Count all multiples at once instead of visiting each: every term counts the multiples of that power of 5 up to n, so a multiple of 25 is counted twice, of 125 three times, and so on.

Walk 5, 10, 15, ... and add how many factors of 5 each contributes. Correct and overflow-free, but linear in n — fine at the n <= 10^4 constraint, wasteful next to the closed-form sum below.

Count all multiples at once instead of visiting each: every term counts the multiples of that power of 5 up to n, so a multiple of 25 is counted twice, of 125 three times, and so on.

Walk 5, 10, 15, ... and add how many factors of 5 each contributes. Correct and overflow-free, but linear in n — fine at the n <= 10^4 constraint, wasteful next to the closed-form sum below.

Count all multiples at once instead of visiting each: every term counts the multiples of that power of 5 up to n, so a multiple of 25 is counted twice, of 125 three times, and so on.

def trailingZeroes_factorial(self, n: int) -> int:
    factorial = math.factorial(n)
    zeroes = 0
    while factorial % 10 == 0:
        factorial //= 10
        zeroes += 1
    return zeroes
def trailingZeroes_count_multiples(self, n: int) -> int:
    zeroes = 0
    for multiple in range(5, n + 1, 5):
        while multiple % 5 == 0:
            multiple //= 5
            zeroes += 1
    return zeroes
def trailingZeroes(self, n: int) -> int:
    zeroes = 0
    while n:
        n //= 5
        zeroes += n
    return zeroes
int trailingZeroesCountMultiples(int n) {
    int zeroes = 0;
    for (int multiple = 5; multiple <= n; multiple += 5) {
        for (int m = multiple; m % 5 == 0; m /= 5) {
            ++zeroes;
        }
    }
    return zeroes;
}
int trailingZeroes(int n) {
    int zeroes = 0;
    while (n > 0) {
        n /= 5;
        zeroes += n;
    }
    return zeroes;
}
pub fn trailing_zeroes_count_multiples(n: i32) -> i32 {
    let mut zeroes = 0;
    let mut multiple = 5;
    while multiple <= n {
        let mut m = multiple;
        while m % 5 == 0 {
            m /= 5;
            zeroes += 1;
        }
        multiple += 5;
    }
    zeroes
}
pub fn trailing_zeroes(n: i32) -> i32 {
    let mut n = n;
    let mut zeroes = 0;
    while n > 0 {
        n /= 5;
        zeroes += n;
    }
    zeroes
}
function trailingZeroesCountMultiples(n: number): number {
    let zeroes = 0;
    for (let multiple = 5; multiple <= n; multiple += 5) {
        for (let m = multiple; m % 5 === 0; m = Math.floor(m / 5)) {
            zeroes++;
        }
    }
    return zeroes;
}
function trailingZeroes(n: number): number {
    let zeroes = 0;
    while (n > 0) {
        n = Math.floor(n / 5);
        zeroes += n;
    }
    return zeroes;
}
func trailingZeroesCountMultiples(n int) int {
	zeroes := 0
	for multiple := 5; multiple <= n; multiple += 5 {
		for m := multiple; m%5 == 0; m /= 5 {
			zeroes++
		}
	}
	return zeroes
}
func trailingZeroes(n int) int {
	zeroes := 0
	for n > 0 {
		n /= 5
		zeroes += n
	}
	return zeroes
}
func trailingZeroesCountMultiples(_ n: Int) -> Int {
    var zeroes = 0
    var multiple = 5
    while multiple <= n {
        var m = multiple
        while m % 5 == 0 {
            m /= 5
            zeroes += 1
        }
        multiple += 5
    }
    return zeroes
}
func trailingZeroes(_ n: Int) -> Int {
    var n = n
    var zeroes = 0
    while n > 0 {
        n /= 5
        zeroes += n
    }
    return zeroes
}
Recommended Approach 1 of 3 · Compute n! and count its zeroes (brute force)O(n^2 log n)-ish big-int work time · O(n log n) digits space

44. Max Points on a Line

Hard · LC 149

Given a set of distinct points in the plane, find the greatest number that lie on a single straight line. Anchor each point in turn and hash every later point by its direction from the anchor, the coordinate differences reduced by their gcd and forced to a canonical sign; the fullest bucket plus the anchor itself is the best line through that anchor. The pitfall is representing slope as a floating-point ratio, which breaks on precision and vertical lines, whereas the reduced integer pair is exact and costs only linear space per anchor.

For each pair, count the points whose cross product with it is zero. No hashing or normalization to get wrong, so it anchors the tests — cubic is fine at n <= 300, but it is the slowest rung by far.

Every pair defines a line; reduce (a, b, c) by their gcd with a canonical sign and collect the distinct points on each line. Drops a factor of n off the brute force, but materializes every line.

For each anchor point, bucket every later point by the direction (dy, dx) reduced by gcd and sign-canonicalized (dx > 0, or vertical stored as (1, 0)). The best line through the anchor is its fullest bucket plus the anchor itself — same quadratic time as Approach 2 with only O(n) live state.

For each pair, count the points whose cross product with it is zero. No hashing or normalization to get wrong, so it anchors the tests — cubic is fine at n <= 300, but the slowest rung by far.

For each anchor point, bucket every later point by its direction (dy, dx) reduced by gcd and sign-canonicalized (dx > 0, vertical stored as (1, 0)). The best line through the anchor is its fullest bucket plus the anchor itself — drops a factor of n off the brute force.

For each pair, count the points whose cross product with it is zero. No hashing or normalization to get wrong, so it anchors the tests — cubic is fine at n <= 300, but the slowest rung by far.

For each anchor point, bucket every later point by its direction (dy, dx) reduced by gcd and sign-canonicalized (dx > 0, vertical stored as (1, 0)). The best line through the anchor is its fullest bucket plus the anchor itself — drops a factor of n off the brute force.

For each pair, count the points whose cross product with it is zero. No hashing or normalization to get wrong, so it anchors the tests — cubic is fine at n <= 300, but the slowest rung by far.

For each anchor point, bucket every later point by its direction (dy, dx) reduced by gcd and sign-canonicalized (dx > 0, vertical stored as (1, 0)), keyed as the exact string "dy,dx". The best line through the anchor is its fullest bucket plus the anchor itself — drops a factor of n off the brute force.

For each pair, count the points whose cross product with it is zero. No hashing or normalization to get wrong, so it anchors the tests — cubic is fine at n <= 300, but the slowest rung by far.

For each anchor point, bucket every later point by its direction (dy, dx) reduced by gcd and sign-canonicalized (dx > 0, vertical stored as (1, 0)). The best line through the anchor is its fullest bucket plus the anchor itself — drops a factor of n off the brute force.

For each pair, count the points whose cross product with it is zero. No hashing or normalization to get wrong, so it anchors the tests — cubic is fine at n <= 300, but the slowest rung by far.

For each anchor point, bucket every later point by its direction (dy, dx) reduced by gcd and sign-canonicalized (dx > 0, vertical stored as (1, 0)); reduced components stay within +/-2e4, so dy * 100003 + dx is a collision-free key. The best line through the anchor is its fullest bucket plus the anchor itself — drops a factor of n off the brute force.

def maxPoints_bruteforce(self, points: list[list[int]]) -> int:
    n = len(points)
    if n <= 2:
        return n

    best = 2
    for i, (x1, y1) in enumerate(points):
        for j in range(i + 1, n):
            x2, y2 = points[j]
            count = 2
            for x3, y3 in points[j + 1:]:
                if (x2 - x1) * (y3 - y1) == (y2 - y1) * (x3 - x1):
                    count += 1
            best = max(best, count)
    return best
def maxPoints_line_hash(self, points: list[list[int]]) -> int:
    n = len(points)
    if n <= 2:
        return n

    lines: dict[tuple[int, int, int], set[int]] = {}
    for i, (x1, y1) in enumerate(points):
        for j in range(i + 1, n):
            x2, y2 = points[j]
            a, b = y2 - y1, x1 - x2
            c = -(a * x1 + b * y1)
            g = gcd(gcd(a, b), c)  # positive: (a, b) != (0, 0)
            a, b, c = a // g, b // g, c // g
            if a < 0 or (a == 0 and b < 0):
                a, b, c = -a, -b, -c
            lines.setdefault((a, b, c), set()).update((i, j))
    return max(len(members) for members in lines.values())
def maxPoints(self, points: list[list[int]]) -> int:
    n = len(points)
    if n <= 2:
        return n

    best = 2
    for i in range(n - 1):     # last point alone anchors nothing new
        x1, y1 = points[i]
        slopes: dict[tuple[int, int], int] = {}
        for x2, y2 in points[i + 1:]:
            dy, dx = y2 - y1, x2 - x1
            g = gcd(dy, dx)        # positive: distinct points
            dy, dx = dy // g, dx // g
            if dx < 0 or (dx == 0 and dy < 0):
                dy, dx = -dy, -dx
            slopes[dy, dx] = slopes.get((dy, dx), 0) + 1
        best = max(best, max(slopes.values()) + 1)
    return best
int maxPointsBruteForce(vector<vector<int>>& points) {
    int n = (int)points.size();
    if (n <= 2)
        return n;

    int best = 2;
    for (int i = 0; i < n; ++i) {
        for (int j = i + 1; j < n; ++j) {
            long long x1 = points[i][0], y1 = points[i][1];
            long long x2 = points[j][0], y2 = points[j][1];
            int count = 2;
            for (int l = j + 1; l < n; ++l) {
                long long x3 = points[l][0], y3 = points[l][1];
                if ((x2 - x1) * (y3 - y1) == (y2 - y1) * (x3 - x1))
                    ++count;
            }
            best = max(best, count);
        }
    }
    return best;
}
int maxPoints(vector<vector<int>>& points) {
    int n = (int)points.size();
    if (n <= 2)
        return n;

    int best = 2;
    for (int i = 0; i < n - 1; ++i) {
        unordered_map<long long, int> slopes;
        for (int j = i + 1; j < n; ++j) {
            int dy = points[j][1] - points[i][1];
            int dx = points[j][0] - points[i][0];
            int g = gcd(dy, dx);  // positive: points are distinct
            dy /= g;
            dx /= g;
            if (dx < 0 || (dx == 0 && dy < 0)) {
                dy = -dy;
                dx = -dx;
            }
            // Reduced components stay within +/-2e4, so this key is
            // collision-free.
            long long key = (long long)dy * 100003 + dx;
            best = max(best, ++slopes[key] + 1);
        }
    }
    return best;
}
pub fn max_points_brute_force(points: Vec<Vec<i32>>) -> i32 {
    let n = points.len();
    if n <= 2 {
        return n as i32;
    }

    let mut best = 2;
    for i in 0..n {
        for j in i + 1..n {
            let (x1, y1) = (points[i][0] as i64, points[i][1] as i64);
            let (x2, y2) = (points[j][0] as i64, points[j][1] as i64);
            let mut count = 2;
            for p in points.iter().skip(j + 1) {
                let (x3, y3) = (p[0] as i64, p[1] as i64);
                if (x2 - x1) * (y3 - y1) == (y2 - y1) * (x3 - x1) {
                    count += 1;
                }
            }
            best = best.max(count);
        }
    }
    best
}
pub fn max_points(points: Vec<Vec<i32>>) -> i32 {
    let n = points.len();
    if n <= 2 {
        return n as i32;
    }

    let mut best = 2;
    for i in 0..n - 1 {
        let mut slopes: HashMap<(i32, i32), i32> = HashMap::new();
        for j in i + 1..n {
            let mut dy = points[j][1] - points[i][1];
            let mut dx = points[j][0] - points[i][0];
            let g = Self::gcd(dy.abs(), dx.abs()); // positive: distinct points
            dy /= g;
            dx /= g;
            if dx < 0 || (dx == 0 && dy < 0) {
                dy = -dy;
                dx = -dx;
            }
            let count = slopes.entry((dy, dx)).or_insert(0);
            *count += 1;
            best = best.max(*count + 1);
        }
    }
    best
}

fn gcd(a: i32, b: i32) -> i32 {
    if b == 0 {
        a
    } else {
        Self::gcd(b, a % b)
    }
}
function maxPointsBruteForce(points: number[][]): number {
    const n = points.length;
    if (n <= 2) {
        return n;
    }

    let best = 2;
    for (let i = 0; i < n; i++) {
        for (let j = i + 1; j < n; j++) {
            const [x1, y1] = points[i];
            const [x2, y2] = points[j];
            let count = 2;
            for (let l = j + 1; l < n; l++) {
                const [x3, y3] = points[l];
                if ((x2 - x1) * (y3 - y1) === (y2 - y1) * (x3 - x1)) {
                    count++;
                }
            }
            best = Math.max(best, count);
        }
    }
    return best;
}
function maxPoints(points: number[][]): number {
    const n = points.length;
    if (n <= 2) {
        return n;
    }

    const gcd = (a: number, b: number): number => (b === 0 ? a : gcd(b, a % b));

    let best = 2;
    for (let i = 0; i < n - 1; i++) {
        const slopes: { [key: string]: number } = {};
        for (let j = i + 1; j < n; j++) {
            let dy = points[j][1] - points[i][1];
            let dx = points[j][0] - points[i][0];
            const g = gcd(Math.abs(dy), Math.abs(dx)); // positive: distinct points
            dy /= g;
            dx /= g;
            if (dx < 0 || (dx === 0 && dy < 0)) {
                dy = -dy;
                dx = -dx;
            }
            const key = `${dy},${dx}`;
            const count = (slopes[key] || 0) + 1;
            slopes[key] = count;
            if (count + 1 > best) {
                best = count + 1;
            }
        }
    }
    return best;
}
func maxPointsBruteForce(points [][]int) int {
	n := len(points)
	if n <= 2 {
		return n
	}

	best := 2
	for i := 0; i < n; i++ {
		for j := i + 1; j < n; j++ {
			x1, y1 := points[i][0], points[i][1]
			x2, y2 := points[j][0], points[j][1]
			count := 2
			for l := j + 1; l < n; l++ {
				x3, y3 := points[l][0], points[l][1]
				if (x2-x1)*(y3-y1) == (y2-y1)*(x3-x1) {
					count++
				}
			}
			if count > best {
				best = count
			}
		}
	}
	return best
}
func maxPoints(points [][]int) int {
	n := len(points)
	if n <= 2 {
		return n
	}

	best := 2
	for i := 0; i < n-1; i++ {
		slopes := map[[2]int]int{}
		for j := i + 1; j < n; j++ {
			dy := points[j][1] - points[i][1]
			dx := points[j][0] - points[i][0]
			g := gcd(dy, dx) // positive: points are distinct
			dy, dx = dy/g, dx/g
			if dx < 0 || (dx == 0 && dy < 0) {
				dy, dx = -dy, -dx
			}
			key := [2]int{dy, dx}
			slopes[key]++
			if slopes[key]+1 > best {
				best = slopes[key] + 1
			}
		}
	}
	return best
}

// gcd returns the positive greatest common divisor (gcd(x, 0) = |x|).
func gcd(a, b int) int {
	if a < 0 {
		a = -a
	}
	if b < 0 {
		b = -b
	}
	for b != 0 {
		a, b = b, a%b
	}
	return a
}
func maxPointsBruteForce(_ points: [[Int]]) -> Int {
    let n = points.count
    if n <= 2 {
        return n
    }

    var best = 2
    for i in 0..<n {
        for j in (i + 1)..<n {
            let (x1, y1) = (points[i][0], points[i][1])
            let (x2, y2) = (points[j][0], points[j][1])
            var count = 2
            for l in (j + 1)..<n {
                let (x3, y3) = (points[l][0], points[l][1])
                if (x2 - x1) * (y3 - y1) == (y2 - y1) * (x3 - x1) {
                    count += 1
                }
            }
            best = max(best, count)
        }
    }
    return best
}
func maxPoints(_ points: [[Int]]) -> Int {
    let n = points.count
    if n <= 2 {
        return n
    }

    var best = 2
    for i in 0..<(n - 1) {
        var slopes: [Int: Int] = [:]
        for j in (i + 1)..<n {
            var dy = points[j][1] - points[i][1]
            var dx = points[j][0] - points[i][0]
            let g = gcd(abs(dy), abs(dx))  // positive: points are distinct
            dy /= g
            dx /= g
            if dx < 0 || (dx == 0 && dy < 0) {
                dy = -dy
                dx = -dx
            }
            let key = dy * 100003 + dx
            let count = (slopes[key] ?? 0) + 1
            slopes[key] = count
            best = max(best, count + 1)
        }
    }
    return best
}

private func gcd(_ a: Int, _ b: Int) -> Int {
    b == 0 ? a : gcd(b, a % b)
}
Recommended Approach 1 of 3 · Brute force over all pairs (baseline)O(n^3) time · O(1) space

Multidimensional DP

45. Triangle

Medium · LC 120

Given a triangle of numbers, find the minimum path sum from the apex to the base, where each step moves to one of the two adjacent entries in the row below. Work bottom-up: copy the last row into a one-dimensional array, then for each higher row overwrite each slot with its own value plus the smaller of the two entries beneath it, until the single surviving slot holds the answer. Going upward is the trick, because it erases the edge special cases and the final minimum scan that a top-down sweep needs, and since values can be negative no greedy shortcut is safe anyway.

The naive recursion is O(2^n); caching each (row, i) collapses the exponential tree into one visit per cell. Memory stays quadratic.

dp[i] = cheapest way to REACH (row, i) from the top — iteration drops the quadratic memo to one row. Edges of each row have one parent; interior cells have two; the answer is min over the last row.

Working upward, dp[i] = best path from (row, i) to the base; each cell only needs the two neighbors below it. Same costs as Approach 2, but the edge special-cases and the final min scan disappear: dp[0] is it.

The naive recursion is O(2^n); caching each (row, i) collapses the exponential tree into one visit per cell. Memory stays quadratic.

Working upward, dp[i] = best path from (row, i) to the base; each cell needs only the two neighbors below it, so one row of state replaces the quadratic memo and the recursion stack. dp[0] is the answer — no final scan, no per-row edge cases.

The naive recursion is O(2^n); caching each (row, i) collapses the exponential tree into one visit per cell. Memory stays quadratic.

Working upward, dp[i] = best path from (row, i) to the base; each cell needs only the two neighbors below it, so one row of state replaces the quadratic memo and the recursion stack. dp[0] is the answer — no final scan, no per-row edge cases.

The naive recursion is O(2^n); caching each (row, i) collapses the exponential tree into one visit per cell. Memory stays quadratic.

Working upward, dp[i] = best path from (row, i) to the base; each cell needs only the two neighbors below it, so one row of state replaces the quadratic memo and the recursion stack. dp[0] is the answer — no final scan, no per-row edge cases.

The naive recursion is O(2^n); caching each (row, i) collapses the exponential tree into one visit per cell. Memory stays quadratic.

Working upward, dp[i] = best path from (row, i) to the base; each cell needs only the two neighbors below it, so one row of state replaces the quadratic memo and the recursion stack. dp[0] is the answer — no final scan, no per-row edge cases.

The naive recursion is O(2^n); caching each (row, i) collapses the exponential tree into one visit per cell. Memory stays quadratic.

Working upward, dp[i] = best path from (row, i) to the base; each cell needs only the two neighbors below it, so one row of state replaces the quadratic memo and the recursion stack. dp[0] is the answer — no final scan, no per-row edge cases.

def minimumTotal_memo(self, triangle: list[list[int]]) -> int:
    memo: dict[tuple[int, int], int] = {}

    def best(row: int, i: int) -> int:
        if row == len(triangle) - 1:
            return triangle[row][i]
        if (row, i) not in memo:
            memo[row, i] = triangle[row][i] + min(best(row + 1, i),
                                                  best(row + 1, i + 1))
        return memo[row, i]

    return best(0, 0)
def minimumTotal_forward(self, triangle: list[list[int]]) -> int:
    dp = [triangle[0][0]]
    for row in range(1, len(triangle)):
        nxt = [0] * (row + 1)
        nxt[0] = dp[0] + triangle[row][0]
        nxt[row] = dp[row - 1] + triangle[row][row]
        for i in range(1, row):
            nxt[i] = min(dp[i - 1], dp[i]) + triangle[row][i]
        dp = nxt
    return min(dp)
def minimumTotal(self, triangle: list[list[int]]) -> int:
    dp = triangle[-1][:]  # copy so we never mutate the input
    for row in range(len(triangle) - 2, -1, -1):
        for i in range(row + 1):
            dp[i] = triangle[row][i] + min(dp[i], dp[i + 1])
    return dp[0]
int minimumTotalMemo(std::vector<std::vector<int>>& triangle) {
    int n = static_cast<int>(triangle.size());
    // Path sums fit comfortably in int (|value| <= 10^4, n <= 200), so
    // INT_MIN is a safe "unset" sentinel.
    std::vector<std::vector<int>> memo(n, std::vector<int>(n, INT_MIN));
    auto best = [&](auto&& self, int row, int i) -> int {
        if (row == n - 1) return triangle[row][i];
        if (memo[row][i] == INT_MIN) {
            memo[row][i] = triangle[row][i] +
                           std::min(self(self, row + 1, i),
                                    self(self, row + 1, i + 1));
        }
        return memo[row][i];
    };
    return best(best, 0, 0);
}
int minimumTotal(std::vector<std::vector<int>>& triangle) {
    std::vector<int> dp = triangle.back();
    for (int row = static_cast<int>(triangle.size()) - 2; row >= 0; --row) {
        for (int i = 0; i <= row; ++i) {
            dp[i] = triangle[row][i] + std::min(dp[i], dp[i + 1]);
        }
    }
    return dp[0];
}
pub fn minimum_total_memo(triangle: Vec<Vec<i32>>) -> i32 {
    fn best(
        triangle: &[Vec<i32>],
        memo: &mut [Vec<Option<i32>>],
        row: usize,
        i: usize,
    ) -> i32 {
        if row == triangle.len() - 1 {
            return triangle[row][i];
        }
        if memo[row][i].is_none() {
            let down = best(triangle, memo, row + 1, i);
            let diag = best(triangle, memo, row + 1, i + 1);
            memo[row][i] = Some(triangle[row][i] + down.min(diag));
        }
        memo[row][i].unwrap()
    }

    let mut memo: Vec<Vec<Option<i32>>> =
        (0..triangle.len()).map(|row| vec![None; row + 1]).collect();
    best(&triangle, &mut memo, 0, 0)
}
pub fn minimum_total(triangle: Vec<Vec<i32>>) -> i32 {
    let mut dp = triangle.last().unwrap().clone();
    for row in (0..triangle.len() - 1).rev() {
        for i in 0..=row {
            dp[i] = triangle[row][i] + dp[i].min(dp[i + 1]);
        }
    }
    dp[0]
}
function minimumTotalMemo(triangle: number[][]): number {
    const n = triangle.length;
    const memo = new Map<number, number>(); // key: row * n + i
    const best = (row: number, i: number): number => {
        if (row === n - 1) return triangle[row][i];
        const key = row * n + i;
        const cached = memo.get(key);
        if (cached !== undefined) return cached;
        const val = triangle[row][i] + Math.min(best(row + 1, i), best(row + 1, i + 1));
        memo.set(key, val);
        return val;
    };
    return best(0, 0);
}
function minimumTotal(triangle: number[][]): number {
    const dp = [...triangle[triangle.length - 1]];
    for (let row = triangle.length - 2; row >= 0; row--) {
        for (let i = 0; i <= row; i++) {
            dp[i] = triangle[row][i] + Math.min(dp[i], dp[i + 1]);
        }
    }
    return dp[0];
}
func minimumTotalMemo(triangle [][]int) int {
	n := len(triangle)
	const unset = math.MinInt // path sums are tiny; MinInt is a safe sentinel
	memo := make([][]int, n)
	for row := range memo {
		memo[row] = make([]int, row+1)
		for i := range memo[row] {
			memo[row][i] = unset
		}
	}
	var best func(row, i int) int
	best = func(row, i int) int {
		if row == n-1 {
			return triangle[row][i]
		}
		if memo[row][i] == unset {
			memo[row][i] = triangle[row][i] + min(best(row+1, i), best(row+1, i+1))
		}
		return memo[row][i]
	}
	return best(0, 0)
}
func minimumTotal(triangle [][]int) int {
	n := len(triangle)
	dp := make([]int, n)
	copy(dp, triangle[n-1])
	for row := n - 2; row >= 0; row-- {
		for i := 0; i <= row; i++ {
			dp[i] = triangle[row][i] + min(dp[i], dp[i+1])
		}
	}
	return dp[0]
}
func minimumTotalMemo(_ triangle: [[Int]]) -> Int {
    let n = triangle.count
    var memo: [[Int?]] = (0..<n).map { [Int?](repeating: nil, count: $0 + 1) }
    func best(_ row: Int, _ i: Int) -> Int {
        if row == n - 1 { return triangle[row][i] }
        if memo[row][i] == nil {
            memo[row][i] = triangle[row][i] + min(best(row + 1, i), best(row + 1, i + 1))
        }
        return memo[row][i]!
    }
    return best(0, 0)
}
func minimumTotal(_ triangle: [[Int]]) -> Int {
    var dp = triangle[triangle.count - 1]
    for row in stride(from: triangle.count - 2, through: 0, by: -1) {
        for i in 0...row {
            dp[i] = triangle[row][i] + min(dp[i], dp[i + 1])
        }
    }
    return dp[0]
}
Recommended Approach 1 of 3 · Top-down memoized recursionO(n^2) time · O(n^2) for the memo (+ O(n) recursion depth) space

46. Best Time to Buy and Sell Stock III

Hard · LC 123

Given daily stock prices, maximize profit using at most two buy-sell transactions, selling each share before buying the next. One pass tracks four running states, the best cash while holding the first share, after selling it, while holding the second, and after selling that, each relaxed in turn against the current price. The subtle point is updating each buy before its matching sell, which allows a trade to open and close on the same day, a harmless no-op that keeps the recurrence simple and the space constant.

dp[t][i] = best profit using <= t trades through day i. The max-diff trick folds the inner "best buy day" scan into the same pass — this is exactly the LC 188 solution with k pinned to 2. The generic tool works, but it drags k rows of machinery through a two-trade problem.

left[i] = best one-transaction profit within prices[:i+1]; right[i] = best within prices[i:]. Split the timeline at every day and take the best sum (sharing day i is safe — sell+rebuy is a no-op). Two elementary LC 121 sweeps instead of layered DP: roughly half the work of Approach 1 and far easier to reason about, still O(n) memory.

Track the best cash after each stage: buy1 (holding 1st share), sell1 (closed 1st trade), buy2, sell2. Each price relaxes all four; updating buys before sells lets both trades touch the same day, which never hurts (a same-day buy+sell nets zero). Drops the O(n) arrays.

left[i] = best one-transaction profit within prices[0..i]; right[i] = best within prices[i..]. Split the timeline at every day and take the best sum (sharing day i is safe — sell+rebuy is a no-op). Two elementary LC 121 sweeps, but two extra arrays.

Track the best cash after each stage: buy1 (holding 1st share), sell1 (closed 1st trade), buy2, sell2. Each price relaxes all four; updating buys before sells lets both trades share a day, which never hurts (a same-day buy+sell nets zero). Drops the O(n) arrays.

left[i] = best one-transaction profit within prices[0..=i]; right[i] = best within prices[i..]. Split the timeline at every day and take the best sum (sharing day i is safe — sell+rebuy is a no-op). Two elementary LC 121 sweeps, but two extra arrays.

Track the best cash after each stage: buy1 (holding 1st share), sell1 (closed 1st trade), buy2, sell2. Each price relaxes all four; updating buys before sells lets both trades share a day, which never hurts (a same-day buy+sell nets zero). Drops the O(n) arrays.

left[i] = best one-transaction profit within prices[0..i]; right[i] = best within prices[i..]. Split the timeline at every day and take the best sum (sharing day i is safe — sell+rebuy is a no-op). Two elementary LC 121 sweeps, but two extra arrays.

Track the best cash after each stage: buy1 (holding 1st share), sell1 (closed 1st trade), buy2, sell2. Each price relaxes all four; updating buys before sells lets both trades share a day, which never hurts (a same-day buy+sell nets zero). Drops the O(n) arrays.

left[i] = best one-transaction profit within prices[0..i]; right[i] = best within prices[i..]. Split the timeline at every day and take the best sum (sharing day i is safe — sell+rebuy is a no-op). Two elementary LC 121 sweeps, but two extra arrays.

Track the best cash after each stage: buy1 (holding 1st share), sell1 (closed 1st trade), buy2, sell2. Each price relaxes all four; updating buys before sells lets both trades share a day, which never hurts (a same-day buy+sell nets zero). Drops the O(n) arrays.

left[i] = best one-transaction profit within prices[0...i]; right[i] = best within prices[i...]. Split the timeline at every day and take the best sum (sharing day i is safe — sell+rebuy is a no-op). Two elementary LC 121 sweeps, but two extra arrays.

Track the best cash after each stage: buy1 (holding 1st share), sell1 (closed 1st trade), buy2, sell2. Each price relaxes all four; updating buys before sells lets both trades share a day, which never hurts (a same-day buy+sell nets zero). Drops the O(n) arrays.

def maxProfit_general_k(self, prices: list[int]) -> int:
    n = len(prices)
    if n < 2:
        return 0

    prev = [0] * n  # dp for t-1 transactions
    for _ in range(2):
        curr = [0] * n
        best_buy = -prices[0]  # max(prev[j] - prices[j]) so far
        for i in range(1, n):
            curr[i] = max(curr[i - 1], prices[i] + best_buy)
            best_buy = max(best_buy, prev[i] - prices[i])
        prev = curr
    return prev[-1]
def maxProfit_two_pass(self, prices: list[int]) -> int:
    n = len(prices)
    if n < 2:
        return 0

    left = [0] * n
    lo = prices[0]
    for i in range(1, n):
        left[i] = max(left[i - 1], prices[i] - lo)
        lo = min(lo, prices[i])

    right = [0] * n
    hi = prices[-1]
    for i in range(n - 2, -1, -1):
        right[i] = max(right[i + 1], hi - prices[i])
        hi = max(hi, prices[i])

    return max(left[i] + right[i] for i in range(n))
def maxProfit(self, prices: list[int]) -> int:
    buy1 = buy2 = float("-inf")
    sell1 = sell2 = 0
    for p in prices:
        buy1 = max(buy1, -p)
        sell1 = max(sell1, buy1 + p)
        buy2 = max(buy2, sell1 - p)
        sell2 = max(sell2, buy2 + p)
    return int(sell2)
int maxProfitTwoPass(std::vector<int>& prices) {
    int n = static_cast<int>(prices.size());
    if (n < 2) return 0;

    std::vector<int> left(n, 0), right(n, 0);
    int lo = prices[0];
    for (int i = 1; i < n; ++i) {
        left[i] = std::max(left[i - 1], prices[i] - lo);
        lo = std::min(lo, prices[i]);
    }
    int hi = prices[n - 1];
    for (int i = n - 2; i >= 0; --i) {
        right[i] = std::max(right[i + 1], hi - prices[i]);
        hi = std::max(hi, prices[i]);
    }
    int best = 0;
    for (int i = 0; i < n; ++i) best = std::max(best, left[i] + right[i]);
    return best;
}
int maxProfit(std::vector<int>& prices) {
    int buy1 = INT_MIN, sell1 = 0, buy2 = INT_MIN, sell2 = 0;
    for (int p : prices) {
        buy1 = std::max(buy1, -p);        // updated before use: no overflow
        sell1 = std::max(sell1, buy1 + p);
        buy2 = std::max(buy2, sell1 - p);
        sell2 = std::max(sell2, buy2 + p);
    }
    return sell2;
}
pub fn max_profit_two_pass(prices: Vec<i32>) -> i32 {
    let n = prices.len();
    if n < 2 {
        return 0;
    }

    let mut left = vec![0; n];
    let mut lo = prices[0];
    for i in 1..n {
        left[i] = left[i - 1].max(prices[i] - lo);
        lo = lo.min(prices[i]);
    }

    let mut right = vec![0; n];
    let mut hi = prices[n - 1];
    for i in (0..n - 1).rev() {
        right[i] = right[i + 1].max(hi - prices[i]);
        hi = hi.max(prices[i]);
    }

    (0..n).map(|i| left[i] + right[i]).max().unwrap()
}
pub fn max_profit(prices: Vec<i32>) -> i32 {
    let (mut buy1, mut sell1, mut buy2, mut sell2) = (i32::MIN, 0, i32::MIN, 0);
    for p in prices {
        buy1 = buy1.max(-p); // updated before use: no overflow
        sell1 = sell1.max(buy1 + p);
        buy2 = buy2.max(sell1 - p);
        sell2 = sell2.max(buy2 + p);
    }
    sell2
}
function maxProfitTwoPass(prices: number[]): number {
    const n = prices.length;
    if (n < 2) return 0;

    const left = new Array<number>(n).fill(0);
    let lo = prices[0];
    for (let i = 1; i < n; i++) {
        left[i] = Math.max(left[i - 1], prices[i] - lo);
        lo = Math.min(lo, prices[i]);
    }

    const right = new Array<number>(n).fill(0);
    let hi = prices[n - 1];
    for (let i = n - 2; i >= 0; i--) {
        right[i] = Math.max(right[i + 1], hi - prices[i]);
        hi = Math.max(hi, prices[i]);
    }

    let best = 0;
    for (let i = 0; i < n; i++) {
        best = Math.max(best, left[i] + right[i]);
    }
    return best;
}
function maxProfit(prices: number[]): number {
    let buy1 = -Infinity;
    let sell1 = 0;
    let buy2 = -Infinity;
    let sell2 = 0;
    for (const p of prices) {
        buy1 = Math.max(buy1, -p);
        sell1 = Math.max(sell1, buy1 + p);
        buy2 = Math.max(buy2, sell1 - p);
        sell2 = Math.max(sell2, buy2 + p);
    }
    return sell2;
}
func maxProfitTwoPass(prices []int) int {
	n := len(prices)
	if n < 2 {
		return 0
	}

	left := make([]int, n)
	lo := prices[0]
	for i := 1; i < n; i++ {
		left[i] = max(left[i-1], prices[i]-lo)
		lo = min(lo, prices[i])
	}

	right := make([]int, n)
	hi := prices[n-1]
	for i := n - 2; i >= 0; i-- {
		right[i] = max(right[i+1], hi-prices[i])
		hi = max(hi, prices[i])
	}

	best := 0
	for i := 0; i < n; i++ {
		best = max(best, left[i]+right[i])
	}
	return best
}
func maxProfit(prices []int) int {
	buy1, sell1 := math.MinInt, 0
	buy2, sell2 := math.MinInt, 0
	for _, p := range prices {
		buy1 = max(buy1, -p) // updated before use: no overflow
		sell1 = max(sell1, buy1+p)
		buy2 = max(buy2, sell1-p)
		sell2 = max(sell2, buy2+p)
	}
	return sell2
}
func maxProfitTwoPass(_ prices: [Int]) -> Int {
    let n = prices.count
    if n < 2 { return 0 }

    var left = [Int](repeating: 0, count: n)
    var lo = prices[0]
    for i in 1..<n {
        left[i] = max(left[i - 1], prices[i] - lo)
        lo = min(lo, prices[i])
    }

    var right = [Int](repeating: 0, count: n)
    var hi = prices[n - 1]
    for i in stride(from: n - 2, through: 0, by: -1) {
        right[i] = max(right[i + 1], hi - prices[i])
        hi = max(hi, prices[i])
    }

    var best = 0
    for i in 0..<n {
        best = max(best, left[i] + right[i])
    }
    return best
}
func maxProfit(_ prices: [Int]) -> Int {
    var buy1 = Int.min, sell1 = 0, buy2 = Int.min, sell2 = 0
    for p in prices {
        buy1 = max(buy1, -p) // updated before use: no overflow
        sell1 = max(sell1, buy1 + p)
        buy2 = max(buy2, sell1 - p)
        sell2 = max(sell2, buy2 + p)
    }
    return sell2
}
Recommended Approach 1 of 3 · General k-transaction DP specialized to k = 2O(k * n) = O(n) time · O(n) space

47. Best Time to Buy and Sell Stock IV

Hard · LC 188

Given daily stock prices and a cap k, maximize profit using at most k buy-sell transactions. When k is at least half the number of days the cap can never bind, so simply bank every day-over-day rise; otherwise widen the two-transaction state machine to k stages, keeping per-stage buy and sell values where each price relaxes the best cash while holding, or after closing, that stage's share. The key observation is that a transaction spans at least two days, which is what justifies the greedy shortcut and keeps the state at O(k) instead of a full day-by-transaction table.

The most literal encoding of the decision tree (skip / buy / sell); easiest to derive live, but the memo grows with k even when k is so large the cap can never bind.

dp[t][i] = best profit using <= t trades through day i. Folding "best prev[j] - prices[j] so far" into the sweep kills the inner loop that would otherwise make this O(k * n^2). Iteration replaces the recursion stack, capping k at n/2 shrinks the table to two O(n) rows.

A transaction needs >= 2 days, so k >= n/2 means the cap never binds: just bank every day-over-day rise. Otherwise run the LC 123 state machine widened to k stages: buy[j] / sell[j] = best cash while holding (resp. after closing) the j-th share. Buys update before sells so a trade may open and close on the same day (a harmless no-op). State shrinks from O(n) per row to O(k) total.

The most literal encoding of the decision tree (skip / buy / sell); easiest to derive live, but the memo grows with k even when k is so large the cap can never bind (fine for LC's k <= 100).

A transaction needs >= 2 days, so k >= n/2 means the cap never binds: just bank every day-over-day rise. Otherwise run the LC 123 state machine widened to k stages: buy[j] / sell[j] = best cash while holding (resp. after closing) the j-th share; buys update before sells so a trade may open and close on the same day (a harmless no-op). State shrinks from O(n * k) to O(k).

The most literal encoding of the decision tree (skip / buy / sell); easiest to derive live, but the memo grows with k even when k is so large the cap can never bind (fine for LC's k <= 100).

A transaction needs >= 2 days, so k >= n/2 means the cap never binds: just bank every day-over-day rise. Otherwise run the LC 123 state machine widened to k stages: buy[j] / sell[j] = best cash while holding (resp. after closing) the j-th share; buys update before sells so a trade may open and close on the same day (a harmless no-op). State shrinks from O(n * k) to O(k).

The most literal encoding of the decision tree (skip / buy / sell); easiest to derive live, but the memo grows with k even when k is so large the cap can never bind (fine for LC's k <= 100).

A transaction needs >= 2 days, so k >= n/2 means the cap never binds: just bank every day-over-day rise. Otherwise run the LC 123 state machine widened to k stages: buy[j] / sell[j] = best cash while holding (resp. after closing) the j-th share; buys update before sells so a trade may open and close on the same day (a harmless no-op). State shrinks from O(n * k) to O(k).

The most literal encoding of the decision tree (skip / buy / sell); easiest to derive live, but the memo grows with k even when k is so large the cap can never bind (fine for LC's k <= 100).

A transaction needs >= 2 days, so k >= n/2 means the cap never binds: just bank every day-over-day rise. Otherwise run the LC 123 state machine widened to k stages: buy[j] / sell[j] = best cash while holding (resp. after closing) the j-th share; buys update before sells so a trade may open and close on the same day (a harmless no-op). State shrinks from O(n * k) to O(k).

The most literal encoding of the decision tree (skip / buy / sell); easiest to derive live, but the memo grows with k even when k is so large the cap can never bind (fine for LC's k <= 100).

A transaction needs >= 2 days, so k >= n/2 means the cap never binds: just bank every day-over-day rise. Otherwise run the LC 123 state machine widened to k stages: buy[j] / sell[j] = best cash while holding (resp. after closing) the j-th share; buys update before sells so a trade may open and close on the same day (a harmless no-op). State shrinks from O(n * k) to O(k).

def maxProfit_memo(self, k: int, prices: list[int]) -> int:
    n = len(prices)
    if k == 0 or n < 2:
        return 0

    memo: dict[tuple[int, int, bool], int] = {}

    def best(i: int, left: int, holding: bool) -> int:
        if i == n or (left == 0 and not holding):
            return 0
        key = (i, left, holding)
        if key not in memo:
            res = best(i + 1, left, holding)  # do nothing today
            if holding:
                res = max(res, prices[i] + best(i + 1, left - 1, False))
            elif left > 0:
                res = max(res, -prices[i] + best(i + 1, left, True))
            memo[key] = res
        return memo[key]

    return best(0, k, False)
def maxProfit_dp2d(self, k: int, prices: list[int]) -> int:
    n = len(prices)
    if k == 0 or n < 2:
        return 0

    prev = [0] * n
    for _ in range(min(k, n // 2)):
        curr = [0] * n
        best_buy = -prices[0]  # max(prev[j] - prices[j]) so far
        for i in range(1, n):
            curr[i] = max(curr[i - 1], prices[i] + best_buy)
            best_buy = max(best_buy, prev[i] - prices[i])
        prev = curr
    return prev[-1]
def maxProfit(self, k: int, prices: list[int]) -> int:
    n = len(prices)
    if k == 0 or n < 2:
        return 0

    if k >= n // 2:  # unlimited transactions in disguise
        return sum(max(0, prices[i] - prices[i - 1]) for i in range(1, n))

    buy = [float("-inf")] * (k + 1)
    sell = [0] * (k + 1)
    for p in prices:
        for j in range(1, k + 1):
            buy[j] = max(buy[j], sell[j - 1] - p)
            sell[j] = max(sell[j], buy[j] + p)
    return int(sell[k])
int maxProfitMemo(int k, std::vector<int>& prices) {
    int n = static_cast<int>(prices.size());
    if (k == 0 || n < 2) return 0;

    // memo[(i * (k+1) + left) * 2 + holding]; profits are never
    // negative, so -1 is a safe "unset" sentinel.
    std::vector<int> memo(static_cast<size_t>(n) * (k + 1) * 2, -1);
    auto best = [&](auto&& self, int i, int left, int holding) -> int {
        if (i == n || (left == 0 && holding == 0)) return 0;
        int& slot = memo[(static_cast<size_t>(i) * (k + 1) + left) * 2 +
                         holding];
        if (slot < 0) {
            int res = self(self, i + 1, left, holding);  // do nothing today
            if (holding) {
                res = std::max(res, prices[i] + self(self, i + 1, left - 1, 0));
            } else if (left > 0) {
                res = std::max(res, -prices[i] + self(self, i + 1, left, 1));
            }
            slot = res;
        }
        return slot;
    };
    return best(best, 0, k, 0);
}
int maxProfit(int k, std::vector<int>& prices) {
    int n = static_cast<int>(prices.size());
    if (k == 0 || n < 2) return 0;

    if (k >= n / 2) {  // unlimited transactions in disguise
        int profit = 0;
        for (int i = 1; i < n; ++i) {
            profit += std::max(0, prices[i] - prices[i - 1]);
        }
        return profit;
    }

    std::vector<int> buy(k + 1, INT_MIN), sell(k + 1, 0);
    for (int p : prices) {
        for (int j = 1; j <= k; ++j) {
            buy[j] = std::max(buy[j], sell[j - 1] - p);  // before use below
            sell[j] = std::max(sell[j], buy[j] + p);
        }
    }
    return sell[k];
}
pub fn max_profit_memo(k: i32, prices: Vec<i32>) -> i32 {
    // memo[(i * (k+1) + left) * 2 + holding]; profits are never
    // negative, so -1 is a safe "unset" sentinel.
    fn best(prices: &[i32], memo: &mut [i32], k: usize, i: usize, left: usize, holding: usize) -> i32 {
        if i == prices.len() || (left == 0 && holding == 0) {
            return 0;
        }
        let idx = (i * (k + 1) + left) * 2 + holding;
        if memo[idx] < 0 {
            let mut res = best(prices, memo, k, i + 1, left, holding); // do nothing
            if holding == 1 {
                res = res.max(prices[i] + best(prices, memo, k, i + 1, left - 1, 0));
            } else if left > 0 {
                res = res.max(-prices[i] + best(prices, memo, k, i + 1, left, 1));
            }
            memo[idx] = res;
        }
        memo[idx]
    }

    let k = k as usize;
    let n = prices.len();
    if k == 0 || n < 2 {
        return 0;
    }
    let mut memo = vec![-1i32; n * (k + 1) * 2];
    best(&prices, &mut memo, k, 0, k, 0)
}
pub fn max_profit(k: i32, prices: Vec<i32>) -> i32 {
    let k = k as usize;
    let n = prices.len();
    if k == 0 || n < 2 {
        return 0;
    }

    if k >= n / 2 {
        // unlimited transactions in disguise
        return prices.windows(2).map(|w| (w[1] - w[0]).max(0)).sum();
    }

    let mut buy = vec![i32::MIN; k + 1];
    let mut sell = vec![0; k + 1];
    for p in prices {
        for j in 1..=k {
            buy[j] = buy[j].max(sell[j - 1] - p); // updated before use below
            sell[j] = sell[j].max(buy[j] + p);
        }
    }
    sell[k]
}
function maxProfitMemo(k: number, prices: number[]): number {
    const n = prices.length;
    if (k === 0 || n < 2) return 0;

    // memo[(i * (k+1) + left) * 2 + holding]; profits are never negative,
    // so -1 is a safe "unset" sentinel.
    const memo = new Array<number>(n * (k + 1) * 2).fill(-1);
    const best = (i: number, left: number, holding: number): number => {
        if (i === n || (left === 0 && holding === 0)) return 0;
        const idx = (i * (k + 1) + left) * 2 + holding;
        if (memo[idx] < 0) {
            let res = best(i + 1, left, holding); // do nothing today
            if (holding === 1) {
                res = Math.max(res, prices[i] + best(i + 1, left - 1, 0));
            } else if (left > 0) {
                res = Math.max(res, -prices[i] + best(i + 1, left, 1));
            }
            memo[idx] = res;
        }
        return memo[idx];
    };
    return best(0, k, 0);
}
function maxProfit(k: number, prices: number[]): number {
    const n = prices.length;
    if (k === 0 || n < 2) return 0;

    if (k >= Math.floor(n / 2)) {
        // unlimited transactions in disguise
        let profit = 0;
        for (let i = 1; i < n; i++) {
            profit += Math.max(0, prices[i] - prices[i - 1]);
        }
        return profit;
    }

    const buy = new Array<number>(k + 1).fill(-Infinity);
    const sell = new Array<number>(k + 1).fill(0);
    for (const p of prices) {
        for (let j = 1; j <= k; j++) {
            buy[j] = Math.max(buy[j], sell[j - 1] - p);
            sell[j] = Math.max(sell[j], buy[j] + p);
        }
    }
    return sell[k];
}
func maxProfitMemo(k int, prices []int) int {
	n := len(prices)
	if k == 0 || n < 2 {
		return 0
	}

	// memo[(i*(k+1)+left)*2+holding]; profits are never negative, so -1
	// is a safe "unset" sentinel.
	memo := make([]int, n*(k+1)*2)
	for i := range memo {
		memo[i] = -1
	}
	var best func(i, left, holding int) int
	best = func(i, left, holding int) int {
		if i == n || (left == 0 && holding == 0) {
			return 0
		}
		idx := (i*(k+1)+left)*2 + holding
		if memo[idx] < 0 {
			res := best(i+1, left, holding) // do nothing today
			if holding == 1 {
				res = max(res, prices[i]+best(i+1, left-1, 0))
			} else if left > 0 {
				res = max(res, -prices[i]+best(i+1, left, 1))
			}
			memo[idx] = res
		}
		return memo[idx]
	}
	return best(0, k, 0)
}
func maxProfit(k int, prices []int) int {
	n := len(prices)
	if k == 0 || n < 2 {
		return 0
	}

	if k >= n/2 { // unlimited transactions in disguise
		profit := 0
		for i := 1; i < n; i++ {
			profit += max(0, prices[i]-prices[i-1])
		}
		return profit
	}

	buy := make([]int, k+1)
	sell := make([]int, k+1)
	for j := range buy {
		buy[j] = math.MinInt
	}
	for _, p := range prices {
		for j := 1; j <= k; j++ {
			buy[j] = max(buy[j], sell[j-1]-p) // updated before use below
			sell[j] = max(sell[j], buy[j]+p)
		}
	}
	return sell[k]
}
func maxProfitMemo(_ k: Int, _ prices: [Int]) -> Int {
    let n = prices.count
    if k == 0 || n < 2 { return 0 }

    // memo[(i * (k+1) + left) * 2 + holding]; profits are never
    // negative, so -1 is a safe "unset" sentinel.
    var memo = [Int](repeating: -1, count: n * (k + 1) * 2)
    func best(_ i: Int, _ left: Int, _ holding: Int) -> Int {
        if i == n || (left == 0 && holding == 0) { return 0 }
        let idx = (i * (k + 1) + left) * 2 + holding
        if memo[idx] < 0 {
            var res = best(i + 1, left, holding) // do nothing today
            if holding == 1 {
                res = max(res, prices[i] + best(i + 1, left - 1, 0))
            } else if left > 0 {
                res = max(res, -prices[i] + best(i + 1, left, 1))
            }
            memo[idx] = res
        }
        return memo[idx]
    }
    return best(0, k, 0)
}
func maxProfit(_ k: Int, _ prices: [Int]) -> Int {
    let n = prices.count
    if k == 0 || n < 2 { return 0 }

    if k >= n / 2 { // unlimited transactions in disguise
        var profit = 0
        for i in 1..<n {
            profit += max(0, prices[i] - prices[i - 1])
        }
        return profit
    }

    var buy = [Int](repeating: Int.min, count: k + 1)
    var sell = [Int](repeating: 0, count: k + 1)
    for p in prices {
        for j in 1...k {
            buy[j] = max(buy[j], sell[j - 1] - p) // updated before use below
            sell[j] = max(sell[j], buy[j] + p)
        }
    }
    return sell[k]
}
Recommended Approach 1 of 3 · Top-down memoized recursion over (day, trades left, holding)O(n * k) time · O(n * k) memo + O(n) recursion depth space

48. Maximal Square

Medium · LC 221

Given a binary matrix of character zeros and ones, find the area of the largest square made entirely of ones. Dynamic programming records, for each cell, the side of the biggest square whose bottom-right corner sits there, which on a one-cell is one plus the minimum of the sides above, to the left, and diagonally up-left; a single rolling row replaces the full table since each cell looks only one row up. The pitfall in the rolling version is the diagonal neighbor, whose slot is overwritten as the sweep passes, so its old value must be stashed in a temporary before each update, and the final answer is the best side squared, not the side itself.

From each '1', extend the side while the new right column and bottom row are all '1'. Fine at LeetCode's 300x300 bound, but every cell re-scans work its neighbors already did.

side(i, j) = biggest square whose bottom-right corner is (i, j) = min(top, left, diagonal) + 1 on a '1' cell — the three neighbors cap how far the square can extend, so no re-scanning. Easy to draw on a whiteboard, but keeps the whole table around.

Same recurrence as Approach 2, but each cell only looks one row up: one row plus a saved diagonal replaces the full m x n table.

From each '1', extend the side while the new right column and bottom row are all '1'. Fine at LeetCode's 300x300 bound, but every cell re-scans work its neighbors already did.

side(i, j) = biggest square whose bottom-right corner is (i, j) = min(top, left, diagonal) + 1 on a '1' cell — the three neighbors cap how far the square can extend, so no re-scanning. Easy to draw on a whiteboard, but keeps the whole table around.

Same recurrence as Approach 2, but each cell only looks one row up: one row plus a saved diagonal replaces the full m x n table.

From each '1', extend the side while the new right column and bottom row are all '1'. Fine at LeetCode's 300x300 bound, but every cell re-scans work its neighbors already did.

side(i, j) = biggest square whose bottom-right corner is (i, j) = min(top, left, diagonal) + 1 on a '1' cell — the three neighbors cap how far the square can extend, so no re-scanning. Easy to draw on a whiteboard, but keeps the whole table around.

Same recurrence as Approach 2, but each cell only looks one row up: one row plus a saved diagonal replaces the full m x n table.

From each '1', extend the side while the new right column and bottom row are all '1'. Fine at LeetCode's 300x300 bound, but every cell re-scans work its neighbors already did.

side(i, j) = biggest square whose bottom-right corner is (i, j) = min(top, left, diagonal) + 1 on a '1' cell — the three neighbors cap how far the square can extend, so no re-scanning. Easy to draw on a whiteboard, but keeps the whole table around.

Same recurrence as Approach 2, but each cell only looks one row up: one row plus a saved diagonal replaces the full m x n table.

From each '1', extend the side while the new right column and bottom row are all '1'. Fine at LeetCode's 300x300 bound, but every cell re-scans work its neighbors already did.

side(i, j) = biggest square whose bottom-right corner is (i, j) = min(top, left, diagonal) + 1 on a '1' cell — the three neighbors cap how far the square can extend, so no re-scanning. Easy to draw on a whiteboard, but keeps the whole table around.

Same recurrence as Approach 2, but each cell only looks one row up: one row plus a saved diagonal replaces the full m x n table.

From each '1', extend the side while the new right column and bottom row are all '1'. Fine at LeetCode's 300x300 bound, but every cell re-scans work its neighbors already did.

side(i, j) = biggest square whose bottom-right corner is (i, j) = min(top, left, diagonal) + 1 on a '1' cell — the three neighbors cap how far the square can extend, so no re-scanning. Easy to draw on a whiteboard, but keeps the whole table around.

Same recurrence as Approach 2, but each cell only looks one row up: one row plus a saved diagonal replaces the full m x n table.

def maximalSquare_brute(self, matrix: list[list[str]]) -> int:
    if not matrix or not matrix[0]:
        return 0
    m, n = len(matrix), len(matrix[0])
    best = 0
    for i in range(m):
        for j in range(n):
            if matrix[i][j] != "1":
                continue
            side = 1
            while i + side < m and j + side < n:
                edge_ok = all(matrix[i + side][j + t] == "1"
                              for t in range(side + 1))
                edge_ok = edge_ok and all(matrix[i + t][j + side] == "1"
                                          for t in range(side))
                if not edge_ok:
                    break
                side += 1
            best = max(best, side)
    return best * best
def maximalSquare_dp2d(self, matrix: list[list[str]]) -> int:
    if not matrix or not matrix[0]:
        return 0
    m, n = len(matrix), len(matrix[0])
    side = [[0] * (n + 1) for _ in range(m + 1)]  # padded top/left border
    best = 0
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if matrix[i - 1][j - 1] == "1":
                side[i][j] = min(side[i - 1][j], side[i][j - 1],
                                 side[i - 1][j - 1]) + 1
                best = max(best, side[i][j])
    return best * best
def maximalSquare(self, matrix: list[list[str]]) -> int:
    if not matrix or not matrix[0]:
        return 0
    cols = len(matrix[0])
    dp = [0] * (cols + 1)  # dp[j+1] = side ending at (prev row, j)
    best = 0
    for row in matrix:
        diag = 0  # dp value of (i-1, j-1) before this row overwrote it
        for j, cell in enumerate(row):
            tmp = dp[j + 1]
            if cell == "1":
                dp[j + 1] = min(dp[j], dp[j + 1], diag) + 1
                best = max(best, dp[j + 1])
            else:
                dp[j + 1] = 0
            diag = tmp
    return best * best
int maximalSquareBruteForce(std::vector<std::vector<char>>& matrix) {
    if (matrix.empty() || matrix[0].empty()) return 0;
    int m = static_cast<int>(matrix.size());
    int n = static_cast<int>(matrix[0].size());
    int best = 0;
    for (int i = 0; i < m; ++i) {
        for (int j = 0; j < n; ++j) {
            if (matrix[i][j] != '1') continue;
            int side = 1;
            while (i + side < m && j + side < n) {
                bool edgeOk = true;
                for (int t = 0; t <= side && edgeOk; ++t) {
                    if (matrix[i + side][j + t] != '1') edgeOk = false;
                }
                for (int t = 0; t < side && edgeOk; ++t) {
                    if (matrix[i + t][j + side] != '1') edgeOk = false;
                }
                if (!edgeOk) break;
                ++side;
            }
            best = std::max(best, side);
        }
    }
    return best * best;
}
int maximalSquareFullTable(std::vector<std::vector<char>>& matrix) {
    if (matrix.empty() || matrix[0].empty()) return 0;
    int m = static_cast<int>(matrix.size());
    int n = static_cast<int>(matrix[0].size());
    // padded top/left border of zeros
    std::vector<std::vector<int>> side(m + 1, std::vector<int>(n + 1, 0));
    int best = 0;
    for (int i = 1; i <= m; ++i) {
        for (int j = 1; j <= n; ++j) {
            if (matrix[i - 1][j - 1] == '1') {
                side[i][j] = std::min({side[i - 1][j], side[i][j - 1],
                                       side[i - 1][j - 1]}) + 1;
                best = std::max(best, side[i][j]);
            }
        }
    }
    return best * best;
}
int maximalSquare(std::vector<std::vector<char>>& matrix) {
    if (matrix.empty() || matrix[0].empty()) return 0;
    int cols = static_cast<int>(matrix[0].size());
    std::vector<int> dp(cols + 1, 0);  // dp[j+1] = side ending at (i-1, j)
    int best = 0;
    for (const auto& row : matrix) {
        int diag = 0;  // side at (i-1, j-1) before this row overwrote it
        for (int j = 0; j < cols; ++j) {
            int tmp = dp[j + 1];
            if (row[j] == '1') {
                dp[j + 1] = std::min({dp[j], dp[j + 1], diag}) + 1;
                best = std::max(best, dp[j + 1]);
            } else {
                dp[j + 1] = 0;
            }
            diag = tmp;
        }
    }
    return best * best;
}
pub fn maximal_square_brute_force(matrix: Vec<Vec<char>>) -> i32 {
    if matrix.is_empty() || matrix[0].is_empty() {
        return 0;
    }
    let (m, n) = (matrix.len(), matrix[0].len());
    let mut best = 0usize;
    for i in 0..m {
        for j in 0..n {
            if matrix[i][j] != '1' {
                continue;
            }
            let mut side = 1usize;
            while i + side < m && j + side < n {
                let row_ok = (0..=side).all(|t| matrix[i + side][j + t] == '1');
                let col_ok = (0..side).all(|t| matrix[i + t][j + side] == '1');
                if !(row_ok && col_ok) {
                    break;
                }
                side += 1;
            }
            best = best.max(side);
        }
    }
    (best * best) as i32
}
pub fn maximal_square_full_table(matrix: Vec<Vec<char>>) -> i32 {
    if matrix.is_empty() || matrix[0].is_empty() {
        return 0;
    }
    let (m, n) = (matrix.len(), matrix[0].len());
    let mut side = vec![vec![0i32; n + 1]; m + 1]; // padded top/left border
    let mut best = 0;
    for i in 1..=m {
        for j in 1..=n {
            if matrix[i - 1][j - 1] == '1' {
                side[i][j] =
                    side[i - 1][j].min(side[i][j - 1]).min(side[i - 1][j - 1]) + 1;
                best = best.max(side[i][j]);
            }
        }
    }
    best * best
}
pub fn maximal_square(matrix: Vec<Vec<char>>) -> i32 {
    if matrix.is_empty() || matrix[0].is_empty() {
        return 0;
    }
    let cols = matrix[0].len();
    let mut dp = vec![0i32; cols + 1]; // dp[j+1] = side ending at (i-1, j)
    let mut best = 0;
    for row in &matrix {
        let mut diag = 0; // side at (i-1, j-1) before this row overwrote it
        for j in 0..cols {
            let tmp = dp[j + 1];
            if row[j] == '1' {
                dp[j + 1] = dp[j].min(dp[j + 1]).min(diag) + 1;
                best = best.max(dp[j + 1]);
            } else {
                dp[j + 1] = 0;
            }
            diag = tmp;
        }
    }
    best * best
}
function maximalSquareBruteForce(matrix: string[][]): number {
    if (matrix.length === 0 || matrix[0].length === 0) return 0;
    const m = matrix.length;
    const n = matrix[0].length;
    let best = 0;
    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            if (matrix[i][j] !== "1") continue;
            let side = 1;
            while (i + side < m && j + side < n) {
                let edgeOk = true;
                for (let t = 0; t <= side && edgeOk; t++) {
                    if (matrix[i + side][j + t] !== "1") edgeOk = false;
                }
                for (let t = 0; t < side && edgeOk; t++) {
                    if (matrix[i + t][j + side] !== "1") edgeOk = false;
                }
                if (!edgeOk) break;
                side++;
            }
            best = Math.max(best, side);
        }
    }
    return best * best;
}
function maximalSquareFullTable(matrix: string[][]): number {
    if (matrix.length === 0 || matrix[0].length === 0) return 0;
    const m = matrix.length;
    const n = matrix[0].length;
    // padded top/left border of zeros
    const side: number[][] = Array.from({ length: m + 1 }, () =>
        new Array<number>(n + 1).fill(0),
    );
    let best = 0;
    for (let i = 1; i <= m; i++) {
        for (let j = 1; j <= n; j++) {
            if (matrix[i - 1][j - 1] === "1") {
                side[i][j] = Math.min(side[i - 1][j], side[i][j - 1], side[i - 1][j - 1]) + 1;
                best = Math.max(best, side[i][j]);
            }
        }
    }
    return best * best;
}
function maximalSquare(matrix: string[][]): number {
    if (matrix.length === 0 || matrix[0].length === 0) return 0;
    const cols = matrix[0].length;
    const dp = new Array<number>(cols + 1).fill(0); // dp[j+1]: side at (i-1, j)
    let best = 0;
    for (const row of matrix) {
        let diag = 0; // side at (i-1, j-1) before this row overwrote it
        for (let j = 0; j < cols; j++) {
            const tmp = dp[j + 1];
            if (row[j] === "1") {
                dp[j + 1] = Math.min(dp[j], dp[j + 1], diag) + 1;
                best = Math.max(best, dp[j + 1]);
            } else {
                dp[j + 1] = 0;
            }
            diag = tmp;
        }
    }
    return best * best;
}
func maximalSquareBruteForce(matrix [][]byte) int {
	if len(matrix) == 0 || len(matrix[0]) == 0 {
		return 0
	}
	m, n := len(matrix), len(matrix[0])
	best := 0
	for i := 0; i < m; i++ {
		for j := 0; j < n; j++ {
			if matrix[i][j] != '1' {
				continue
			}
			side := 1
			for i+side < m && j+side < n {
				edgeOk := true
				for t := 0; t <= side && edgeOk; t++ {
					if matrix[i+side][j+t] != '1' {
						edgeOk = false
					}
				}
				for t := 0; t < side && edgeOk; t++ {
					if matrix[i+t][j+side] != '1' {
						edgeOk = false
					}
				}
				if !edgeOk {
					break
				}
				side++
			}
			best = max(best, side)
		}
	}
	return best * best
}
func maximalSquareFullTable(matrix [][]byte) int {
	if len(matrix) == 0 || len(matrix[0]) == 0 {
		return 0
	}
	m, n := len(matrix), len(matrix[0])
	side := make([][]int, m+1) // padded top/left border of zeros
	for i := range side {
		side[i] = make([]int, n+1)
	}
	best := 0
	for i := 1; i <= m; i++ {
		for j := 1; j <= n; j++ {
			if matrix[i-1][j-1] == '1' {
				side[i][j] = min(side[i-1][j], side[i][j-1], side[i-1][j-1]) + 1
				best = max(best, side[i][j])
			}
		}
	}
	return best * best
}
func maximalSquare(matrix [][]byte) int {
	if len(matrix) == 0 || len(matrix[0]) == 0 {
		return 0
	}
	cols := len(matrix[0])
	dp := make([]int, cols+1) // dp[j+1] = side ending at (i-1, j)
	best := 0
	for _, row := range matrix {
		diag := 0 // side at (i-1, j-1) before this row overwrote it
		for j := 0; j < cols; j++ {
			tmp := dp[j+1]
			if row[j] == '1' {
				dp[j+1] = min(dp[j], dp[j+1], diag) + 1
				best = max(best, dp[j+1])
			} else {
				dp[j+1] = 0
			}
			diag = tmp
		}
	}
	return best * best
}
func maximalSquareBruteForce(_ matrix: [[Character]]) -> Int {
    if matrix.isEmpty || matrix[0].isEmpty { return 0 }
    let m = matrix.count, n = matrix[0].count
    var best = 0
    for i in 0..<m {
        for j in 0..<n {
            if matrix[i][j] != "1" { continue }
            var side = 1
            while i + side < m && j + side < n {
                var edgeOk = true
                for t in 0...side {
                    if matrix[i + side][j + t] != "1" {
                        edgeOk = false
                        break
                    }
                }
                if edgeOk {
                    for t in 0..<side {
                        if matrix[i + t][j + side] != "1" {
                            edgeOk = false
                            break
                        }
                    }
                }
                if !edgeOk { break }
                side += 1
            }
            best = max(best, side)
        }
    }
    return best * best
}
func maximalSquareFullTable(_ matrix: [[Character]]) -> Int {
    if matrix.isEmpty || matrix[0].isEmpty { return 0 }
    let m = matrix.count, n = matrix[0].count
    // padded top/left border of zeros
    var side = [[Int]](repeating: [Int](repeating: 0, count: n + 1), count: m + 1)
    var best = 0
    for i in 1...m {
        for j in 1...n {
            if matrix[i - 1][j - 1] == "1" {
                side[i][j] = min(side[i - 1][j], side[i][j - 1], side[i - 1][j - 1]) + 1
                best = max(best, side[i][j])
            }
        }
    }
    return best * best
}
func maximalSquare(_ matrix: [[Character]]) -> Int {
    if matrix.isEmpty || matrix[0].isEmpty { return 0 }
    let cols = matrix[0].count
    var dp = [Int](repeating: 0, count: cols + 1) // dp[j+1]: side at (i-1, j)
    var best = 0
    for row in matrix {
        var diag = 0 // side at (i-1, j-1) before this row overwrote it
        for j in 0..<cols {
            let tmp = dp[j + 1]
            if row[j] == "1" {
                dp[j + 1] = min(dp[j], dp[j + 1], diag) + 1
                best = max(best, dp[j + 1])
            } else {
                dp[j + 1] = 0
            }
            diag = tmp
        }
    }
    return best * best
}
Recommended Approach 1 of 3 · Brute force — grow a square from every top-left cornerO(m * n * min(m, n)^2) time · O(1) space