Introduction
A hands-on workbook for mastering data structures and algorithms from first principles. Each topic is a fill-in-yourself lecture page (concept → complexity → implementation walkthrough) paired with a Java skeleton, JUnit 5 tests, and a problem set. Everything ships blank: you implement the skeletons until the tests pass and grind the problems until the patterns are automatic.
🗺️ Roadmap
Work the modules in this order — each phase assumes the vocabulary of the ones above it. Inside a phase, order is flexible.
graph TD
F["00 · Foundations"]
F --> A["01 · Arrays"]
F --> LL["02 · Linked Lists"]
F --> SQ["03 · Stacks & Queues"]
F --> HT["12 · Hash Tables"]
A --> RC["04 · Recursion &<br/>Divide and Conqueror"]
RC --> SS["05 · Searching & Sorting"]
SS --> TR["06 · Trees"]
TR --> HE["07 · Heaps"]
TR --> TRI["16 · Tries"]
TR --> SPT["08 · Spatial Trees"]
HT --> DSU["09 · Disjoint Sets"]
TR --> DSU
DSU --> GF["10 · Graph Fundamentals"]
GF --> GA["11 · Graph Algorithms"]
SS --> DP["13 · Dynamic Programming"]
SS --> GR["14 · Greedy"]
TRI --> STR["15 · String Algorithms"]
HE --> ADV["17 · Advanced"]
GA --> ADV
DP --> ADV
The path
| Phase | Modules | What you learn | What it unlocks |
|---|---|---|---|
| 0 · Foundations | 00 | Big-O, recurrences, amortized analysis | The language to reason about every later structure: don’t skip it |
| 1 · Linear structures | 01 Arrays · 02 Linked Lists · 03 Stacks & Queues · 12 Hash Tables | contiguous vs linked memory, LIFO/FIFO, O(1) hashing, prefix sums, monotonic stack | the containers every algorithm is built on (pull Hash Tables forward: it’s used everywhere) |
| 2 · Recursion & Order | 04 Recursion / Divide and Conqueror · 05 Searching and Sorting | recursion/backtracking, divide-and-conquer, the comparison sorts, binary search (incl. on the answer), quickselect | recursive thinking + the sort/search toolkit trees, graphs, and DP all lean on |
| 3 · Hierarchical | 06 Trees · 07 Heaps · 16 Tries · 08 Spatial Trees | BSTs and Balance (i.e. AVL/RB/B-tree), priority queues, prefix trees, k-d/quad trees | logarithmic ordered / priority / prefix queries |
| 4 · Graphs | 09 Disjoint Sets · 10 Graph Fundamentals · 11 Graph Algorithms | union-find, BFS/DFS/topo/SCC, Dijkstra/Bellman-Ford/Floyd-Warshall, MST, max-flow/min-cut | modeling the huge class of problems that are secretly graphs |
| 5 · Paradigms | 13 Dynamic Programming · 14 Greedy | state/transition design, the classic DPs, exchange-argument greedy | optimization problems — the hardest, most rewarding tier |
| 6 · Strings | 15 String Algorithms | KMP, Rabin–Karp, Z-algorithm | linear-time pattern matching (pairs with Tries from Phase 3) |
| 7 · Advanced | 17 | segment/Fenwick trees, lazy propagation, sparse tables, suffix arrays/trees, treaps, vEB | competitive-programming range-query power — a capstone |
How to work a module / topic
Best done with an AI coding agent as a tutor — see Learning with AI for the full setup. Per topic:
- Learn it section by section —
/teach <topic>(or “teach me<topic>”). The agent teaches one section, then stops until you understand it and have written that section’s notes in your own words. - Implement the
code/skeleton until thetests/conceptstests go green. - Grind the problem set — Foundational first (write them cold), then Applied (recognize the pattern, no peeking). They self-grade via tests, and LeetCode ones link out.
- Drill more when you want extra reps —
/extra-practice <module>generates fresh problems + stubs + tests in the same format.
The agent never hands over answers — it teaches, grades, and drills; you write every note, skeleton, and solution.
Pacing and Checkpoints
- ~1 module/week solo. Don’t advance until the Foundational problems are automatic; re-do a few old Applied ones weekly so earlier patterns stay sharp.
- After Phase 2 → most easy/medium array & sorting problems are comfortable.
- After Phase 4 → you can model and solve graph problems (a major interview gate).
- After Phase 5 → DP isn’t scary; you derive recurrences yourself.
- After Phase 7 → ready for competitive programming and the hardest interviews.
Learning This Workbook with AI
This workbook is meant to be worked with an AI coding agent — Claude Code, Cursor, Copilot, Codex, Gemini CLI, Windsurf, Aider — but in a very specific way. Used right, the AI is the fastest tutor you have ever had. Used wrong, it is the fastest way to learn nothing.
The one rule: the AI never gives you the answer. You write every note, implement every skeleton, and solve every problem yourself. The AI teaches the concepts, checks your work, and drills you — it never fills anything in.
This repo ships guard files that put any cooperating agent into tutor mode by default. Ask one to fill a note, implement a stub, or solve a problem and it will decline and coach you instead. The guard is for your benefit — don’t fight it.
Why so strict? Reading a handed-over solution feels like progress and produces almost none. Mastery comes from retrieval — pulling the answer out of your own head — and from practice. The AI’s job is to make retrieval and practice faster, not to skip them.
Setup — any agent works
- Get your own copy — use “Use this template” (or fork/clone) so you have a repo to fill in.
- Open it in your agent — the guard and teaching protocol load automatically. Every agent reads its own instructions file (
CLAUDE.md,AGENTS.md,.cursor/rules/,GEMINI.md, …), all of which point at the canonicalAGENTS.md. Zero setup. - (Optional) Turn on the tool layer — the repo ships a zero-dependency MCP server in
mcp/. If your agent supports MCP, approve thedsa-workbookserver when prompted (no install, nothing to run). It adds push-buttonteach_topicandgenerate_extra_practicetools. Seemcp/README.md.
The same workflow reaches you three ways — you don’t choose, they just work:
- Instruction files — zero setup, every agent, plain English (“teach me stacks”).
- MCP tools — approve once, deterministic (real section lists, problem numbering).
- Claude Code skills —
/teach,/extra-practiceslash commands (Claude Code only).
The loop — repeat for every topic
1. Learn it — section by section
Ask your agent to teach the topic:
- Claude Code:
/teach stacks - Any other agent: “teach me stacks” — it follows the same protocol.
You get a gated lecture: the agent teaches one section, then stops — you
explain it back and fill that section’s notes/ prompts in your own words
before it continues. It grades what you wrote; it never writes it for you. That
repeats, section by section, until the topic is done.
2. Write your notes
This happens inside the gate above. Each notes/ page is a list of italic
questions; you answer them in your own words as you are taught. Don’t move on
until a section’s notes hold up under the agent’s grading.
3. Implement the code
Read your own notes, fill the code/ skeleton, and run the module’s tests until
they pass:
./gradlew test_03-stacks-queues
Skeletons throw UnsupportedOperationException until you implement them — red is
the starting line, green is the goal.
4. Get your code reviewed
“Tests pass on my Stack — review it for edge cases and complexity I missed. Point them out, don’t fix them.”
Green tests are necessary, not sufficient.
5. Drill the problem set
Work the module’s PROBLEM_SET.md: Foundational problems cold (write them
with no help), then Applied. They self-grade via tests, and LeetCode ones
link out.
6. Get more reps
When you want extra practice — or you’re back for spaced review:
- Claude Code:
/extra-practice stacks - Any agent: “give me more problems on stacks”
It generates fresh problems into src/03-stacks-queues/extra-practice/ — markdown
problems, stubs, and failing tests in the same format as the shipped set. Solve
them until ./gradlew test_03-stacks-queues is green again.
7. Space it out
A few days later: “quiz me on what I struggled with last week.” Spaced repetition is what makes earlier topics stick.
One line:
/teach(gated) → write notes → implement → review → drill →/extra-practice→ space it out.
What the AI is for
Tutor mode is not “no help.” Lean on it for:
- Explaining concepts, complexity, and worked examples other than the assigned problem.
- Debugging your code — pointing at the bug, not rewriting it.
- Setup, Gradle, tooling, and navigating the repo.
- Generating extra practice on your weak spots.
Prompts worth keeping
| Goal | Prompt |
|---|---|
| Learn a topic | “Teach me <topic>” (or /teach <topic>) |
| Warm up | “Quiz me on <topic>, 5 questions, answers hidden.” |
| Grade recall | “Grade my filled-in note. Mark the gaps. Don’t rewrite it.” |
| Unstick | “I’m stuck on <problem>. Give the next step only — not the solution.” |
| Code review | “Tests pass. Review for edge cases and complexity. Don’t fix it.” |
| More reps | “More problems on <pattern>, harder.” (or /extra-practice <module>) |
| Spaced review | “Quiz me on what I struggled with last week.” |
The trap
The moment you type “just write it for me,” you have stopped learning. If you are tempted, that is exactly the rep that matters — ask for a hint, not the answer. The guard will nudge you back anyway.
Asymptotic Analysis
Why We Measure Growth, Not Wall-Clock Time
Argue why “this loop took 3ms on my laptop” is a useless way to compare algorithms — what variables (CPU, language, input) does wall-clock confound that asymptotics removes?
What “Input Size” Actually Means
Pin down \( n \) for different problems: array length, number of bits in an integer, vertices vs edges in a graph — how can choosing the wrong \( n \) make an analysis silently wrong?
Counting the Operations That Matter
Pick the dominant operation in a function and explain why you count it instead of every machine instruction — what gets folded into the constant factor, and why is that safe asymptotically?
The Cost Model You’re Assuming
Spell out the unit-cost (RAM) model: which operations you treat as \( O(1) \) — arithmetic, array indexing, comparisons — and where that assumption breaks (big integers, string compares, hashing).
Big-O as an Upper Bound
Describe Big-O as a “no worse than, up to a constant, for large enough \( n \)” promise, then connect that intuition to the \( \exists c, n_0 \) definition without proving anything.
Reading Big-O Off Real Code
Given nested loops and sequential blocks, walk through how you’d eyeball the Big-O — what do you do with constants, lower-order terms, and a loop that runs a fixed number of times?
The Role of \( c \) and \( n_0 \)
Explain what the witness constant \( c \) and threshold \( n_0 \) are doing — why is it fine that a “slower” Big-O algorithm wins for small \( n \)?
Omega and Theta: Lower and Tight Bounds
Explain what \( \Omega \) promises and what makes \( \Theta \) a “the growth is exactly this” statement — when can you NOT give a single \( \Theta \) for an algorithm?
Little-o and Little-omega
Contrast the strict \( o \) and \( \omega \) with \( O \) and \( \Omega \) using “strictly grows slower/faster” — how does the quantifier flip from “exists \( c \)” to “for all \( c \)”?
The Growth-Class Ladder
Order \( 1, \log n, n, n\log n, n^2, n^3, 2^n, n! \) and describe, for each rung, what code structure lands you there.
How One Class Dominates the Next
For an adjacent pair (say \( n\log n \) vs \( n^2 \)), describe the intuition for why the gap grows without bound — and roughly where the crossover sits in practice.
How Constants and Lower-Order Terms Vanish
Justify why \( 3n^2 + 100n + 7 \) collapses to \( n^2 \) — at what size does the \( n^2 \) term dominate, and why don’t we care about that crossover?
Combining Bounds: Sequential vs Nested Code
State the rule for adding costs (sequential blocks) vs multiplying them (nesting), and try it on a snippet with a loop after a doubly-nested loop — which term survives?
The Limit and Ratio Method
Show how \( \lim_{n\to\infty} f(n)/g(n) \) landing on \( 0 \), a positive constant, or \( \infty \) classifies the bound — when is this cleaner than juggling \( c \) and \( n_0 \) by hand?
Best, Worst, and Average Case
For one algorithm (try linear search or quicksort), predict all three and explain what input triggers each.
Which Case Does Big-O Describe?
Clear up the conflation: is Big-O the same as worst case? Explain how you can put a Big-O on the best case too.
Time Complexity
Define what you’re bounding when you say an algorithm is \( O(f(n)) \) in time — count of dominant operations as a function of \( n \), independent of hardware.
Per-Operation vs Whole-Algorithm Cost
Distinguish the cost of one method call from running the whole algorithm — when is it more honest to report per-operation cost (e.g. for a data structure’s API)?
Best / Average / Worst and Their Triggers
For a chosen algorithm, name the exact input shapes that trigger each case — why does average case usually require an assumption about the input distribution?
Why the Bound Holds (Intuition)
Take a doubly-nested loop and reason informally to \( O(n^2) \): how many times does the inner body run in total, and why does the summation come out quadratic?
Constant Factors That Actually Matter
Discuss when two \( O(n) \) algorithms differ enough in constant factor to matter — number of passes, cache behavior — and why Big-O hides this.
Space Complexity
Define space complexity as extra memory used as a function of \( n \) — what counts, and does the input array itself count?
Auxiliary Space vs Total Space
Separate “in-place” (\( O(1) \) auxiliary) from algorithms that allocate \( O(n) \) scratch — why is auxiliary space the number people usually quote?
Call-Stack Space: Iterative vs Recursive
Explain why recursion can cost \( O(\text{depth}) \) space with no explicit allocation — how deep is the stack for a balanced recursion vs a degenerate one, and how does converting to iteration change it?
Time–Space Tradeoffs
Give the intuition that you can buy speed with memory (memo tables, hashing) or save memory by recomputing — what question decides which side to favor?
Common Misconceptions and Traps
Collect classic mistakes: “Big-O means worst case”, “lower Big-O is always faster in practice”, reporting a loose \( O \) as tight, ignoring stack space — pick two and explain the fix.
Why This Is the Language of the Whole Course
Explain how every later topic gets summarized in this notation — what does it let you predict about an algorithm before you ever run it?
Implementation Walkthrough
Plan a tiny harness that empirically checks an asymptotic guess by operation-counting across growing \( n \) — sketch the shape of the code before writing it.
Setup and State
Decide what you’ll vary (the sizes of \( n \)) and what you’ll record (operation count or elapsed time) — what holds the results?
Generating Inputs per Size
Figure out how to build a representative input for each \( n \), and how to force best/worst case when you want to observe them.
The Measurement Loop
Work out how to run the algorithm under test for each \( n \) and capture its cost — where do you reset counters, and why warm up first if timing?
Inferring the Growth Class
Decide how you’d read the table of (\( n \), cost) pairs to guess the Big-O — what does the ratio of successive costs tell you when \( n \) doubles?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Recurrence Relations
What a Recurrence Is and Why Recursion Needs One
Explain why you can’t just “look at” a recursive function’s runtime the way you do a loop — what role does the recurrence play in describing the call structure?
Turning Code Into \( T(n) \)
Take a recursive routine and identify the three pieces: number of subcalls, subproblem size, and the non-recursive work — how does each map into the recurrence equation?
Reading the Three Parameters Off Code
For a given function, locate where \( a \) (branch factor), \( b \) (shrink factor), and \( f(n) \) (combine/split work) each live in the source — which is easiest to miscount?
Don’t Forget the Base Case
Explain why the base case matters for correctness even though it usually costs \( O(1) \) — when can a bad base case change the asymptotic answer?
The Recursion-Tree Method
Draw the tree for \( T(n) = 2T(n/2) + n \): label per-node cost, count nodes per level, and describe how summing levels gives the total.
Per-Level Cost and Tree Height
Reason about the two quantities that drive the total: how much work per level, and how many levels until the base case — what sets the height?
Where the Work Lives
Compare a tree with constant work per level, one dominated by the root, and one dominated by the leaves — how does the “shape of the work” predict the answer?
The Substitution Method
Describe the guess-then-verify-by-induction loop — what is the guess based on, and how do you check it holds for all \( n \)?
Why You Subtract a Lower-Order Term
Explain the classic trap where an off-by-a-constant guess fails the induction, and how strengthening the hypothesis (subtracting a term) fixes it.
The Master Theorem: The Cheat Sheet
State the comparison between \( n^{\log_b a} \) and \( f(n) \), and describe the three cases in words — which side “wins” in each?
Applying It Mechanically
Walk through extracting \( a \), \( b \), and \( f(n) \), then picking the case — what’s the most common extraction mistake?
When the Master Theorem Does NOT Apply
List what it can’t handle (non-polynomial gap, unequal subproblems, non-constant \( a \)) — how do you recognize each from the recurrence?
Common Divide-and-Conquer Recurrences
Match \( T(n)=2T(n/2)+n \), \( T(n)=2T(n/2)+1 \), and \( T(n)=T(n/2)+1 \) to algorithms and answers — what does each shape “feel” like?
Subtract-and-Conquer Recurrences
Contrast \( T(n)=T(n-1)+n \) with the divide-style ones — why does shrinking by a constant instead of a fraction blow up the cost?
Changing Variables to Tame a Recurrence
Show how a substitution like \( m = \log n \) can turn an awkward recurrence into a familiar linear one — what’s the signal this trick will help?
Time Complexity
Explain how solving the recurrence yields the time bound — why is the closed form of \( T(n) \) exactly the time complexity you quote?
Reading Time Off the Tree
Given the recursion tree, describe how total work = (work per level) × (number of levels) in the balanced case, and when that shortcut fails.
Best vs Worst Case via Different Recurrences
Show how the SAME algorithm can have two recurrences (e.g. quicksort’s balanced \( T(n/2) \) split vs degenerate \( T(n-1) \) split) — what input drives each?
Why the Bound Holds (Intuition)
For \( 2T(n/2)+n \), argue informally why the \( n \) work repeated over \( \log n \) levels gives \( n\log n \) — no formal induction required.
Space Complexity
Explain why a recurrence describes time, but the recursion ALSO costs memory — what determines that cost?
Call-Stack Depth
Reason about peak stack depth from the recurrence: for \( T(n/2) \)-style splits the depth is \( O(\log n) \), for \( T(n-1) \) it’s \( O(n) \) — why does the shrink factor set the depth?
Auxiliary Space Beyond the Stack
Separate stack space from arrays/buffers a divide-and-conquer routine allocates per call (e.g. mergesort’s merge buffer) — how do these combine into the total?
From Recurrence Back to Big-O
Explain how the closed form becomes the Big-O you quote — and why mergesort’s \( \Theta(n\log n) \) is “obvious” once you have the recurrence.
Implementation Walkthrough
Plan a recursive routine (e.g. mergesort) whose runtime you can analyze with a recurrence — sketch its parts before coding.
Setup and State
Decide the function signature and what bounds it receives — what’s the smallest input the recursion should stop on?
The Base Case
Work out the termination condition and why it must be reached on every path — what happens to \( T(n) \) if it’s wrong?
The Divide and Recurse Step
Figure out how the input is split into subproblems and how many recursive calls fire — this is the \( a \) and \( b \) of your recurrence.
The Combine Step
Determine the non-recursive work done per call (the \( f(n) \)) and where it happens relative to the recursion — does it dominate or get dominated?
Tracing the Recurrence to a Bound
Decide how you’d instrument the code (count calls, measure depth) to confirm the recurrence and bound you predicted.
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Proof Techniques
Why Algorithms Need Proofs, Not Just Tests
Explain what a proof of correctness buys you that a passing test suite never can — what category of bug do finite tests structurally miss?
Anatomy of a Claim
Break a statement like “for all sorted arrays, binary search returns the target if present” into its quantifier, hypothesis, and conclusion — what exactly must you establish?
Universal vs Existential Claims
Contrast “for all” and “there exists” claims — how does the burden of proof (and the way to disprove) flip between them?
Direct Proof
Describe chaining from hypothesis to conclusion using definitions and known facts — how do you decide the first thing to write down?
Proof by Contrapositive
Explain why proving \( \lnot Q \Rightarrow \lnot P \) settles \( P \Rightarrow Q \), and give the gut-check for when the contrapositive is the easier direction.
Proof by Contradiction
Describe assuming the negation and chasing it to an absurdity — what makes a claim a good candidate for this technique?
Contradiction vs Contrapositive
These feel similar — pin down the difference in what you assume and what you conclude, and when each reads more cleanly.
Mathematical Induction: The Engine
Lay out base case and inductive step as a “domino” argument — what is the inductive hypothesis actually letting you assume?
Weak vs Strong Induction
Compare assuming just \( k \) versus all of \( 1..k \) — describe a problem (proving a recursive routine correct) where you genuinely need the strong form.
Structural Induction
Explain induction over recursively-defined structures (trees, lists) — how does the base case become “the leaf/empty case” and the step become “assume children correct”?
Induction Pitfalls
Collect the classic failures: forgetting the base case, a base case that doesn’t connect to the step, assuming what you’re proving — pick one and show how it sneaks in.
Loop Invariants: Induction for Iterative Code
State the initialization, maintenance, and termination pattern — explain how it’s just induction over loop iterations in disguise.
Designing an Invariant
Given a loop (try insertion sort’s outer loop), describe how you’d find an invariant strong enough to imply correctness at termination.
Initialization, Maintenance, Termination
For your chosen loop, lay out what each of the three steps must show — which one most often reveals an off-by-one bug?
Counterexamples
Explain why a single counterexample destroys a “for all” claim, and how hunting for one is a debugging strategy before you commit to a proof.
Proof by Cases (Exhaustive Reasoning)
Describe when you must split into cases and what “exhaustive” requires — what’s the risk if your cases overlap or leave a gap?
Reasoning About Complexity Claims
Explain how proof technique underpins a complexity statement — when you assert an algorithm is \( O(n\log n) \), what is the implicit “for all inputs of size \( n \)” claim, and how would induction or an invariant justify it?
Time-Bound Arguments via Invariants
Show how a loop invariant about “work done so far” can prove a running-time bound, not just correctness — what quantity would the invariant track?
Space-Bound Arguments via Induction
Reason about proving a recursive routine never exceeds \( O(\log n) \) stack depth — how does induction on the recursion depth give that bound?
Connecting Proofs to Real Algorithm Analysis
Map each technique to where it shows up later: invariants for iterative algorithms, induction for recursion, contradiction for greedy/optimality, counterexamples for disproving a heuristic — which would you reach for first on a greedy proof?
Implementation Walkthrough
Plan code that DEMONSTRATES a proof technique — e.g. a function whose loop invariant you can assert and check at runtime — sketch its parts first.
Setup and State
Decide what state the loop maintains and what invariant you claim about it — where will you assert it?
The Main Loop and Its Invariant
Work out where in the loop body the invariant is temporarily broken and where it’s restored (maintenance) — what assertion catches a violation?
Termination and the Conclusion
Figure out the loop’s exit condition and what the invariant guarantees at that moment — how does it yield the correctness conclusion?
Turning the Argument Into a Test
Decide how you’d encode the invariant as runtime checks or property-based tests so the proof and the code agree.
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Logarithms And Summations
Logarithms as “How Many Times Can I Halve?”
Give the intuition that \( \log_2 n \) counts halvings (or doublings) — connect it to a number’s digit/bit count before any formula.
The Identities You Actually Use
List the product, quotient, power, and change-of-base rules, and flag the two or three that come up constantly — which one lets you swap bases freely?
Change of Base in Practice
Show how \( \log_b n = \log_k n / \log_k b \) turns any base into another times a constant — why does that constant disappear in Big-O?
Why the Log Base Doesn’t Matter in Big-O
Explain why \( \log_2 n \), \( \log_{10} n \), and \( \ln n \) are all \( \Theta(\log n) \) — what swallows the conversion constant, and when DOES the base matter (inside an exponent)?
Where Logs Come From in Code
Identify the two classic sources of a log factor: repeatedly halving the input, and tree height in a balanced structure — what do they share?
Logs in Divide and Conquer
Connect halving the problem size to \( \log_2 n \) levels of recursion — why is that the height of the recursion tree?
Logs from Balanced Tree Height
Reason about why a balanced binary tree on \( n \) nodes has height \( \Theta(\log n) \) — and why an unbalanced one degrades to \( \Theta(n) \).
Summations Show Up Whenever You Loop
Explain why analyzing a loop with varying per-iteration cost becomes a summation — give the translation from a for loop to \( \sum \).
Arithmetic Series
Recall the closed form for \( \sum_{i=1}^{n} i \) and describe where the \( n^2 \) in a nested loop secretly comes from this sum.
Geometric Series
Give the closed form for \( \sum_{i=0}^{n} r^i \) and the key insight: when \( r \neq 1 \), the sum is \( \Theta \) of its largest term — why does that make doubling/halving sums easy?
Increasing vs Decreasing Geometric
Contrast a sum dominated by its last term (growing) vs its first term (shrinking) — how does this explain why “last level dominates” in some recursion trees?
The Harmonic Series
Explain why \( \sum_{i=1}^{n} 1/i \approx \ln n \), and name an analysis (the cost of building something incrementally, or quicksort’s average case) where this \( \log \) sneaks in.
Bounding Sums With Integrals
Describe the picture: a monotone sum sandwiched between two integrals — how does this give a quick \( \Theta \) when no closed form is handy?
Telescoping Sums
Explain when consecutive terms cancel and only the endpoints survive — try spotting it in a sum that came from a recurrence.
Time Complexity
Explain why summations ARE the time-complexity calculation for loop-based code — what does \( \sum \) of per-iteration costs literally measure?
From Loop Shape to Summation
For a nested loop where the inner bound depends on the outer index, write the summation that counts inner-body executions — what closed form does it reduce to?
Why a Geometric Loop Is Often \( \Theta(n) \), Not \( \Theta(n\log n) \)
Reason about a loop whose work halves each round: why does the geometric sum collapse to a constant times the first term, keeping total work linear?
Why a Log Factor Appears
Take a loop that runs \( \log n \) times with \( O(n) \) work each (or \( n \) times with \( O(\log n) \) work) — explain how the product gives \( n\log n \).
Space Complexity
Connect logs and sums to memory: when does a structure need \( O(\log n) \) space and when \( O(n) \)?
Logarithmic Space from Recursion Depth
Explain why a balanced recursive routine holds \( O(\log n) \) frames on the stack — tie it back to the tree-height argument.
Summing Allocations Across a Build
Reason about the total memory when a structure grows in stages (e.g. doubling): why does the SUM of allocation sizes stay \( \Theta(n) \) rather than \( \Theta(n\log n) \)?
Putting It Together in Analysis
Take a doubly-nested loop where the inner bound depends on the outer index, set up the summation, and describe how you’d reduce it to a clean Big-O.
Implementation Walkthrough
Plan code that computes a series (e.g. partial sums of harmonic or geometric) and lets you observe its growth — sketch the parts first.
Setup and State
Decide what accumulator(s) you keep and what \( n \) you iterate to — what type avoids overflow or precision loss?
The Accumulation Loop
Work out the loop that adds each term — for a geometric series, how do you update the running term without recomputing a power each step?
Handling Edge Cases
Figure out the degenerate cases (\( r = 1 \), \( n = 0 \), the harmonic divide) and why each needs special thought.
Verifying Against the Closed Form
Decide how you’d compare the loop’s result to the closed-form formula to confirm your derivation.
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Amortized Analysis
The Problem: Worst-Case per Operation Lies
Explain why quoting the single most expensive operation overstates the real cost of a long sequence — what question does amortized analysis answer instead?
Amortized Is Not Average-Case
Clarify the difference: amortized makes no probability assumptions and holds for ANY sequence — why is this a stronger guarantee than “average over random inputs”?
The Mental Model: Spreading Cost Over Time
Describe the core intuition of “saving up” for an expensive operation across many cheap ones — what real-world analogy (prepaying, subscriptions) captures it?
The Aggregate Method
Explain the recipe: bound the total cost of \( n \) operations, then divide by \( n \) — what makes this the simplest method, and what’s its weakness?
Worked Setup: Dynamic Array Doubling
Set up the aggregate argument for \( n \) appends with table doubling — where do the expensive copies happen and what do the copy costs sum to?
The Accounting (Banker’s) Method
Describe assigning each operation a charge that may differ from its real cost, banking the surplus as credit — what invariant must the stored credit always satisfy?
Choosing the Right Charge
Explain how you pick a per-operation charge so credit never goes negative — what’s the trial-and-error feel of getting it right?
Mapping Credit to Real Objects
Reason about WHERE credit physically lives (e.g. on each array element) — why does tying credit to objects make the “never negative” check concrete?
The Potential Method
Define a potential function \( \Phi \) and the amortized cost \( \hat{c_i} = c_i + \Phi_i - \Phi_{i-1} \) — what properties must \( \Phi \) have for the bound to be valid?
Intuition for Potential
Explain \( \Phi \) as “stored energy” or “messiness” of the structure — how does an expensive operation correspond to a big drop in \( \Phi \)?
Picking \( \Phi \) for a Structure
Describe how you’d choose a potential for a dynamic array (relate it to how full the array is) — what makes a candidate \( \Phi \) work or fail?
Case Study: Dynamic Arrays (ArrayList Growth)
Walk through why each append is \( O(1) \) amortized despite occasional \( O(n) \) resizes — and why doubling, not adding a constant, is what makes it work.
Why Growth-by-Constant Breaks It
Reason about resizing by adding a fixed chunk instead of doubling — why does the amortized cost degrade to \( O(n) \) per append?
Case Study: A Stack With Multipop
Analyze a stack supporting push, pop, and multipop(k); argue why a sequence is cheap amortized even though one multipop can be \( O(n) \).
Case Study: A Binary Counter
Take incrementing a binary counter and show why the total bit-flips over \( n \) increments is \( O(n) \), making each increment \( O(1) \) amortized.
Time Complexity
Pin down what “amortized \( O(1) \)” actually claims about TIME over a sequence — and how it differs from a per-call worst-case bound.
Amortized vs Worst-Case per Operation
For the dynamic array, contrast the \( O(n) \) single-operation worst case with the \( O(1) \) amortized cost — which one matters for a tight loop of \( n \) appends, and why?
Why the Amortized Bound Holds (Intuition)
Argue informally why doubling makes the total copy work across \( n \) appends sum to \( O(n) \) — what geometric series is hiding in the resize costs?
When Amortized Isn’t Good Enough
Reason about a latency-sensitive setting where one \( O(n) \) resize spike is unacceptable even if amortized cost is \( O(1) \) — what does this push you toward (incremental resizing)?
Space Complexity
Explain the memory side of these structures — why does amortized time often come WITH some wasted space?
Wasted Space from Doubling
Reason about how much capacity sits unused right after (and right before) a resize — what’s the worst-case fraction of allocated space that’s empty?
Time–Space Tradeoff in the Growth Factor
Discuss how the growth factor (2× vs 1.5×) trades amortized time against wasted memory — why might a library pick a smaller factor?
Pitfalls and How to Sanity-Check
Collect common errors: confusing amortized with average, a potential that can go negative, forgetting initial potential — how would you catch each?
Where It Shows Up Later
Name structures whose advertised bounds are amortized (dynamic arrays, hash-table resizing, union-find, splay trees, Fibonacci heaps) — why does the field rely on this so heavily?
Implementation Walkthrough
Plan a dynamic array (or binary counter) from scratch so you can measure its amortized behavior — sketch the parts first.
Setup and State
Decide the fields you need: backing array, size, capacity — what’s the initial capacity and why does it matter for the first few appends?
The Append / Increment Operation
Work out the fast path that’s usually \( O(1) \) — what condition triggers the slow path?
The Resize / Carry Step
Figure out what the expensive branch does (allocate, copy, swap; or ripple-carry the bits) and why it runs rarely — how does its cost relate to the current size?
Instrumenting to Observe Amortization
Decide what to count (total copies or flips across \( n \) operations) and how you’d divide by \( n \) to confirm the amortized bound empirically.
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Foundations: Problem Set
Computational warm-ups on growth rates, operation counting, and the closed forms behind summations and recurrences. Every problem returns a single number — fill in the stub in problemset/ and make its test in tests/problemset/ pass.
Foundational
Problem 1: Nested Loop Operation Count
Description
A doubly nested loop runs the inner index j from the outer index i up to n (inclusive on i, exclusive on n):
for i in 0..n-1:
for j in i..n-1:
op() // count this
Given an integer n, return the exact number of times op() executes. This is the triangular number n(n+1)/2.
Examples
Example 1:
Input: n = 4
Output: 10
The inner loop runs 4, 3, 2, 1 times for i = 0, 1, 2, 3, summing to 10.
Example 2:
Input: n = 1
Output: 1
Example 3:
Input: n = 0
Output: 0
Constraints
0 <= n <= 100000
Problem 2: Geometric Series Sum
Description
Given a first term a, an integer ratio r, and a count k, return the sum of the geometric series a + a*r + a*r^2 + ... + a*r^(k-1), i.e. sum over i=0..k-1 of a*r^i. Handle the r = 1 case (the sum is a*k).
Examples
Example 1:
Input: a = 1, r = 2, k = 4
Output: 15
1 + 2 + 4 + 8 = 15.
Example 2:
Input: a = 3, r = 1, k = 5
Output: 15
With ratio 1 every term is 3, so the sum is 3 * 5 = 15.
Example 3:
Input: a = 2, r = 3, k = 3
Output: 26
2 + 6 + 18 = 26.
Constraints
1 <= k <= 301 <= r <= 10- the result fits in a 64-bit signed integer
Problem 3: Floor of Log Base 2
Description
Given an integer n >= 1, return floor(log2(n)) using only integer operations (no floating point). This equals the index of the highest set bit, or the number of times you can halve n before reaching 1.
Examples
Example 1:
Input: n = 1
Output: 0
2^0 = 1 <= 1 < 2.
Example 2:
Input: n = 8
Output: 3
2^3 = 8 <= 8 < 16.
Example 3:
Input: n = 100
Output: 6
2^6 = 64 <= 100 < 128.
Constraints
1 <= n <= 2000000000
Problem 4: Harmonic Number
Description
Given an integer n >= 0, return the n-th harmonic number H_n = sum over i=1..n of 1/i as a double, with H_0 = 0.
Examples
Example 1:
Input: n = 0
Output: 0.0
Example 2:
Input: n = 1
Output: 1.0
Example 3:
Input: n = 4
Output: 2.0833333333333335
1 + 1/2 + 1/3 + 1/4 = 25/12.
Constraints
0 <= n <= 1000000
Problem 5: Sum of First N Squares
Description
Given an integer n >= 0, return sum over i=1..n of i^2, which has the closed form n(n+1)(2n+1)/6.
Examples
Example 1:
Input: n = 3
Output: 14
1 + 4 + 9 = 14.
Example 2:
Input: n = 0
Output: 0
Example 3:
Input: n = 5
Output: 55
1 + 4 + 9 + 16 + 25 = 55.
Constraints
0 <= n <= 1000000
Applied Problems
Problem 6: Dynamic Array Doubling Copy Cost
Description
A dynamic array starts with capacity 1 and doubles its capacity whenever a push would overflow. Each doubling copies all currently stored elements into the new buffer. Simulate n pushes and return the total number of element copies performed across all resizes.
Examples
Example 1:
Input: n = 1
Output: 0
The first push fits in the initial capacity-1 buffer; no copy.
Example 2:
Input: n = 4
Output: 3
Resizes happen at the 2nd, 3rd, and 5th push; for n = 4 we copy 1 + 2 = 3 elements (capacity grows 1 -> 2 -> 4).
Example 3:
Input: n = 5
Output: 7
Capacity grows 1 -> 2 -> 4 -> 8, copying 1 + 2 + 4 = 7 elements.
Constraints
0 <= n <= 1000000
Problem 7: Sum of All Subarray Sums
Description
Given an array a of length m, return the sum of the sums of every contiguous subarray. Element a[i] appears in (i+1)*(m-i) subarrays, so the answer is sum over i of a[i]*(i+1)*(m-i) — computable in linear time.
Examples
Example 1:
Input: a = [1, 2, 3]
Output: 20
Subarrays: [1]=1, [2]=2, [3]=3, [1,2]=3, [2,3]=5, [1,2,3]=6, totaling 20.
Example 2:
Input: a = [4]
Output: 4
Example 3:
Input: a = [1, 1]
Output: 4
[1]=1, [1]=1, [1,1]=2, totaling 4.
Constraints
1 <= m <= 100000- the result fits in a 64-bit signed integer
Problem 8: Trailing Zeros of a Factorial
Description
Given an integer n >= 0, return the number of trailing zeros in n! in base 10. This equals floor(n/5) + floor(n/25) + floor(n/125) + ..., counting factors of 5.
Examples
Example 1:
Input: n = 5
Output: 1
5! = 120 ends in one zero.
Example 2:
Input: n = 25
Output: 6
floor(25/5) + floor(25/25) = 5 + 1 = 6.
Example 3:
Input: n = 3
Output: 0
3! = 6 has no trailing zero.
Constraints
0 <= n <= 2000000000
Problem 9: Reduce a Fraction
Description
Given a numerator and a nonzero denominator, reduce the fraction to lowest terms using the Euclidean algorithm. Return a two-element array [num, den] with the sign normalized so that den is always positive.
Examples
Example 1:
Input: numerator = 6, denominator = 8
Output: [3, 4]
Example 2:
Input: numerator = -4, denominator = -2
Output: [2, 1]
Both negatives cancel; gcd(4, 2) = 2.
Example 3:
Input: numerator = 3, denominator = -9
Output: [-1, 3]
The sign moves to the numerator.
Constraints
-1000000000 <= numerator <= 10000000001 <= |denominator| <= 1000000000
Problem 10: Log Factorial Cost
Description
Given an integer n >= 0, return log2(n!) computed as sum over i=1..n of log2(i) as a double. This summation grows as Theta(n log n) and equals 0.0 for n = 0 or n = 1.
Examples
Example 1:
Input: n = 1
Output: 0.0
log2(1) = 0.
Example 2:
Input: n = 2
Output: 1.0
log2(1) + log2(2) = 0 + 1 = 1.
Example 3:
Input: n = 4
Output: 4.584962500721156
log2(2) + log2(3) + log2(4) = 1 + 1.585 + 2.
Constraints
0 <= n <= 1000000
Problem 11: Recursion Tree Total Work
Description
For a power-of-two n, the recurrence T(n) = 2*T(n/2) + n builds a recursion tree where each level contributes exactly n work and there are log2(n) + 1 levels. Given such an n, return the total work summed across every node, which equals n*(log2(n) + 1).
Examples
Example 1:
Input: n = 1
Output: 1
A single node of work 1; log2(1) + 1 = 1 level.
Example 2:
Input: n = 8
Output: 32
8 * (3 + 1) = 32.
Example 3:
Input: n = 4
Output: 12
4 * (2 + 1) = 12.
Constraints
nis a power of two,1 <= n <= 1073741824
Problem 12: Binary Counter Bit Flips
Description
Starting from zero, perform k increments on a binary counter. Each increment flips a run of trailing 1s to 0 and then sets one 0 to 1, flipping every bit it touches. Return the total number of bit flips performed across all k increments. The amortized cost approaches 2 flips per increment.
Examples
Example 1:
Input: k = 1
Output: 1
0 -> 1 flips one bit.
Example 2:
Input: k = 4
Output: 7
Flips: 1 + 2 + 1 + 3 = 7 to reach 100.
Example 3:
Input: k = 8
Output: 15
Constraints
0 <= k <= 1000000000
Problem 13: Digit Power Summation
Description
For every integer from 1 to n, raise each of its decimal digits to the power p and accumulate. Return the total sum of all those digit-powers across the whole range.
Examples
Example 1:
Input: n = 11, p = 2
Output: 288
Numbers 1..9 contribute their digit squared, summing to 1+4+9+…+81 = 285; 10 adds 1^2+0^2 = 1; 11 adds 1^2+1^2 = 2, for 288 total.
Example 2:
Input: n = 9, p = 1
Output: 45
Each number 1..9 is a single digit; raising to power 1 leaves it unchanged, summing to 45.
Example 3:
Input: n = 10, p = 2
Output: 286
1^2 + 2^2 + … + 9^2 = 285, plus 10 contributes 1^2 + 0^2 = 1, totaling 286.
Constraints
1 <= n <= 1000001 <= p <= 6
Problem 14: Collatz Step Counter
Description
Starting at a positive integer n, repeatedly halve it when even or replace it with 3n + 1 when odd. Return the number of steps required to first reach 1.
Examples
Example 1:
Input: n = 1
Output: 0
Already at 1.
Example 2:
Input: n = 6
Output: 8
6 -> 3 -> 10 -> 5 -> 16 -> 8 -> 4 -> 2 -> 1 is 8 steps.
Example 3:
Input: n = 7
Output: 16
Constraints
1 <= n <= 1000000
Problem 15: Triangular Layer Budget
Description
A triangular stack places i crates in layer i, so the first k layers need k(k+1)/2 crates. Given a crate budget, return the largest number of complete layers k such that k(k+1)/2 <= crates.
Examples
Example 1:
Input: crates = 10
Output: 4
1+2+3+4 = 10 fits exactly; a 5th layer would need 15.
Example 2:
Input: crates = 0
Output: 0
Example 3:
Input: crates = 11
Output: 4
10 used by 4 layers; the 5th needs 15, which exceeds 11.
Constraints
0 <= crates <= 2000000000
Problem 16: Exponential Overtakes Polynomial
Description
Return the smallest positive integer n such that 2^n > n^k for a given exponent k. This shows concretely that 2^n eventually dominates any fixed polynomial n^k. Compare in a way that avoids overflow (e.g. logarithms or careful big-integer style growth).
Examples
Example 1:
Input: k = 1
Output: 1
2^1 = 2 > 1^1 = 1.
Example 2:
Input: k = 2
Output: 5
2^5 = 32 > 5^2 = 25, and no smaller n works (2^4 = 16 < 16 is not strictly greater).
Example 3:
Input: k = 3
Output: 10
2^10 = 1024 > 10^3 = 1000.
Constraints
1 <= k <= 10
Problem 17: Tribonacci Modulo
Description
Evaluate the recurrence T(k) = T(k-1) + T(k-2) + T(k-3) with T(0) = 0, T(1) = 1, T(2) = 1, and return T(k) mod 1000000007. Iterate to stay within machine integers.
Examples
Example 1:
Input: k = 0
Output: 0
Example 2:
Input: k = 4
Output: 4
T(3) = 0+1+1 = 2, T(4) = 1+1+2 = 4.
Example 3:
Input: k = 10
Output: 149
Constraints
0 <= k <= 1000000
Memory Model
What an Array Is & When to Reach for One
What problem does a fixed, indexable block of same-typed elements solve, and when is it the obvious first choice over a map or a list?
The Mental Model: One Labeled Ruler
Picture memory as a long ruler of numbered slots — what does it buy you to demand that all your data sit on one unbroken stretch of that ruler?
Contiguous Allocation
What does it mean for all elements to sit back-to-back in one block, and why must the runtime know the element size up front to allocate it?
Why Same-Typed Elements
Why does every slot having identical size make the indexing trick possible, and what would break if element sizes varied?
Stack vs Heap Placement
Where does the block physically come from for a local fixed array versus a heap-allocated one, and how does that affect lifetime?
The Base Pointer & Element Stride
Where does the array “live” as a single starting address, and what is the stride that separates one element’s start from the next?
Address Arithmetic
How is the address of element \( i \) computed from base and stride, and why is it pure multiply-and-add with no scanning?
Why Indexing Never “Looks Through” Earlier Elements
Why does the CPU never visit elements 0..i-1 to reach element \( i \) — what does it compute directly instead?
Zero-Based Indexing
Why does counting from 0 make the offset formula cleanest, and what does index 0 mean in terms of the base address?
Constant-Time Random Access
Why does reaching element one million cost the same as element 0, while a linked structure cannot promise that?
Cache Lines & Locality
How does a sequential walk ride the CPU cache line-by-line, and what makes a strided or random walk fall off a performance cliff even at the same Big-O?
Spatial vs Temporal Locality
What is the difference between reusing nearby addresses and reusing the same address, and which does a forward array scan exploit?
When Stride Hurts
Why can jumping by a large stride defeat the cache even though each individual access is still \( O(1) \)?
Fixed Size & Why Arrays Don’t Grow
Once the block is allocated, why can’t you append in place, and what does “the array is full” actually force you to do?
Bounds & Out-of-Range Access
What lives just past index \( n-1 \), who is responsible for the check, and how do Java’s checked access and C’s unchecked access differ?
The Invariant a Valid Index Must Hold
What condition must every index satisfy before use, and why is violating it either a thrown exception or silent corruption depending on the language?
Value vs Reference Element Types
What does each slot hold when elements are primitives versus objects, and where does the real object data actually live?
Time Complexity
Reason about the cost of every fundamental array operation and what makes each bound hold.
Index Read / Write
Why is access or assignment by index \( O(1) \) in all cases with no best/worst split — what single computation does it reduce to?
Search by Value
Why is finding an unknown value \( O(n) \) worst case, what is the best case (first slot) and the average case, and how would a sorted array change the picture?
Insert or Delete at an Index
Why does editing the middle cost \( O(n) \) from shifting, why is the front the worst case, and why is the tail the cheap case?
Why Constant Factors Still Matter
Two \( O(n) \) scans can differ widely in wall-clock time — how do cache behavior and stride change the hidden constant?
Space Complexity
Reason about how much memory an array occupies and what extra space its operations need.
The Array Itself
Why is the storage \( O(n) \) for \( n \) elements, and what fixed overhead (length field, header) rides along?
Operating In Place
Why do read, write, and search need only \( O(1) \) auxiliary space, and what does “in place” really mean here?
Iterative vs Recursive Traversal
Why does an iterative scan use \( O(1) \) extra space while a naive recursive walk can cost \( O(n) \) on the call stack — and what sits in those stack frames?
Common Bugs & Edge Cases
Off-by-one at the boundaries, the empty array, uninitialized/default values, signed-index mistakes, assuming a fixed array can grow — which trip people up most?
Real-World & Interview Uses
Where does the raw array hide under the hood — strings, buffers, matrices, hash-table backing stores — and which interview patterns lean on \( O(1) \) indexing?
Implementation Walkthrough
Before writing code, break the problem into the pieces you must get right.
State & Setup
What fields must your array wrapper hold (backing storage, length), and what must be true the instant it is constructed?
The Access Path
What does get(i) / set(i, v) do step by step, and where exactly does the bounds check belong relative to the offset computation?
The Traversal Loop
How is the scan loop structured, what are its start and stop conditions, and what invariant holds about every index already visited?
Termination & Return
How does each operation decide it is done, and what does it hand back (value, nothing, or a thrown error) in the success and out-of-bounds cases?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Dynamic Arrays
What It Is & When to Use It
Why wrap a fixed array in a structure that “grows,” and when do you pick this over a linked list or a plain array?
The Mental Model: A Resizable Wrapper
How is a dynamic array really a fixed array plus bookkeeping that lies about its size — what illusion does it maintain for the caller?
Capacity vs Size
What is the difference between how many elements exist and how many slots are allocated, and why must you track both as separate numbers?
The Core Invariant
What relationship between size and capacity must always hold, and what does each append or remove do to preserve it?
The Backing Array
How does the structure hold a private fixed array behind the scenes, and what stays the same when only size changes but capacity does not?
Append: The Central Operation
Why is push-to-end the operation that defines this structure, and what are its two distinct paths?
Append at the End (Room to Spare)
Walk the happy path — where does the new element go, what increments, and why is no copy involved?
Append When Full
What ordered sequence fires when capacity is exhausted: allocate a bigger block, copy the old elements, swap the reference, then write the new one?
Geometric Growth
Why multiply capacity by a factor (often 2 or 1.5) instead of adding a fixed chunk, and what would fixed-increment growth cost you over many appends?
Why a Factor, Not a Constant
What is the mental picture for why doubling makes resizes exponentially rarer as the array grows?
Insert & Delete at an Index
Why does inserting or removing in the middle force a shift of everything after it, which direction does each shift go, and when can a resize also be triggered?
Shrinking Policy
When should the backing array contract, why shrink at quarter-full rather than half-full, and how does that gap avoid grow/shrink thrashing at the boundary?
Iterator Invalidation & Mutation During Iteration
Why can a resize or a mid-loop removal leave a saved index, pointer, or iterator referring to the wrong slot or a freed block?
Time Complexity
Reason about per-operation cost, including why one operation needs the amortized lens.
Index Access & Update
Why are get and set flat \( O(1) \) just like a plain array, regardless of size?
Amortized Append
Why is a single append \( O(n) \) in the worst case (the resize) yet \( O(1) \) amortized — what is the intuition for spreading the rare expensive copy across the many cheap appends? State the bound; don’t formally derive it.
Insert / Delete at Index
Why is editing position \( k \) \( O(n - k) \), making the front \( O(n) \) and the tail \( O(1) \), and how does a triggered resize fold into that?
Search
Why is unsorted search \( O(n) \), and what changes if the contents are sorted?
Space Complexity
Reason about the memory the structure holds versus the data it stores.
Storage and Slack
Why is space \( O(n) \) yet potentially up to ~2x the live data because of unused capacity — and when is that slack at its worst?
Resize Transients
Why does a grow momentarily hold both the old and new backing arrays, and what is the peak extra space during the copy?
Auxiliary Space of Operations
Why do append, insert, and delete each need only \( O(1) \) auxiliary space beyond any resize copy?
Trade-offs vs Linked List
Random access and cache friendliness vs cheap middle insertion and no resize cost — when does each structure win?
Common Bugs & Edge Cases
Forgetting to null out removed slots (leaks), growing from capacity 0, integer overflow on the new capacity, copying size vs capacity elements — which bite hardest?
Real-World & Interview Uses
Where does this back the standard library (ArrayList, vector, Python list), and which interview patterns assume an \( O(1) \) amortized push?
Implementation Walkthrough
Before writing code, break the problem into the pieces you must get right.
State & Setup
What fields does the structure carry (backing array, size, capacity), and what should an empty or default-constructed instance look like?
The Grow Routine
What does the private resize do step by step — compute new capacity, allocate, copy each live element, reassign — and what must be true when it returns?
Append Threading Through Grow
How does add decide whether to call grow first, and in what order must the size increment and the element write happen?
Shifting for Insert and Delete
Which way do you copy elements to open or close a gap, and why does the copy direction matter to avoid overwriting data you still need?
Termination & Return
How does each operation signal success, bounds errors, or the new size, and what invariant must hold on every exit?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Multidimensional Arrays
What They Model & When to Use One
Grids, matrices, images, game boards — when is a 2D+ array the natural fit versus a map keyed by coordinates?
The Mental Model: A Shape Laid Over a Line
Memory is one-dimensional — what does it mean to impose a 2D “shape” on a single line of cells, and why is the shape just an interpretation of offsets?
Flattening a Grid into Linear Memory
What does it mean to “flatten” a rectangular grid into one unbroken run of cells, and why does flattening even happen under the hood?
Row-Major vs Column-Major Layout
Two ways to order the cells: walk a full row before dropping down, or walk a full column. Which does Java use, which do Fortran and NumPy default to?
Index Flattening Formula
How do you turn a coordinate \( (i, j) \) into one linear offset given the column count, and why does the number of columns appear in the formula?
Reading Back a Coordinate
Given a flat offset, how do you recover the row and column using divide and modulo, and which operand is the column count?
Strides for Higher Dimensions
Generalize the offset to 3D and beyond — what is a per-axis stride, and how do the strides chain so each index contributes its own multiplied term?
The Stride of Each Axis
Why does the innermost axis have stride 1 while each outer axis’s stride is the product of the sizes inside it?
Jagged (Array-of-Arrays) vs Rectangular
When are rows separate sub-arrays each with their own length and address versus one solid contiguous block? What does Java’s int[][] actually give you?
Memory Layout Consequences
Why does a jagged array scatter rows across the heap (extra pointer hop, worse locality) while a true rectangular block stays contiguous?
Traversal Order & Locality
Why can swapping the inner and outer loop over the same matrix change runtime dramatically, even though you touch every cell exactly once?
Matching Loop Order to Layout
Why should the innermost loop vary the axis with stride 1, and what cache penalty appears when it doesn’t?
Allocation & Initialization Patterns
How do you allocate a rectangular block, a jagged structure, or a single flat array you index by hand — and what are the default cell values in each?
Common Operations
Transpose, row/column sums, neighbor lookups on a board — how does each map onto the indexing scheme?
Time Complexity
Reason about the cost of grid operations in terms of rows R and columns C.
Cell Access
Why is reading or writing a single cell \( O(1) \) regardless of grid size, even with the flattening math?
Full Traversal
Why is visiting every cell \( O(R \cdot C) \), and why is that bound the same no matter the loop order even when wall-clock time differs?
Row Scan vs Column Scan
Why are both a single row scan \( O(C) \) and a single column scan \( O(R) \) in Big-O, yet one can be far slower in practice on a row-major layout?
Space Complexity
Reason about the memory a grid occupies and what extra space operations need.
Storing the Grid
Why is the data \( O(R \cdot C) \), and what extra overhead does a jagged array carry (one pointer and length per row) over a flat block?
In-Place vs Allocating Operations
Why can a row sum run in \( O(1) \) extra space while a transpose into a new grid needs \( O(R \cdot C) \), and when can transpose be done in place?
Recursive Grid Walks
Why can a recursive flood-fill or DFS over a grid use up to \( O(R \cdot C) \) call-stack space in the worst case?
Common Bugs & Edge Cases
Swapped \( i \) and \( j \), mismatched row lengths in a jagged array, off-by-one on the last row/column, assuming rectangularity that isn’t there.
Real-World & Interview Uses
Image buffers, matrices, dynamic-programming tables, grid/maze problems — which interview patterns live on a 2D array?
Implementation Walkthrough
Before writing code, break the problem into the pieces you must get right.
State & Setup
What does your structure store (a flat backing array plus row/column counts, or nested arrays), and what defines a valid newly-allocated grid?
The Coordinate-to-Offset Step
What does get(i, j) / set(i, j, v) compute, where do the two bounds checks go, and how does the offset formula appear in code?
The Nested Traversal
How are the outer and inner loops ordered, and what invariant holds about all cells already visited at any point in the walk?
Termination & Return
How does each operation finish, and what does it return or throw for a valid coordinate versus an out-of-range one?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Prefix & Suffix Sums and Products
What It’s For & Why It Matters
Why precompute cumulative totals once so that any range query becomes \( O(1) \) instead of re-scanning the subarray every time, and where do range queries dominate the workload?
The Core Idea
How does storing “the answer for every prefix” let you recover the answer for any interval by subtracting two stored values, turning repeated work into a single lookup?
The Mental Model
How do you picture the prefix-sum array as a running odometer, where the cost of a stretch of road is just the difference of two readings?
The Prefix-Sum Array
If \( P[i] \) is the sum of the first \( i \) elements, what does \( P \) let you compute, and why is the length usually \( n+1 \) with \( P[0] = 0 \)?
Building It
Why is each entry just the previous entry plus one new element, and why does that make construction a single left-to-right pass?
Range Sum in O(1)
Why does the sum of \( a[lo..hi] \) equal \( P[hi+1] - P[lo] \), and how does the \( P[0]=0 \) sentinel make the edge case \( lo = 0 \) just work?
Off-by-One Discipline
What goes wrong if you mix up inclusive vs. exclusive bounds, and how does fixing one convention (half-open) avoid the bugs?
Suffix Sums
What changes when you accumulate from the right instead of the left, and when is a suffix sum the natural tool rather than a prefix sum?
Prefix × Suffix Products
For “product of the array except self,” why does multiplying a prefix-product (everything before \( i \)) by a suffix-product (everything after \( i \)) give the answer without ever dividing?
Why Avoid Division
Why is the prefix·suffix approach preferable to “total product ÷ a[i],” especially when the array can contain a zero?
Difference Arrays (the Inverse Trick)
If prefix sums turn point values into range queries, why does a difference array do the opposite — turning many range updates into a single prefix-sum pass at the end?
One Update in O(1)
Why does adding delta at lo and subtracting it just past hi mark a whole range, and why does the final prefix-sum pass “spread” those marks back into actual values?
2-D Prefix Sums
How does inclusion–exclusion extend the idea to a grid so any axis-aligned rectangle sum is \( O(1) \), and why are there four terms in the query?
Building the Table
Why does each table cell combine the cell above, the cell to the left, and a subtracted overlap, and why is the table sized \( (R+1)\times(C+1) \)?
The Four-Corner Query
Why does a rectangle sum add two corners and subtract two, and which corner is the doubly-removed overlap that gets added back?
Implementation Walkthrough
Before writing code, decide each part.
Setup & Sizing
Why allocate \( n+1 \) (or \( (R+1)\times(C+1) \)) slots with a zero sentinel, and what invariant does \( P[0]=0 \) guarantee about every later entry?
The Build Loop
How does the single accumulation pass maintain “P[i] holds the sum of the first i elements” as it advances, and what is true of all entries to the left at each step?
The Query Step
How does a range or rectangle query reduce to a constant number of array reads and one subtraction (or inclusion–exclusion), with the bounds checks placed before the arithmetic?
Termination & Return
What does each operation return — a freshly built array/table from a build, a single number from a query — and what does it throw for an out-of-range interval?
Time Complexity
Separate the one-time build cost from the per-query cost.
Build Cost
Why is building the prefix array \( O(n) \) (or \( O(R \cdot C) \) in 2-D) — a single pass that touches each element once?
Query Cost
Why is each range or rectangle query \( O(1) \) regardless of how wide the interval is, since it reads only a fixed number of precomputed cells?
When the Trade Pays Off
Why does the up-front build only beat naive re-summing once you make enough queries, and how do you reason about the crossover point?
Space Complexity
Reason about the memory beyond the input.
The Stored Table
Why does the prefix array cost \( O(n) \) extra space (or \( O(R \cdot C) \) for a 2-D table), and what does that extra space buy you per query?
In-Place vs Allocating
When can a difference array or prefix pass be done in place over the input, and when must you allocate a separate \( n+1 \) array to keep the sentinel?
Common Bugs & Edge Cases
What breaks with integer overflow on large ranges (why long?), with the \( n+1 \) sizing, with an empty array, with a zero in product-except-self, or with forgetting the final prefix pass on a difference array?
Real-World & Interview Uses
Where do range-sum queries, “subarray with given sum,” running balances, image integral tables, and batch range-update problems rely on these techniques?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Arrays: Problem Set
Drill the core array techniques — two pointers, prefix sums, Kadane, matrix transforms, Boyer–Moore voting, and sliding windows. Foundational problems build the moves you should write cold; Applied Problems weave interview-style (LeetCode) and competitive-programming-style (Codeforces / Advent of Code / Codyssi) challenges, roughly easy to hard.
Foundational
Problem 1: Reverse In Place
LeetCode: 344. Reverse String
Description
You are given an int[] array. Reverse its elements in place using O(1) extra space — swap from both ends toward the middle — and return the same array reference.
Examples
Example 1:
Input: [1, 2, 3, 4]
Output: [4, 3, 2, 1]
Two-pointer swaps: (1,4) then (2,3).
Example 2:
Input: [1, 2, 3]
Output: [3, 2, 1]
Example 3:
Input: []
Output: []
Constraints
0 <= n <= 10^5- Must use
O(1)extra space.
Problem 2: Rotate Array Left
LeetCode: 189. Rotate Array
Description
Rotate the array nums to the left by k positions, in place. k may exceed n, so reduce it modulo n first. The element at index i moves to index (i - k mod n + n) mod n.
Examples
Example 1:
Input: nums = [1, 2, 3, 4, 5], k = 2
Output: [3, 4, 5, 1, 2]
The first two elements wrap around to the end.
Example 2:
Input: nums = [1, 2, 3], k = 0
Output: [1, 2, 3]
Example 3:
Input: nums = [1, 2, 3, 4], k = 6
Output: [3, 4, 1, 2]
6 mod 4 = 2.
Constraints
1 <= n <= 10^50 <= k <= 10^9
Problem 3: Two Sum II (Sorted)
LeetCode: 167. Two Sum II - Input Array Is Sorted
Description
Given a sorted (ascending) array a and a target, return the two 0-based indices [i, j] with i < j whose values sum to target. If no such pair exists, return [-1, -1]. Use the two-pointer technique in O(n).
Examples
Example 1:
Input: a = [2, 7, 11, 15], target = 9
Output: [0, 1]
2 + 7 = 9.
Example 2:
Input: a = [2, 3, 4], target = 6
Output: [0, 2]
Example 3:
Input: a = [1, 2, 3], target = 100
Output: [-1, -1]
Constraints
2 <= n <= 10^4- At most one valid pair.
Problem 4: Merge Sorted Arrays
LeetCode: 88. Merge Sorted Array
Description
Given two sorted (ascending) arrays a and b, merge them into a single new sorted array and return it. Use a two-pointer merge in O(a.length + b.length).
Examples
Example 1:
Input: a = [1, 3, 5], b = [2, 4]
Output: [1, 2, 3, 4, 5]
Example 2:
Input: a = [], b = [1, 2]
Output: [1, 2]
Example 3:
Input: a = [1, 1, 2], b = [1, 3]
Output: [1, 1, 1, 2, 3]
Constraints
0 <= a.length, b.length <= 10^5
Problem 5: Maximum Subarray
LeetCode: 53. Maximum Subarray
Description
Return the maximum sum of any contiguous, non-empty subarray of a, using Kadane’s algorithm in O(n). At each index keep the best sum ending there: cur = max(a[i], cur + a[i]).
Examples
Example 1:
Input: [-2, 1, -3, 4, -1, 2, 1, -5, 4]
Output: 6
The subarray [4, -1, 2, 1] sums to 6.
Example 2:
Input: [-3, -1, -2]
Output: -1
All negative — the single largest element wins.
Example 3:
Input: [5, 4, -1, 7, 8]
Output: 23
Constraints
1 <= n <= 10^5-10^4 <= a[i] <= 10^4
Problem 6: Remove Duplicates from Sorted Array
LeetCode: 26. Remove Duplicates from Sorted Array
Description
Given a sorted array, compact it in place so each value appears once, preserving order. Return the new logical length k; the first k slots must hold the unique values. Use a slow/fast two-pointer write index.
Examples
Example 1:
Input: [0, 0, 1, 1, 2]
Output: 3
The array begins [0, 1, 2, ...].
Example 2:
Input: [1, 1, 1]
Output: 1
Example 3:
Input: [1, 2, 3]
Output: 3
Constraints
0 <= n <= 3 * 10^4- Input is sorted ascending.
Applied Problems
Problem 7: Move Zeroes
LeetCode: 283. Move Zeroes
Description
Move all 0s in nums to the end while preserving the relative order of the non-zero elements. Do it in place with O(1) extra space, then return the array.
Examples
Example 1:
Input: [0, 1, 0, 3, 12]
Output: [1, 3, 12, 0, 0]
Example 2:
Input: [0, 0, 1]
Output: [1, 0, 0]
Example 3:
Input: [1, 2, 3]
Output: [1, 2, 3]
Constraints
1 <= n <= 10^4- In place,
O(1)extra space.
Problem 8: Majority Element
LeetCode: 169. Majority Element
Description
Return the element that appears more than n/2 times (guaranteed to exist). Use the Boyer–Moore voting algorithm in O(n) time and O(1) space: track a candidate and a count.
Examples
Example 1:
Input: [3, 2, 3]
Output: 3
Example 2:
Input: [2, 2, 1, 1, 1, 2, 2]
Output: 2
Example 3:
Input: [7]
Output: 7
Constraints
1 <= n <= 5 * 10^4- A majority element always exists.
Problem 9: Glacier Survey
Description
A drone records ice thickness t[i] over n days. A thaw streak is a maximal run of consecutive days where each day is strictly thinner than the previous one. Return the length of the longest thaw streak. A single day counts as a streak of length 1; an empty array returns 0.
Examples
Example 1:
Input: [5, 3, 2, 4, 1]
Output: 3
The run 5, 3, 2 is strictly decreasing.
Example 2:
Input: [1, 2, 3]
Output: 1
No two consecutive days decrease.
Example 3:
Input: []
Output: 0
Constraints
0 <= n <= 10^5
Problem 10: Container With Most Water
LeetCode: 11. Container With Most Water
Description
Given vertical line heights h, find two lines that with the x-axis form a container holding the most water. Return the maximum area min(h[i], h[j]) * (j - i) over all pairs. Use two pointers in O(n).
Examples
Example 1:
Input: [1, 8, 6, 2, 5, 4, 8, 3, 7]
Output: 49
Lines at indices 1 and 8: min(8,7) * 7 = 49.
Example 2:
Input: [1, 1]
Output: 1
Example 3:
Input: [4, 3, 2, 1, 4]
Output: 16
Constraints
2 <= n <= 10^50 <= h[i] <= 10^4
Problem 11: Relay Towers
Description
n towers stand in a row with heights h[i]. Looking from the far left, a tower is visible if it is strictly taller than every tower before it (the first tower is always visible). Return the count of visible towers.
Examples
Example 1:
Input: [3, 1, 4, 1, 5]
Output: 3
Towers 3, 4, 5 are each a new running maximum.
Example 2:
Input: [5, 4, 3]
Output: 1
Example 3:
Input: [1, 2, 3, 4]
Output: 4
Constraints
1 <= n <= 10^5
Problem 12: Best Time to Buy and Sell Stock
LeetCode: 121. Best Time to Buy and Sell Stock
Description
Given daily prices, return the maximum profit from buying on one day and selling on a strictly later day. If no profit is possible, return 0. Track the running minimum price in one O(n) pass.
Examples
Example 1:
Input: [7, 1, 5, 3, 6, 4]
Output: 5
Buy at 1, sell at 6.
Example 2:
Input: [7, 6, 4, 3, 1]
Output: 0
Prices only fall.
Example 3:
Input: [2, 4, 1]
Output: 2
Constraints
1 <= n <= 10^50 <= prices[i] <= 10^4
Problem 13: Product of Array Except Self
LeetCode: 238. Product of Array Except Self
Description
Return an array out where out[i] is the product of all elements of a except a[i]. Do it without division in O(n) using prefix and suffix products.
Examples
Example 1:
Input: [1, 2, 3, 4]
Output: [24, 12, 8, 6]
Example 2:
Input: [-1, 1, 0, -3, 3]
Output: [0, 0, 9, 0, 0]
Example 3:
Input: [2, 3]
Output: [3, 2]
Constraints
2 <= n <= 10^5- The full product fits in a 64-bit integer.
Problem 14: Ledger Audit
Description
Given n daily balance changes d[i] and q range queries (l, r), return an array whose j-th entry is the net change over days l..r inclusive. Answer all queries in O(n + q) using a prefix-sum array.
Examples
Example 1:
Input: d = [1, -2, 3, 4], queries = [[0,1], [1,3], [0,3]]
Output: [-1, 5, 6]
d[0..1] = -1, d[1..3] = 5, d[0..3] = 6.
Example 2:
Input: d = [5], queries = [[0,0]]
Output: [5]
Example 3:
Input: d = [2, 2, 2], queries = [[0,2], [1,1]]
Output: [6, 2]
Constraints
1 <= n, q <= 10^50 <= l <= r < n
Problem 15: Spiral Matrix
LeetCode: 54. Spiral Matrix
Description
Given an r x c matrix, return all its elements in clockwise spiral order, starting from the top-left. Walk right, down, left, up while shrinking the four boundaries.
Examples
Example 1:
Input: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Output: [1, 2, 3, 6, 9, 8, 7, 4, 5]
Example 2:
Input: [[1, 2], [3, 4]]
Output: [1, 2, 4, 3]
Example 3:
Input: [[1, 2, 3, 4]]
Output: [1, 2, 3, 4]
Constraints
1 <= r, c <= 100
Problem 16: Caravan Fuel (Gas Station)
LeetCode: 134. Gas Station
Description
On a circular route of n stations, station i provides gas[i] fuel and driving to the next station burns cost[i]. Starting with an empty tank, return the index of the first station from which you can complete a full loop, or -1 if impossible. Solve in one O(n) pass.
Examples
Example 1:
Input: gas = [1, 2, 3, 4, 5], cost = [3, 4, 5, 1, 2]
Output: 3
Starting at station 3 completes the loop.
Example 2:
Input: gas = [2, 3, 4], cost = [3, 4, 3]
Output: -1
Example 3:
Input: gas = [5, 1, 2, 3, 4], cost = [4, 4, 1, 5, 1]
Output: 4
Constraints
1 <= n <= 10^5- The answer is unique if it exists.
Problem 17: Rotate Image
LeetCode: 48. Rotate Image
Description
Rotate an n x n matrix 90° clockwise, in place, then return it. Standard approach: transpose the matrix, then reverse each row.
Examples
Example 1:
Input: [[1, 2], [3, 4]]
Output: [[3, 1], [4, 2]]
Example 2:
Input: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Output: [[7, 4, 1], [8, 5, 2], [9, 6, 3]]
Example 3:
Input: [[1]]
Output: [[1]]
Constraints
1 <= n <= 20- Rotate in place.
Problem 18: Seismic Peaks
Description
Given seismograph readings a[i], a peak is an index i with 0 < i < n-1 satisfying a[i-1] < a[i] > a[i+1]. Return the number of peaks (endpoints can never be peaks).
Examples
Example 1:
Input: [1, 3, 2, 4, 1]
Output: 2
Peaks at index 1 (value 3) and index 3 (value 4).
Example 2:
Input: [1, 2, 3]
Output: 0
Example 3:
Input: [5, 1, 5, 1, 5]
Output: 1
Constraints
1 <= n <= 10^5
Problem 19: Find Pivot Index
LeetCode: 724. Find Pivot Index
Description
Return the leftmost pivot index i where the sum of elements strictly to its left equals the sum strictly to its right. If none exists, return -1. Use a total sum and a running left sum in O(n).
Examples
Example 1:
Input: [1, 7, 3, 6, 5, 6]
Output: 3
Left of index 3 sums to 11; right of it sums to 11.
Example 2:
Input: [1, 2, 3]
Output: -1
Example 3:
Input: [2, 1, -1]
Output: 0
Left of index 0 is empty (0); right is 1 + (-1) = 0.
Constraints
1 <= n <= 10^4-1000 <= a[i] <= 1000
Problem 20: Report Repair
Description
From Advent of Code: given an expense report a of integers, find the two distinct entries that sum to target and return their product. Exactly one such pair exists. A hash set or two-pointer scan over the sorted array both work in O(n).
Examples
Example 1:
Input: a = [1721, 979, 366, 299, 675, 1456], target = 2020
Output: 514579
1721 + 299 = 2020, and 1721 * 299 = 514579.
Example 2:
Input: a = [10, 20, 30], target = 50
Output: 600
20 * 30 = 600.
Example 3:
Input: a = [1010, 1010, 5], target = 2020
Output: 1020100
Constraints
2 <= n <= 10^5- Exactly one valid pair exists.
Problem 21: Trapping Rain Water
LeetCode: 42. Trapping Rain Water
Description
Given bar heights h, compute how much water is trapped after raining. Water above each bar is min(maxLeft, maxRight) - h[i]. Use two pointers in O(n) time and O(1) extra space.
Examples
Example 1:
Input: [0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1]
Output: 6
Example 2:
Input: [4, 2, 0, 3, 2, 5]
Output: 9
Example 3:
Input: [1, 2, 3]
Output: 0
Constraints
0 <= n <= 2 * 10^40 <= h[i] <= 10^5
Problem 22: Subarray Sum Equals K
LeetCode: 560. Subarray Sum Equals K
Description
Return the number of contiguous subarrays whose elements sum to exactly k. Use prefix sums with a hash map counting how many times each running sum has been seen, in O(n).
Examples
Example 1:
Input: nums = [1, 1, 1], k = 2
Output: 2
Subarrays [0..1] and [1..2].
Example 2:
Input: nums = [1, 2, 3], k = 3
Output: 2
[1, 2] and [3].
Example 3:
Input: nums = [1, -1, 0], k = 0
Output: 3
Constraints
1 <= n <= 2 * 10^4-10^7 <= k <= 10^7
Problem 23: Warehouse Forklift
Description
A row of n crates has weights w[i]. A forklift moves left to right carrying a running load; whenever adding the next crate would exceed capacity L, it drops its current load (always able to carry at least one crate) and starts fresh with that crate. Return the total number of drops needed to clear all crates. The final non-empty load also counts as a drop.
Examples
Example 1:
Input: w = [2, 3, 1, 5], L = 5
Output: 3
Loads [2,3], [1], [5] — three drops.
Example 2:
Input: w = [1, 1, 1], L = 5
Output: 1
All three fit in one load.
Example 3:
Input: w = [4, 4, 4], L = 4
Output: 3
Constraints
1 <= n <= 10^51 <= w[i] <= L <= 10^9
Problem 24: Maximum Sum of Window Size K
LeetCode: 643. Maximum Average Subarray I
Description
Given an array a and an integer k, return the maximum sum of any contiguous subarray of length exactly k. Slide a fixed-size window: add the entering element, subtract the leaving one, in O(n).
Examples
Example 1:
Input: a = [1, 12, -5, -6, 50, 3], k = 4
Output: 51
Window [-5, -6, 50, 3] sums to 42… the best window [12, -5, -6, 50] sums to 51.
Example 2:
Input: a = [5, 5, 5], k = 1
Output: 5
Example 3:
Input: a = [-1, -2, -3, -4], k = 2
Output: -3
Window [-1, -2].
Constraints
1 <= k <= n <= 10^5
Singly Linked List
What It Is & When to Use It
A chain of nodes where each points only forward — when does this beat an array, and when does it lose badly?
The Mental Model: A Treasure Hunt
Why is a linked list less like a numbered shelf and more like a trail of clues where each node only knows where the next one is?
Node Layout
What does one node hold — a payload plus a single next reference — and why is there no contiguity guarantee between consecutive nodes?
Nodes Scattered Across the Heap
Why can two adjacent-in-the-list nodes sit far apart in memory, and what does that cost you in cache behavior versus an array?
Head Pointer & the Null Terminator
Where does the list begin, what marks the end, and why is the empty list simply head == null?
The Structural Invariant
Following next repeatedly from a non-cyclic head must end in null — why is preserving that the heart of every edit?
Traversal
How do you walk node-to-node with a moving cursor, why can you only ever go forward, and why is there no way to “back up” without restarting?
Core Operations
The everyday verbs — split each into its own pointer dance.
Insert at Head
Why is prepending the cheapest possible write, and what two assignments make it work and in what order?
Insert at Tail
Why does appending cost a full walk unless you keep a tail pointer, and what does maintaining a tail pointer buy and cost you?
Insert After a Given Node
Which links rewire to splice a new node between two existing ones, and in what order so you never lose the rest of the chain?
Delete a Node
Why do you need the node before the target, and how does the predecessor’s next jump over the victim to unlink it?
Search by Value
Why is finding an element inherently a linear scan with no shortcut, even when the data happens to be sorted?
Reversal
How do you flip every next pointer in a single pass using three cursors (prev, curr, next), and where does the old head end up?
Why You Need Three Pointers
Why does saving curr.next before rewiring it prevent you from severing the unvisited remainder of the list?
Cycle Detection
How do fast and slow pointers (tortoise and hare) reveal a loop, and what does it mean structurally when they meet?
Time Complexity
Reason about each operation and exactly what makes it cheap or costly.
Head Operations
Why are insert-at-head and delete-head \( O(1) \) — what work is bounded no matter how long the list is?
Tail Operations
Why is insert-at-tail \( O(n) \) without a tail pointer but \( O(1) \) with one, and why is delete-tail still \( O(n) \) even with a tail pointer?
Search & Index Access
Why are search and “get the k-th element” \( O(n) \) worst case, what is the best case, and why is there no \( O(1) \) random access at all?
Reversal & Cycle Detection
Why are both single-pass \( O(n) \) walks, and why does the two-pointer cycle check stay \( O(n) \) despite the hare moving twice as fast?
Space Complexity
Reason about per-node overhead and the extra space operations consume.
Per-Node Pointer Overhead
Why does each node cost its payload plus one reference, making total space \( O(n) \) with a real constant the array doesn’t pay?
In-Place Operations
Why do insert, delete, search, and iterative reversal all need only \( O(1) \) auxiliary space?
Recursive vs Iterative Traversal
Why does a recursive traversal or recursive reversal use \( O(n) \) call-stack space while the iterative version uses \( O(1) \) — and what risk does that create on a long list?
Trade-offs vs Arrays
No random access and poor cache behavior vs \( O(1) \) splicing and no resize cost — when does each win?
Pointer-Surgery Bugs & Edge Cases
Losing the tail by reassigning next too early, dropping the last reference (leak), one-element and empty-list cases, deleting the head — which are the classic traps?
Real-World & Interview Uses
Stacks, adjacency lists, LRU chains, undo stacks, and the many “reverse/merge/detect-cycle” interview staples.
Implementation Walkthrough
Before writing code, break the problem into the pieces you must get right.
State & Setup
What fields does the list hold (head, optional tail, optional size), and what does a freshly constructed empty list look like?
The Traversal Cursor
How do you set up and advance a walking pointer, and what is the loop’s stop condition so you don’t dereference null?
The Pointer-Rewire Step
For insert and delete, in what order must you read-then-write the next links so no part of the chain is orphaned mid-edit?
Edge-Case Branches
Which special cases (empty list, single node, operating on the head) need their own branch, and what must each branch update?
Termination & Return
How does each operation decide it is done, and what does it return — a value, the new head, a boolean, or nothing?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Doubly Linked List
What It Is & When to Use It
Each node links both ways — what does the backward pointer unlock that a singly linked list can’t offer?
The Mental Model: A Two-Way Street
Why is a doubly linked list like a street with signs pointing both directions at every house, and what new movements does that enable?
Node Layout (prev / next)
What three fields does a node carry now, and what invariant ties a.next == b to b.prev == a?
The Mutual-Link Invariant
Why must every forward link have a matching backward link, and how does breaking that symmetry corrupt the whole list?
Head, Tail & Sentinel Nodes
Why keep both a head and a tail pointer, and how do dummy sentinel nodes erase the special cases at the boundaries?
Life Without Sentinels
What boundary checks does every insert and delete need when head and tail can be null, and which bugs does that breed?
Life With Sentinels
How does ringing the list with permanent dummy head and tail nodes mean a real node always has non-null neighbors?
Bidirectional Traversal
How does holding both links let you walk forward from the head or backward from the tail with the same code shape?
Core Operations
Every edit touches more pointers than in a singly list — break them out.
Insert at Head / Tail
With a tail pointer or tail sentinel, why are both ends now symmetric \( O(1) \) inserts, and which links update at each end?
Insert Between Two Nodes
Which four pointers change when you splice a new node between a left and right neighbor, and in what safe order so no link is read after it’s overwritten?
Delete Given a Node
Why can you remove a node in constant time when you already hold a reference to it — no predecessor search needed — and which two links bridge the gap left behind?
Search by Value
Why is finding by value still a linear scan despite the extra pointer, and can searching from both ends help in the worst case?
Sentinels vs Null Checks
How does ringing the list with dummy head/tail nodes turn every insert and delete into the same uniform four assignments with no branching?
Time Complexity
Reason about what the backward pointer makes cheap and what it leaves unchanged.
Insert / Delete at a Known Node
Why is splicing in or removing a node you already hold strictly \( O(1) \), and why is this the single biggest win over a singly list?
Head & Tail Operations
Why are insert and delete at both ends \( O(1) \) here, when a singly list could only cheaply delete at the head?
Search & Index Access
Why are search and “get the k-th node” still \( O(n) \), and how does bidirectional access only halve the constant, not the order?
Space Complexity
Reason about the cost of the second pointer.
Two Pointers Per Node
Why is total space \( O(n) \) but with a larger constant than a singly list, and when does that overhead actually matter?
Sentinel Overhead
Why do the dummy head and tail add only \( O(1) \) fixed space while removing branching from every operation?
Auxiliary Space of Operations
Why do all insert, delete, and search operations need only \( O(1) \) extra space, and why is a recursive traversal still \( O(n) \) on the stack?
Trade-offs vs Singly Linked
Two pointers per node means more memory and more links to keep consistent — when is the backward link worth that overhead?
Pointer-Surgery Bugs & Edge Cases
Updating next but forgetting the matching prev, breaking the a.next/b.prev invariant, single-node and empty cases, deleting head or tail without sentinels.
Real-World & Interview Uses
LRU caches (the canonical use), browser history, text-editor buffers, deques, and java.util.LinkedList under the hood.
Implementation Walkthrough
Before writing code, break the problem into the pieces you must get right.
State & Setup
What does the list hold (head/tail pointers or two sentinels, plus size), and how do you wire the sentinels together at construction so the empty list is valid?
The Four-Pointer Splice
For insert between neighbors, name the four assignments and the order that keeps both the new node’s and the neighbors’ links consistent.
The Unlink Step
For delete, which two links must you redirect to bypass the node, and what should you do to the removed node’s own pointers?
Edge-Case Branches (or Their Absence)
Which boundary cases need special handling without sentinels, and why do sentinels let you delete those branches entirely?
Termination & Return
How does each operation finish, and what does it return — a value, a boolean, or the affected node?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Skip Lists
What It Is & When to Use It
A sorted linked list with stacked “express lanes” — when do you reach for this instead of a balanced tree, and why is it loved for concurrency?
The Mental Model: Express Lanes Over Local Stops
Why is a skip list like a subway map where higher levels are express trains skipping many stops, and how does dropping to a slower line near your destination mirror the search?
The Layered Structure
How do stacked sorted lists sit on top of a full base list, and what does each higher level skip over relative to the level below it?
The Base Level Holds Everything
Why must the bottom level contain every element in sorted order, and why is correctness anchored there?
Node Towers
What does it mean for one node to exist on several levels at once, and what does a node’s array of forward pointers look like?
Height of a Tower
What determines how tall a given node’s tower is, and what does a node of height 1 versus height 4 participate in?
Probabilistic Levels
Why use a coin flip to decide each node’s height instead of strictly balancing, and roughly what fraction of nodes reach each successive level?
Why Randomness Replaces Rebalancing
What does the coin flip buy you that lets you skip the rotations a balanced tree needs, and what do you give up in exchange?
Search
How does a query start at the top-left, move right until the next node would overshoot the target, then drop a level — and why does that feel like a balanced-tree descent?
Tracking the Update Path
Why must search remember the rightmost node it stopped at on every level, and how is that saved path reused by insert and delete?
Insertion & Tower Building
How do you find the slot via the update path, flip coins to pick the new node’s height, and splice it into each level it reaches?
Growing the List’s Level
What happens when a new tower is taller than any existing one, and how must the head and the recorded level adjust?
Deletion
How do you unlink a node from every level it appears on, why is the update path again the key, and when should the list’s top level shrink back?
Time Complexity
Reason about why the randomized structure behaves like a logarithmic one.
Why Search Is Expected \( O(\log n) \)
Why does halving (roughly) the candidates at each level make the expected number of levels about \( \log n \), and why is the work per level expected constant?
Insert & Delete
Why do insert and delete share search’s \( O(\log n) \) expected cost plus an expected-constant amount of splicing across the touched levels?
Worst Case vs Expected
Why is the worst case \( O(n) \) (every tower the same height), why is “expected” the honest word, and why does it rarely bite in practice?
Space Complexity
Reason about the cost of all those extra pointers.
Expected Tower Heights
Why does the average tower height stay constant, making total pointer space expected \( O(n) \) rather than \( O(n \log n) \)?
Tuning with the Probability Factor
How does the coin’s probability \( p \) trade pointer space against search speed, and what does a smaller \( p \) do to both?
Search Auxiliary Space
Why does a search need only \( O(1) \) working space, while remembering the update path for insert/delete costs \( O(\log n) \) expected?
Trade-offs vs Balanced Trees
Simpler code and no rotations vs randomized (not worst-case) guarantees — when does a skip list win over a red-black or AVL tree?
Common Bugs & Edge Cases
Forgetting to update every level, an empty or single-node list, growing the head’s level when a tall tower appears, an unseeded RNG making the structure degenerate.
Real-World & Interview Uses
Redis sorted sets, ConcurrentSkipListMap, LevelDB-style stores, and ranked leaderboards — where the ordered, lock-friendly structure shines.
Implementation Walkthrough
Before writing code, break the problem into the pieces you must get right.
State & Setup
What does the structure hold (a head with an array of forward pointers, a current level, a max level, the probability \( p \)), and what does the empty list look like?
The Random-Level Routine
How does the coin-flip loop decide a new node’s height, what caps it, and how should the RNG be seeded to avoid degenerate runs?
The Search-and-Record Loop
How do you descend from the top level, move right while the next key is smaller than the target, and store the stop node per level into the update array?
Splicing Across Levels
Using the update array, how do you rewire forward pointers at each level the new node reaches, and how does this mirror singly-linked insertion repeated per level?
Termination & Return
How does each operation finish, when do you raise or lower the list’s current level, and what does each return (found value, success boolean)?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Linked Lists: Problem Set
Work these in order. Every problem is an implementation task: fill in the stub
in problemset/ and make its test in tests/problemset/ pass. The Foundational
problems drill the core pointer-rewiring moves; the Applied problems weave
interview classics together with contest-style challenges, ramping from easy to
hard. All share the ListNode helper and are verified the same way. No
solutions are provided.
Foundational
Problem 1: Reverse a Singly Linked List
LeetCode: 206. Reverse Linked List
Description
Given the head of a singly linked list, reverse the list and return the head
of the reversed list. Do it iteratively in \( O(n) \) time and \( O(1) \)
extra space by rewiring the next pointers as you walk.
Examples
Example 1:
Input: 1 -> 2 -> 3 -> 4 -> 5
Output: 5 -> 4 -> 3 -> 2 -> 1
Every link is flipped to point at the previous node.
Example 2:
Input: 7
Output: 7
A single node is its own reverse.
Example 3:
Input: (empty)
Output: (empty)
An empty list reverses to an empty list.
Constraints
- The number of nodes is in the range \( [0, 5000] \).
- \( -5000 \le \text{Node.val} \le 5000 \).
Problem 2: Find the Middle Node
LeetCode: 876. Middle of the Linked List
Description
Given the head of a non-empty singly linked list, return the middle node. If
the list has an even number of nodes, return the second of the two middle nodes.
Use a single pass with the slow/fast two-pointer technique.
Examples
Example 1:
Input: 1 -> 2 -> 3 -> 4 -> 5
Output: 3 -> 4 -> 5
The middle of five nodes is the third node.
Example 2:
Input: 1 -> 2 -> 3 -> 4 -> 5 -> 6
Output: 4 -> 5 -> 6
For six nodes the second middle (the fourth node) is returned.
Example 3:
Input: 1
Output: 1
A single node is its own middle.
Constraints
- The number of nodes is in the range \( [1, 100] \).
- \( 1 \le \text{Node.val} \le 100 \).
Problem 3: Detect a Cycle
LeetCode: 141. Linked List Cycle
Description
Given the head of a singly linked list, return true if the list contains a
cycle and false otherwise. A cycle exists if some node can be reached again by
continuously following next. Use \( O(1) \) extra space (Floyd’s
slow/fast pointers).
Examples
Example 1:
Input: 3 -> 2 -> 0 -> -4, tail connects back to node index 1 (value 2)
Output: true
Following next from the tail re-enters the list at the node with value 2.
Example 2:
Input: 1 -> 2, tail connects back to node index 0 (value 1)
Output: true
The two-node list loops on itself.
Example 3:
Input: 1 -> 2 -> 3 -> 4 (no cycle)
Output: false
The tail’s next is null, so traversal terminates.
Constraints
- The number of nodes is in the range \( [0, 10^4] \).
- \( -10^5 \le \text{Node.val} \le 10^5 \).
Problem 4: Merge Two Sorted Lists
LeetCode: 21. Merge Two Sorted Lists
Description
You are given the heads of two singly linked lists, a and b, each sorted in
ascending order. Splice their nodes together into one ascending sorted list and
return its head. The result should be made by reusing the existing nodes, not by
allocating new ones.
Examples
Example 1:
Input: a = 1 -> 2 -> 4, b = 1 -> 3 -> 4
Output: 1 -> 1 -> 2 -> 3 -> 4 -> 4
Nodes are interleaved in non-decreasing order.
Example 2:
Input: a = (empty), b = (empty)
Output: (empty)
Two empty lists merge to an empty list.
Example 3:
Input: a = (empty), b = 0
Output: 0
Merging with an empty list returns the other list.
Constraints
- The number of nodes in both lists is in the range \( [0, 50] \).
- \( -100 \le \text{Node.val} \le 100 \).
Problem 5: Remove Duplicates from a Sorted List
LeetCode: 83. Remove Duplicates from Sorted List
Description
Given the head of a sorted singly linked list, delete all nodes that have
duplicate values so that each value appears exactly once. Return the head of the
modified list, which must remain sorted.
Examples
Example 1:
Input: 1 -> 1 -> 2
Output: 1 -> 2
The second 1 is dropped.
Example 2:
Input: 1 -> 1 -> 2 -> 3 -> 3
Output: 1 -> 2 -> 3
Each run of equal values collapses to one node.
Example 3:
Input: (empty)
Output: (empty)
An empty list is unchanged.
Constraints
- The number of nodes is in the range \( [0, 300] \).
- \( -100 \le \text{Node.val} \le 100 \), and the list is sorted ascending.
Applied Problems
Problem 6: Remove the N-th Node From the End
LeetCode: 19. Remove Nth Node From End of List
Description
Given the head of a linked list, remove the n-th node from the end of the
list and return its head. Do it in a single pass by advancing one pointer n
steps ahead of another so the gap pinpoints the node to unlink.
Examples
Example 1:
Input: head = 1 -> 2 -> 3 -> 4 -> 5, n = 2
Output: 1 -> 2 -> 3 -> 5
The 2nd-from-last node (value 4) is removed.
Example 2:
Input: head = 1, n = 1
Output: (empty)
Removing the only node leaves an empty list.
Example 3:
Input: head = 1 -> 2, n = 2
Output: 2
Removing the 2nd-from-last node (the head) leaves the tail.
Constraints
- The number of nodes is
sz, with \( 1 \le sz \le 30 \). - \( 1 \le n \le sz \).
Problem 7: Palindrome Linked List
LeetCode: 234. Palindrome Linked List
Description
Given the head of a singly linked list, return true if the sequence of
values reads the same forwards and backwards. Aim for \( O(n) \) time and
\( O(1) \) extra space (find the middle, reverse the second half, compare).
Examples
Example 1:
Input: 1 -> 2 -> 2 -> 1
Output: true
The values mirror around the center.
Example 2:
Input: 1 -> 2 -> 3 -> 2 -> 1
Output: true
An odd-length palindrome with a lone middle value.
Example 3:
Input: 1 -> 2
Output: false
Reversed it reads 2 -> 1, which differs.
Constraints
- The number of nodes is in the range \( [1, 10^5] \).
- \( 0 \le \text{Node.val} \le 9 \).
Problem 8: Odd-Even Linked List
LeetCode: 328. Odd Even Linked List
Description
Given the head of a singly linked list, group all nodes at odd positions
together followed by the nodes at even positions, then return the reordered
list. Positions are 1-based by index, not by value. Preserve the relative order
within each group and use \( O(1) \) extra space.
Examples
Example 1:
Input: 1 -> 2 -> 3 -> 4 -> 5
Output: 1 -> 3 -> 5 -> 2 -> 4
Odd-position nodes (1,3,5) precede even-position nodes (2,4).
Example 2:
Input: 2 -> 1 -> 3 -> 5 -> 6 -> 4 -> 7
Output: 2 -> 3 -> 6 -> 7 -> 1 -> 5 -> 4
Grouping is by position, so values stay in their original relative order.
Example 3:
Input: 1 -> 2
Output: 1 -> 2
With one node per group, the list is unchanged.
Constraints
- The number of nodes is in the range \( [0, 10^4] \).
- \( -10^6 \le \text{Node.val} \le 10^6 \).
Problem 9: Swap Nodes in Pairs
LeetCode: 24. Swap Nodes in Pairs
Description
Given the head of a linked list, swap every two adjacent nodes and return its
head. You must change the node links rather than the values. If the list has an
odd number of nodes, the final unpaired node stays in place.
Examples
Example 1:
Input: 1 -> 2 -> 3 -> 4
Output: 2 -> 1 -> 4 -> 3
Each adjacent pair is swapped.
Example 2:
Input: 1 -> 2 -> 3
Output: 2 -> 1 -> 3
The trailing odd node (value 3) keeps its position.
Example 3:
Input: 1
Output: 1
A single node has nothing to swap.
Constraints
- The number of nodes is in the range \( [0, 100] \).
- \( 0 \le \text{Node.val} \le 100 \).
Problem 10: Intersection of Two Linked Lists
LeetCode: 160. Intersection of Two Linked Lists
Description
Given the heads of two singly linked lists a and b, return the first node at
which they intersect by reference, or null if they never intersect. Intersection
means the two lists share the same tail nodes (same objects, not just equal
values). Aim for \( O(m + n) \) time and \( O(1) \) extra space.
Examples
Example 1:
Input: a = 4 -> 1 -> 8 -> 4 -> 5, b = 5 -> 6 -> 1 -> 8 -> 4 -> 5
the "8 -> 4 -> 5" tail is the same shared nodes
Output: node with value 8
Both lists converge at the shared node holding value 8.
Example 2:
Input: a = 1 -> 9 -> 1 -> 2 -> 4, b = 3 -> 2 -> 4 (shared tail 2 -> 4)
Output: node with value 2
The shared suffix begins at the node holding value 2.
Example 3:
Input: a = 2 -> 6 -> 4, b = 1 -> 5 (no shared nodes)
Output: null
The lists are disjoint, so there is no intersection.
Constraints
- The number of nodes of each list is in the range \( [0, 3 \times 10^4] \).
- \( 1 \le \text{Node.val} \le 10^5 \).
Problem 11: Reorder List
LeetCode: 143. Reorder List
Description
Given the head of a singly linked list \( L_0 \to L_1 \to \dots \to L_n \),
reorder it in place to \( L_0 \to L_n \to L_1 \to L_{n-1} \to \dots \) You may
not modify the values, only the node links. Return the new head.
Examples
Example 1:
Input: 1 -> 2 -> 3 -> 4
Output: 1 -> 4 -> 2 -> 3
Front and back nodes are interleaved inward.
Example 2:
Input: 1 -> 2 -> 3 -> 4 -> 5
Output: 1 -> 5 -> 2 -> 4 -> 3
With odd length the middle node lands last.
Example 3:
Input: 1 -> 2
Output: 1 -> 2
A two-node list is already in reordered form.
Constraints
- The number of nodes is in the range \( [1, 5 \times 10^4] \).
- \( 1 \le \text{Node.val} \le 1000 \).
Problem 12: Add Two Numbers (Most-Significant First)
Description
Two non-negative integers are stored as singly linked lists of decimal digits in
most-significant-digit-first order, so 7 -> 2 -> 4 represents 724. Given the
heads a and b, return their sum as a new most-significant-first digit list
with no leading zeros (except the single node 0 for a zero sum). Each list’s
value fits in a long. This is the Advent-of-Code flavor of LeetCode’s “Add Two
Numbers” with the digits running high-to-low.
Examples
Example 1:
Input: a = 7 -> 2 -> 4, b = 5 -> 6 (724 + 56)
Output: 7 -> 8 -> 0 (780)
Adding from the least-significant ends carries into the tens place.
Example 2:
Input: a = 0, b = 0
Output: 0
A zero sum is the single node 0.
Example 3:
Input: a = 9 -> 9, b = 1 (99 + 1)
Output: 1 -> 0 -> 0 (100)
The carry extends the result by one digit.
Constraints
- Each list has between 1 and 18 digits.
- The leading digit is non-zero unless the number is the single node
0.
Problem 13: Rotate List Right
LeetCode: 61. Rotate List
Description
Given the head of a singly linked list, rotate the list to the right by k
places, so the last k nodes move to the front. k may exceed the list length,
in which case take it modulo the length. Return the new head; an empty list
returns null.
Examples
Example 1:
Input: head = 1 -> 2 -> 3 -> 4 -> 5, k = 2
Output: 4 -> 5 -> 1 -> 2 -> 3
The last two nodes wrap around to the front.
Example 2:
Input: head = 0 -> 1 -> 2, k = 4
Output: 2 -> 0 -> 1
k = 4 modulo length 3 is an effective rotation of 1.
Example 3:
Input: head = 1 -> 2 -> 3, k = 3
Output: 1 -> 2 -> 3
Rotating by the full length restores the original order.
Constraints
- The number of nodes is in the range \( [0, 500] \).
- \( -100 \le \text{Node.val} \le 100 \), and \( 0 \le k \le 2 \times 10^9 \).
Problem 14: Train Car Partition
Description
A freight train is a singly linked list of car weights, and the yard master
picks a pivot weight x. Reorder the cars so every car with weight strictly less
than x precedes every car with weight \ge x, preserving the relative order
within each group. Mutate the links only, not the values. This is the
contest framing of LeetCode’s “Partition List.”
Examples
Example 1:
Input: cars = 1 -> 4 -> 3 -> 2 -> 5 -> 2, x = 3
Output: 1 -> 2 -> 2 -> 4 -> 3 -> 5
Cars lighter than 3 (1,2,2) keep order ahead of the rest (4,3,5).
Example 2:
Input: cars = 2 -> 1, x = 2
Output: 1 -> 2
The lighter car moves ahead of the pivot-weight car.
Example 3:
Input: cars = 1 -> 2 -> 3, x = 0
Output: 1 -> 2 -> 3
No car is lighter than the pivot, so order is unchanged.
Constraints
- The number of cars is in the range \( [0, 200] \).
- \( -100 \le \text{weight} \le 100 \) and \( -200 \le x \le 200 \).
Problem 15: Reverse Nodes in k-Group
LeetCode: 25. Reverse Nodes in k-Group
Description
Given the head of a linked list, reverse the nodes of the list k at a time
and return the modified list. k is a positive integer no larger than the list
length. Nodes left over at the end (fewer than k) stay in their original
order. Change only the links, not the values.
Examples
Example 1:
Input: head = 1 -> 2 -> 3 -> 4 -> 5, k = 2
Output: 2 -> 1 -> 4 -> 3 -> 5
Each full pair is reversed; the lone node 5 stays put.
Example 2:
Input: head = 1 -> 2 -> 3 -> 4 -> 5, k = 3
Output: 3 -> 2 -> 1 -> 4 -> 5
The first three reverse; the trailing two are short of k.
Example 3:
Input: head = 1 -> 2 -> 3 -> 4, k = 4
Output: 4 -> 3 -> 2 -> 1
The whole list is a single group and fully reverses.
Constraints
- The number of nodes is in the range \( [1, 5000] \).
- \( 0 \le \text{Node.val} \le 1000 \) and \( 1 \le k \le n \).
Problem 16: Copy List with Random Pointer
LeetCode: 138. Copy List with Random Pointer
Description
You are given the head of a RandomNode list where each node carries a next
pointer and a random pointer that may point to any node in the list or be
null. Return a deep copy: a brand-new list of RandomNode objects whose next
and random structure mirrors the original, sharing no nodes with it. Aim for
\( O(n) \) time.
Examples
Example 1:
Input: vals = [7, 13, 11, 10, 1], random indices = [null, 0, 4, 2, 0]
Output: a new list with identical values and random targets, distinct objects
Node 13’s random points at node 7 in the copy, just as in the original.
Example 2:
Input: vals = [1, 2], random indices = [1, 1]
Output: a new two-node list where both copies' random point at the copied node 2
Random pointers reference copied nodes, never the originals.
Example 3:
Input: (empty)
Output: (empty)
An empty list copies to an empty list.
Constraints
- The number of nodes is in the range \( [0, 1000] \).
- \( -10^4 \le \text{Node.val} \le 10^4 \);
randomisnullor points into the list.
Problem 17: Remove Duplicates From a Sorted List II
LeetCode: 82. Remove Duplicates from Sorted List II
Description
Given the head of a sorted singly linked list, delete every node that has a
duplicate value, leaving only the values that were originally distinct. Return
the new head, still sorted, in \( O(n) \) time. Framed as submarine depth
readings, 1 -> 2 -> 3 -> 3 -> 4 -> 4 -> 5 compacts to 1 -> 2 -> 5.
Examples
Example 1:
Input: 1 -> 2 -> 3 -> 3 -> 4 -> 4 -> 5
Output: 1 -> 2 -> 5
Both 3s and both 4s are removed entirely.
Example 2:
Input: 1 -> 1 -> 1 -> 2 -> 3
Output: 2 -> 3
The whole run of 1s disappears, including the head.
Example 3:
Input: (empty)
Output: (empty)
An empty list is unchanged.
Constraints
- The number of nodes is in the range \( [0, 300] \).
- \( -100 \le \text{Node.val} \le 100 \), and the list is sorted ascending.
Problem 18: Next Greater Node in Linked List
LeetCode: 1019. Next Greater Node In Linked List
Description
A relay team is a singly linked list of runner speeds. For each runner from front
to back, report the speed of the next runner who is strictly faster, or 0 if no
later runner is faster. Return the answers as an array indexed by 0-based
position. Aim for \( O(n) \) time using a monotonic stack.
Examples
Example 1:
Input: 2 -> 1 -> 5
Output: [5, 5, 0]
The next strictly greater value after 2 and after 1 is 5; 5 has none.
Example 2:
Input: 2 -> 7 -> 4 -> 3 -> 5
Output: [7, 0, 5, 5, 0]
Each entry is the first later value that exceeds it, else 0.
Example 3:
Input: 1 -> 7 -> 5 -> 1 -> 9 -> 2 -> 5 -> 1
Output: [7, 9, 9, 9, 0, 5, 0, 0]
Values with no greater successor map to 0.
Constraints
- The number of nodes is in the range \( [1, 10^4] \).
- \( 1 \le \text{Node.val} \le 10^9 \).
Problem 19: Merge Zero Segments
LeetCode: 2181. Merge Nodes in Between Zeros
Description
A flight recorder is a singly linked list that begins and ends with a 0 node;
between every pair of consecutive zeros lies one console reading’s values, and no
two zeros are adjacent. Merge each maximal segment strictly between two zeros into
a single node holding the sum of its values, then drop the zeros. Return the head
of the compacted list.
Examples
Example 1:
Input: 0 -> 3 -> 1 -> 0 -> 4 -> 5 -> 2 -> 0
Output: 4 -> 11
The first segment sums to 4 and the second to 11.
Example 2:
Input: 0 -> 1 -> 0 -> 3 -> 0 -> 2 -> 2 -> 0
Output: 1 -> 3 -> 4
Each between-zeros segment collapses to its sum.
Example 3:
Input: 0 -> 5 -> 0
Output: 5
A single segment yields a single node.
Constraints
- The number of nodes is in the range \( [3, 2 \times 10^5] \).
- \( 0 \le \text{Node.val} \le 1000 \); the list starts and ends with
0and has no adjacent zeros.
Problem 20: Sort a Linked List
LeetCode: 148. Sort List
Description
Given the head of a singly linked list, return it sorted in ascending order.
Aim for \( O(n \log n) \) time and, ideally, \( O(1) \) auxiliary space using
a bottom-up merge sort over the links.
Examples
Example 1:
Input: 4 -> 2 -> 1 -> 3
Output: 1 -> 2 -> 3 -> 4
The nodes are relinked into ascending order.
Example 2:
Input: -1 -> 5 -> 3 -> 4 -> 0
Output: -1 -> 0 -> 3 -> 4 -> 5
Negative and zero values sort correctly alongside positives.
Example 3:
Input: (empty)
Output: (empty)
An empty list is already sorted.
Constraints
- The number of nodes is in the range \( [0, 5 \times 10^4] \).
- \( -10^5 \le \text{Node.val} \le 10^5 \).
Stacks
What It Is & When to Use It
What problems naturally call for “deal with the most recent thing first,” and what makes a stack the right fit over a list or queue?
LIFO Discipline
What does last-in-first-out mean for the order things leave, and how is that ordering a feature rather than a limitation?
The Mental Model
If you picture a stack of plates, which plate is reachable and which are trapped, and why?
The Single Access Point Invariant
Why does allowing access at exactly one end make the structure easier to reason about and to keep correct?
Core Operations
Which operations form the public contract, and what does each promise to the caller?
push
How do you add an element so it becomes the new top without disturbing what’s below?
pop
How do you remove and return the top, and what must be true before you’re allowed to?
peek / top
How do you read the top without removing it, and why is that distinct from pop?
isEmpty / size
How do you expose state so callers can guard their own pops without touching internals?
State and Invariants
What internal fields must always agree, and what relationship between them must hold after every operation?
The Top Index or Pointer
How does the top marker tell push and pop where to act, and what value marks “empty”?
Invariant Maintenance
After any push or pop, what must still be true about size, the top marker, and the stored elements?
Array-Backed vs Linked-List Backing
What changes about growth, pointers, cache locality, and worst-case spikes when you swap the underlying store?
Array Backing
How does a top index plus a contiguous array realize a stack, and what happens at the boundary when it fills?
Linked-List Backing
Why does pushing onto the head of a singly linked list give a clean O(1) with no resize ever needed?
Choosing Between Them
Which backing wins on memory overhead, which on predictable latency, and why?
Growth and Resizing
When an array stack fills, how do you grow it, and why is doubling the capacity the standard move rather than adding a fixed amount?
Why Doubling Amortizes
How does the cost of an occasional expensive copy get spread thin across many cheap pushes?
Time Complexity
What does each operation cost, and what in the implementation forces that bound?
push
Why is push constant time in the common case, and which case makes a single push expensive?
pop and peek
Why are these always constant time regardless of how full the stack is?
Amortized vs Worst-Case push
What is the difference between the cost of one unlucky push and the average over many, and what triggers the unlucky one?
Constant Factors
Where does array backing tend to beat linked backing in practice even when both are labeled O(1)?
Space Complexity
How much memory does the structure use beyond the elements themselves, and where does it come from?
Storage Overhead by Backing
Why does a linked stack pay per-element pointer overhead while an array stack may hold unused slack capacity?
Auxiliary Space of Operations
Why do push, pop, and peek need no extra space proportional to size?
Call-Stack Space When Used Recursively
If a stack is consumed by a recursive walk, how does the recursion’s own frame depth add to space cost?
Trade-offs vs Alternatives
When would you reach for a stack over a queue, a deque, or a plain dynamic list, and what do you give up?
Common Bugs & Edge Cases
What goes wrong on popping or peeking an empty stack, off-by-one on the top index, forgetting to null freed slots, or resizing at the wrong moment?
Real-World & Interview Uses
Where do call frames, undo, bracket matching, expression evaluation, and DFS rely on this exact shape?
Implementation Walkthrough
Before writing code, break the build into parts and decide each one.
State & Setup
What fields hold the elements, the capacity, and the top marker, and what are their initial values for an empty stack?
push Step by Step
In what order do you check capacity, possibly resize, place the element, and advance the top marker?
pop Step by Step
In what order do you check emptiness, read the top, shrink the marker, and optionally free the slot?
Resize Step
How do you allocate the bigger store and copy elements so the top marker still points correctly afterward?
Termination & Return
What does each operation return, and how do you signal an illegal pop on an empty stack?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Queues
What It Is & When to Use It
What problems need “serve in the order things arrived,” and what makes a queue the right fit over a stack or list?
FIFO Discipline
What does first-in-first-out mean for who gets served next, and why is fairness a natural consequence?
The Mental Model
If you picture a line at a counter, where do arrivals join and where do departures happen?
Two Ends, Two Roles
Why do the back and front play different roles, unlike a stack’s single end?
Core Operations
Which operations make up the contract, and what does each promise?
enqueue
How do you add an element at the back without disturbing the front?
dequeue
How do you remove and return the front, and what must hold before you may?
peek / front
How do you read the front without removing it, and why keep that separate from dequeue?
State and Invariants
What internal fields must always agree for the queue to stay correct?
Front and Rear Markers
How do two indices let you avoid shifting every element on each dequeue?
The Size or Count Field
Why is an explicit count often the cleanest way to keep the structure honest?
Circular Buffer Wraparound
How does modular arithmetic let a slot freed at the front be reused at the back of a fixed array?
Why a Ring Beats a Line
Without wraparound, why does a simple array queue “walk off” the end and waste space?
Distinguishing Full from Empty
Why does front == rear alone fail to tell full from empty, and what two fixes resolve it?
Array-Backed vs Linked-List Backing
What does each backing buy you for growth, wraparound bookkeeping, and pointer overhead?
Array Backing with a Ring
How do head, tail, and count cooperate to keep operations constant time?
Linked-List Backing
Why do head and tail node references give a clean queue with no wraparound math at all?
Growth and Resizing
When a circular array fills, how do you copy into a larger one without scrambling the logical order across the wrap point?
Time Complexity
What does each operation cost, and what forces that bound?
enqueue and dequeue
Why are both constant time once you have front and rear markers, even though elements never shift?
Amortized enqueue on a Growable Queue
What single enqueue becomes expensive, what triggers it, and why does the average stay constant?
peek
Why is reading the front always cheap regardless of length?
Space Complexity
How much memory beyond the elements, and where does it come from?
Slack Capacity vs Pointer Overhead
Why does a ring-buffer queue hold unused slots while a linked queue pays a reference per node?
Auxiliary Space of Operations
Why do the core operations need no extra space proportional to length?
Queue Space in a BFS
When a queue drives a breadth-first search, how large can it grow relative to the input, and what determines its peak?
Trade-offs vs Alternatives
When is a queue the right call over a stack, a deque, or a priority queue, and what do you sacrifice?
Common Bugs & Edge Cases
What breaks on dequeue-from-empty, the full/empty ambiguity, a missed modulo, or a resize that ignores the wrap point?
Real-World & Interview Uses
Where do BFS, task scheduling, buffering, and producer/consumer pipelines depend on this shape?
Implementation Walkthrough
Before writing code, settle each part.
State & Setup
What fields hold the buffer, capacity, front, rear, and count, and what are their empty-queue values?
enqueue Step by Step
In what order do you check fullness, possibly resize, place the element, advance rear with wraparound, and bump count?
dequeue Step by Step
In what order do you check emptiness, read the front, advance front with wraparound, and drop count?
The Wraparound Computation
How exactly does modular arithmetic turn “past the end” back to index zero for both markers?
Resize Step
How do you re-lay the elements in logical order when copying a wrapped buffer into a bigger one?
Termination & Return
What does each operation return, and how do you signal an illegal dequeue on an empty queue?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Deques
Open at Both Ends
What can a deque do that a plain stack or queue cannot, and why is that worth a more complex structure?
When to Use It
What problems want fast insertion and removal at both ends, and how do you recognize them?
The Mental Model
If you picture a double-ended line where people join and leave from either side, what stays symmetric and what does not?
Four Core Operations
What are the push/pop pairs at each end, and how do they mirror one another?
pushFront / pushBack
How do you add at the head versus the tail, and what marker moves in each case?
popFront / popBack
How do you remove at each end, and what must you check before each removal?
peekFront / peekBack
How do you read either end without removing, and why keep that separate?
State and Invariants
What internal fields must always agree, and what must hold about head, tail, and count after every operation?
Head and Tail Markers
How do two indices, moving in opposite directions, track both ends of the data?
The Count Field
Why does an explicit element count make the full and empty conditions unambiguous?
Circular Array Wraparound
How do head and tail indices move in opposite directions around a ring?
Wraparound Indexing
How does modular arithmetic keep both indices in bounds as they cross zero in either direction?
Distinguishing Full from Empty
Why can’t head and tail alone tell full from empty here, and how does a count resolve it?
Doubly-Linked Alternative
When is a node-based deque, with prev and next pointers, preferable to an array-based one?
Why Both Pointers Are Needed
What would break at popBack if each node only knew its successor and not its predecessor?
As a Stack and as a Queue
How does a deque subsume both by ignoring one end, and what restriction recovers each?
Time Complexity
What does each end operation cost, and what makes the bound hold?
The Four End Operations
Why are all four push/pop-at-an-end operations constant time once head, tail, and count exist?
Amortized Cost on a Growable Array Deque
Which operation occasionally triggers a resize, and why does the per-operation average stay constant anyway?
Array vs Linked Constant Factors
Where does the array deque’s contiguous memory beat the linked deque even when both are O(1)?
Space Complexity
How much memory beyond the elements, and where does it come from?
Slack vs Pointer Overhead
Why does the ring-buffer deque carry unused capacity while the linked deque pays two pointers per node?
Auxiliary Space of Operations
Why do the end operations need no scratch space proportional to size?
Sliding-Window Use
How does a monotonic deque maintain a window maximum in O(n) total work?
Why Each Index Enters and Leaves Once
What argument explains the linear total cost even though a single step can pop many elements?
Trade-offs vs Alternatives
When does the extra generality of a deque cost you versus a dedicated stack or queue?
Common Bugs & Edge Cases
What goes wrong with index underflow on popFront, full/empty ambiguity, a missed modulo in either direction, or stale window indices?
Real-World & Interview Uses
Where do work-stealing schedulers, undo/redo, palindrome checks, and sliding-window problems use deques?
Implementation Walkthrough
Before writing code, settle each part.
State & Setup
What fields hold the buffer, capacity, head, tail, and count, and what are their empty-deque values?
pushFront and pushBack Step by Step
In what order do you check fullness, possibly resize, place the element, and move the correct marker in the correct direction?
popFront and popBack Step by Step
In what order do you check emptiness, read the end, move the marker, and update count?
Wraparound in Both Directions
How do you compute the next index when advancing forward versus stepping backward past zero?
Resize Step
How do you copy a possibly-wrapped buffer into a larger one while preserving front-to-back order?
Termination & Return
What does each operation return, and how do you signal an illegal pop on an empty deque?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Monotonic Stack
What It Is & When to Use It
What class of problems — “for each element, find the nearest larger or smaller one” — collapses from \( O(n^2) \) to \( O(n) \) once you keep a stack in sorted order, and what signals that a problem is secretly a monotonic-stack problem?
How the Ordered Stack Works
As you scan the array, what does the stack hold at any moment, and why does keeping it strictly ordered let each new element instantly “answer” the elements it displaces?
The Mental Model of a Staircase
How do you picture the stack as a rising or falling staircase, and what does it mean physically when an incoming element knocks several steps off the top before settling?
The Push-While-Pop Invariant
Before pushing the new element, why do you pop everything that violates the order, and what stays true about the remaining stack after every step?
Increasing vs. Decreasing
How does the direction of monotonicity decide whether you are answering “next greater” versus “next smaller,” and how do left-to-right versus right-to-left scans change which neighbor (next vs. previous) you recover?
Storing Indices vs. Values
Why is it usually better to push indices rather than raw values, and what extra information (distance, span, width) does an index give you that a bare value cannot?
The Next Greater / Next Smaller Pattern
When you finally pop an element, why is the element currently being processed exactly its answer — and what do the indices left on the stack at the end (never popped) tell you about elements that have no greater or smaller neighbor?
Strict vs. Non-Strict Comparison on Ties
How does choosing < versus <= when popping decide whether equal elements count as “greater-or-equal” neighbors, and why must this match the problem’s definition exactly?
Span & Histogram Applications
How do “stock span” and “daily temperatures” reduce to a previous/next comparison, and in the Largest Rectangle in a Histogram, why does the moment you pop a bar reveal the widest rectangle that bar can be the minimum of?
Sentinels
Why does pushing a zero-height bar at the end (and/or a boundary at the start) flush the stack cleanly and remove an annoying “drain the leftovers” edge case?
Time Complexity
Why is the whole scan \( O(n) \) even though a single incoming element can trigger many pops in one step?
Best Case
When does the stack barely move — an already-monotonic input — and why is the per-element work still just a push and one comparison?
Worst & Average Case
Each index is pushed exactly once and popped at most once — why does that cap the total number of pops across the entire run at \( n \), making the amortized cost per element \( O(1) \) and the whole algorithm \( \Theta(n) \)?
Space Complexity
Why is the extra space \( O(n) \) in the worst case (a fully monotonic input that never pops), and what input shape forces the stack to hold every element at once?
Trade-offs vs. Brute Force & Other Structures
When is the naive \( O(n^2) \) double loop actually fine, and when would a different structure (segment tree, sparse table) be preferred over a monotonic stack?
Common Bugs & Edge Cases
Where do bugs creep in — wrong strict-vs-nonstrict comparison on ties, pushing values instead of indices, forgetting to drain the stack at the end, or mishandling an empty or single-element array?
Implementation Walkthrough
Break the technique into the parts you must get right before you write a line.
Setup
What do you initialize the stack and the answer array to, and how do you represent “no neighbor found”?
The Scan Loop
As you iterate, what is the pop condition that compares the incoming element to the stack top, and after popping, what do you record and then push?
The Tricky Step
At the instant you pop, how do you use both the popped index and the current index (and the new top) to compute the answer — the distance, the width, or the neighbor?
Termination & Draining
When the scan ends, what do leftover stack entries mean, and how do sentinels or a final pass assign their answers?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Stacks & Queues: Problem Set
Each problem is an implementation task: fill in the stub in problemset/ and make
its test in tests/problemset/ pass. The problems are split into two groups.
Foundational problems drill the core LIFO/FIFO mechanics — bracket matching,
expression evaluation, and building one structure out of another. Applied
Problems weave LeetCode and contest-style challenges in roughly increasing
difficulty: monotonic stacks, parsing, scheduling, and queue simulation. Work
them in order; later problems lean on patterns introduced earlier.
Foundational
Problem 1: Valid Parentheses
LeetCode: 20. Valid Parentheses
Description
Given a string s containing only the characters '(', ')', '{', '}',
'[' and ']', determine whether the input string is valid. A string is valid
when every open bracket is closed by a bracket of the same type, brackets close
in the correct order, and every closing bracket has a corresponding open bracket
of the same type. Solve it in O(n) time using a stack.
Examples
Example 1:
Input: s = "()[]{}"
Output: true
Every opening bracket is immediately matched by the correct closing bracket.
Example 2:
Input: s = "([)]"
Output: false
The brackets are not closed in the correct order: ) arrives while [ is still open.
Example 3:
Input: s = "{[]}"
Output: true
Brackets are correctly nested.
Constraints
1 <= s.length <= 10^4sconsists only of the characters()[]{}.
Problem 2: Evaluate Reverse Polish Notation
LeetCode: 150. Evaluate Reverse Polish Notation
Description
You are given an array of strings tokens that represents an arithmetic
expression in Reverse Polish (postfix) Notation. Evaluate the expression and
return the resulting integer. The valid operators are +, -, *, and /.
Each operand may be an integer or another expression. Division between two
integers truncates toward zero. The input is always a valid expression. Use a
stack to push operands and apply each operator to the two most recent operands.
Examples
Example 1:
Input: tokens = ["2","1","+","3","*"]
Output: 9
This evaluates to ((2 + 1) * 3) = 9.
Example 2:
Input: tokens = ["4","13","5","/","+"]
Output: 6
This evaluates to (4 + (13 / 5)) = 4 + 2 = 6.
Example 3:
Input: tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
Output: 22
Constraints
1 <= tokens.length <= 10^4tokens[i]is either an operator+,-,*,/, or an integer in the range[-200, 200].
Problem 3: Min Stack
LeetCode: 155. Min Stack
Description
Design a stack that supports push, pop, top, and retrieving the minimum
element, all in constant time. Implement the Problem3 class with:
Problem3()initializes an empty stack.void push(int val)pushesvalonto the stack.void pop()removes the element on the top of the stack.int top()gets the top element of the stack.int getMin()retrieves the minimum element in the stack.
Each operation must run in O(1) time. The trick is to maintain an auxiliary
stack that tracks the running minimum alongside the value stack.
Examples
Example 1:
Input: push(-2), push(0), push(-3), getMin(), pop(), top(), getMin()
Output: -3, then 0, then -2
After pushing -2, 0, -3, the minimum is -3. After one pop, the top is 0
and the minimum is -2.
Example 2:
Input: push(1), push(1), push(0), getMin(), pop(), getMin()
Output: 0, then 1
Duplicate minimums must be tracked so that popping one 0 still leaves the
minimum correct.
Example 3:
Input: push(7), top(), getMin()
Output: 7, then 7
Constraints
-2^31 <= val <= 2^31 - 1pop,top, andgetMinare only called on non-empty stacks.- At most
3 * 10^4calls are made in total.
Problem 4: Implement Queue using Stacks
LeetCode: 232. Implement Queue using Stacks
Description
Implement a first-in-first-out (FIFO) queue using only two stacks. The queue
should support all the standard operations: push, peek, pop, and empty.
Implement the Problem4 class with:
void push(int x)pushes elementxto the back of the queue.int pop()removes the element from the front of the queue and returns it.int peek()returns the element at the front of the queue.boolean empty()returnstrueif the queue is empty.
You may only use standard stack operations (push to top, peek/pop from top,
size, and is-empty). Amortized cost per operation should be O(1).
Examples
Example 1:
Input: push(1), push(2), peek(), pop(), empty()
Output: 1, then 1, then false
peek() and the first pop() both return 1 (FIFO order). The queue still
holds 2, so empty() is false.
Example 2:
Input: push(5), pop(), empty()
Output: 5, then true
Example 3:
Input: push(3), push(4), pop(), push(5), pop(), pop(), empty()
Output: 3, then 4, then 5, then true
Constraints
1 <= x <= 9- At most
100calls are made topush,pop,peek, andempty. - All calls to
popandpeekare valid (the queue is non-empty).
Problem 5: Implement Stack using Queues
LeetCode: 225. Implement Stack using Queues
Description
Implement a last-in-first-out (LIFO) stack using only one or two queues. The
stack should support all the standard operations: push, top, pop, and
empty. Implement the Problem5 class with:
void push(int x)pushes elementxonto the stack.int pop()removes the element on the top of the stack and returns it.int top()returns the element on the top of the stack.boolean empty()returnstrueif the stack is empty.
You may only use standard queue operations (push to back, peek/pop from front, size, and is-empty).
Examples
Example 1:
Input: push(1), push(2), top(), pop(), empty()
Output: 2, then 2, then false
top() and the first pop() both return 2 (LIFO order). The stack still
holds 1, so empty() is false.
Example 2:
Input: push(8), pop(), empty()
Output: 8, then true
Example 3:
Input: push(1), push(2), push(3), pop(), top()
Output: 3, then 2
Constraints
1 <= x <= 9- At most
100calls are made topush,pop,top, andempty. - All calls to
popandtopare valid (the stack is non-empty).
Problem 6: Sliding Window Maximum
LeetCode: 239. Sliding Window Maximum
Description
You are given an array of integers nums and a window size k. A window of
size k slides from the very left of the array to the very right. At each step
you can see only the k numbers inside the window, and the window moves right by
one position. Return an array of the maximum value in each window. Use a
monotonic deque to achieve O(n) total time, evicting indices that fall out of
the window from the front and indices of smaller values from the back.
Examples
Example 1:
Input: nums = [1,3,-1,-3,5,3,6,7], k = 3
Output: [3,3,5,5,6,7]
The windows are [1,3,-1], [3,-1,-3], [-1,-3,5], [-3,5,3], [5,3,6],
[3,6,7], with maximums 3, 3, 5, 5, 6, 7.
Example 2:
Input: nums = [1], k = 1
Output: [1]
Example 3:
Input: nums = [9,8,7,6,5], k = 2
Output: [9,8,7,6]
Constraints
1 <= nums.length <= 10^5-10^4 <= nums[i] <= 10^41 <= k <= nums.length
Applied Problems
Problem 7: Baseball Game
LeetCode: 682. Baseball Game
Description
You are keeping the scores for a game with a series of operations given as an
array of strings operations. Maintain a record (a stack) of valid scores and
apply each operation:
- An integer
x: record a new score ofx. "+": record a new score that is the sum of the previous two scores."D": record a new score that is double the previous score."C": invalidate and remove the previous score from the record.
It is guaranteed that every operation is valid when it is applied. Return the sum of all the scores remaining in the record after applying every operation.
Examples
Example 1:
Input: operations = ["5","2","C","D","+"]
Output: 30
Record 5, record 2, cancel 2 (record [5]), double to 10 (record
[5,10]), sum to 15 (record [5,10,15]). Total = 5 + 10 + 15 = 30.
Example 2:
Input: operations = ["5","-2","4","C","D","9","+","+"]
Output: 27
The record ends as [5, -2, 9, 18, 27], summing to 27.
Example 3:
Input: operations = ["1","C"]
Output: 0
Constraints
1 <= operations.length <= 1000operations[i]is"C","D","+", or a string representing an integer in[-3 * 10^4, 3 * 10^4].- For
"+"operations, there are always at least two previous scores. - For
"C"and"D"operations, there is always at least one previous score.
Problem 8: Backspace String Compare
LeetCode: 844. Backspace String Compare
Description
Given two strings s and t, return true if they are equal when both are
typed into empty text editors. The character '#' means a backspace character,
which deletes the character before it (if any). Note that backspacing on an
empty editor leaves it empty. Build each resulting string with a stack of
characters and compare the results.
Examples
Example 1:
Input: s = "ab#c", t = "ad#c"
Output: true
Both s and t become "ac" after applying backspaces.
Example 2:
Input: s = "a##c", t = "#a#c"
Output: true
Both become "c".
Example 3:
Input: s = "a#c", t = "b"
Output: false
s becomes "c" while t stays "b".
Constraints
1 <= s.length, t.length <= 200sandtcontain only lowercase letters and'#'characters.
Problem 9: Remove All Adjacent Duplicates In String
LeetCode: 1047. Remove All Adjacent Duplicates In String
Description
You are given a string s consisting of lowercase English letters. A duplicate
removal consists of choosing two adjacent and equal letters and removing them.
Repeatedly make duplicate removals on s until no more can be made. Return the
final string after all such removals; the answer is guaranteed to be unique. A
stack makes each removal O(1): push each character, but if it equals the top
of the stack, pop instead.
Examples
Example 1:
Input: s = "abbaca"
Output: "ca"
Remove "bb" to get "aaca", then remove "aa" to get "ca".
Example 2:
Input: s = "azxxzy"
Output: "ay"
Remove "xx" to get "azzy", then remove "zz" to get "ay".
Example 3:
Input: s = "aaaaaa"
Output: ""
Constraints
1 <= s.length <= 10^5sconsists of lowercase English letters.
Problem 10: Make The String Great
LeetCode: 1544. Make The String Great
Description
Given a string s of lowercase and uppercase English letters, a good string is
one with no two adjacent characters s[i] and s[i + 1] where one is the
lowercase version and the other is the uppercase version of the same letter
(for example, "aA" or "Aa"). To make the string good, repeatedly remove such
adjacent bad pairs until none remain. Return the resulting good string; the
answer is unique. Use a stack: push each character unless it forms a bad pair
with the current top, in which case pop instead.
Examples
Example 1:
Input: s = "leEeetcode"
Output: "leetcode"
Removing "eE" (or "Ee") leaves "leetcode", which has no bad pairs.
Example 2:
Input: s = "abBAcC"
Output: ""
"bB" is removed leaving "aAcC", then "aA" and "cC" are removed, leaving "".
Example 3:
Input: s = "s"
Output: "s"
Constraints
1 <= s.length <= 100scontains only lowercase and uppercase English letters.
Problem 11: Next Greater Element I
LeetCode: 496. Next Greater Element I
Description
The next greater element of some element x in an array is the first greater
element that is to the right of x in the same array. You are given two
distinct integer arrays nums1 and nums2, where nums1 is a subset of
nums2. For each nums1[i], find the index of nums1[i] in nums2 and
determine its next greater element in nums2. Return an array ans of the same
length as nums1 where ans[i] is the next greater element described above, or
-1 if it does not exist. A monotonic decreasing stack over nums2 lets you
precompute every next greater element in O(n).
Examples
Example 1:
Input: nums1 = [4,1,2], nums2 = [1,3,4,2]
Output: [-1,3,-1]
For 4, there is no greater element to its right, so -1. For 1, the next
greater is 3. For 2, there is no greater element to its right, so -1.
Example 2:
Input: nums1 = [2,4], nums2 = [1,2,3,4]
Output: [3,-1]
The next greater element of 2 is 3; 4 has none.
Example 3:
Input: nums1 = [1], nums2 = [1]
Output: [-1]
Constraints
1 <= nums1.length <= nums2.length <= 10000 <= nums1[i], nums2[i] <= 10^4- All integers in
nums1andnums2are unique, andnums1is a subset ofnums2.
Problem 12: Daily Temperatures
LeetCode: 739. Daily Temperatures
Description
Given an array of integers temperatures representing the daily temperatures,
return an array answer such that answer[i] is the number of days you have to
wait after the i-th day to get a warmer temperature. If there is no future day
for which this is possible, keep answer[i] == 0. Use a monotonic decreasing
stack of indices: when a warmer day arrives, pop every cooler day and record the
gap, giving O(n) total time.
Examples
Example 1:
Input: temperatures = [73,74,75,71,69,72,76,73]
Output: [1,1,4,2,1,1,0,0]
Day 0 (73) waits 1 day for 74; day 2 (75) waits 4 days for 76; the last two
days have no warmer future day.
Example 2:
Input: temperatures = [30,40,50,60]
Output: [1,1,1,0]
Example 3:
Input: temperatures = [30,60,90]
Output: [1,1,0]
Constraints
1 <= temperatures.length <= 10^530 <= temperatures[i] <= 100
Problem 13: Next Greater Element II
LeetCode: 503. Next Greater Element II
Description
Given a circular integer array nums (the next element of nums[nums.length-1]
is nums[0]), return the next greater number for every element. The next
greater number of an element x is the first greater number traversing the
array in circular order; if it does not exist, the answer is -1. Process the
array twice with a monotonic decreasing stack of indices, using i % n to wrap
around, so the whole thing runs in O(n).
Examples
Example 1:
Input: nums = [1,2,1]
Output: [2,-1,2]
The first 1’s next greater is 2; 2 has none; the last 1 wraps around to
find 2.
Example 2:
Input: nums = [1,2,3,4,3]
Output: [2,3,4,-1,4]
Example 3:
Input: nums = [5,4,3,2,1]
Output: [-1,5,5,5,5]
Constraints
1 <= nums.length <= 10^4-10^9 <= nums[i] <= 10^9
Problem 14: Simplify Path
LeetCode: 71. Simplify Path
Description
You are given an absolute path for a Unix-style file system, which always begins
with a slash '/'. Transform this absolute path into its simplified canonical
path. In a Unix file system, a single period '.' refers to the current
directory, a double period '..' refers to the parent directory, and any
number of consecutive slashes is treated as a single slash. The canonical path
must start with a single '/', have directories separated by exactly one '/',
not end with a trailing '/', and contain no '.' or '..' components. Split
on slashes and use a stack of directory names, popping on each '..'.
Examples
Example 1:
Input: path = "/home//foo/"
Output: "/home/foo"
Multiple slashes collapse into one and the trailing slash is dropped.
Example 2:
Input: path = "/a/./b/../../c/"
Output: "/c"
. is ignored; each .. pops the previous directory, leaving only c.
Example 3:
Input: path = "/../"
Output: "/"
Going above the root directory stays at the root.
Constraints
1 <= path.length <= 3000pathconsists of English letters, digits, periods'.', slashes'/', and underscores'_'.pathis a valid absolute Unix path.
Problem 15: Asteroid Collision
LeetCode: 735. Asteroid Collision
Description
We are given an array asteroids of integers representing asteroids in a row.
For each asteroid, the absolute value represents its size, and the sign
represents its direction (positive meaning right, negative meaning left). Each
asteroid moves at the same speed. Find out the state of the asteroids after all
collisions. If two asteroids meet, the smaller one explodes; if both are the
same size, both explode. Two asteroids moving in the same direction will never
meet. A stack models the rightward-moving survivors: a leftward asteroid
collides with the top of the stack until it is destroyed or survives.
Examples
Example 1:
Input: asteroids = [5,10,-5]
Output: [5,10]
The 10 and -5 collide, and the -5 explodes. The 10 and 5 never meet
because they move in the same direction.
Example 2:
Input: asteroids = [8,-8]
Output: []
They are the same size, so both explode.
Example 3:
Input: asteroids = [10,2,-5]
Output: [10]
The -5 destroys the 2, then collides with the 10 and explodes.
Constraints
2 <= asteroids.length <= 10^4-1000 <= asteroids[i] <= 1000asteroids[i] != 0
Problem 16: Number of Recent Calls
LeetCode: 933. Number of Recent Calls
Description
You have a RecentCounter class which counts the number of recent requests
within a certain time frame. Implement the Problem16 class:
Problem16()initializes the counter with zero recent requests.int ping(int t)adds a new request at timet(in milliseconds) and returns the number of requests that have happened in the inclusive range[t - 3000, t].
It is guaranteed that every call to ping uses a strictly larger value of t
than the previous call. A FIFO queue of timestamps lets you drop everything
older than t - 3000 from the front and return the queue size.
Examples
Example 1:
Input: ping(1), ping(100), ping(3001), ping(3002)
Output: 1, 2, 3, 3
At 3001, the request at time 1 is exactly 3000 ms old and still counts. At
3002, the request at time 1 (3001 ms old) is evicted, leaving 3.
Example 2:
Input: ping(1000)
Output: 1
Example 3:
Input: ping(1), ping(2), ping(3)
Output: 1, 2, 3
Constraints
1 <= t <= 10^9- Each test case calls
pingwith strictly increasing values oft. - At most
10^4calls are made toping.
Problem 17: Validate Stack Sequences
LeetCode: 946. Validate Stack Sequences
Description
Given two integer arrays pushed and popped, each with distinct values,
return true if and only if this could have been the result of a sequence of
push and pop operations on an initially empty stack. Simulate the process: push
each value from pushed in order, and whenever the top of the stack equals the
next value to pop, pop it. The sequence is valid if the stack is empty at the
end.
Examples
Example 1:
Input: pushed = [1,2,3,4,5], popped = [4,5,3,2,1]
Output: true
One valid order: push 1,2,3,4, pop 4, push 5, pop 5,3,2,1.
Example 2:
Input: pushed = [1,2,3,4,5], popped = [4,3,5,1,2]
Output: false
After popping 4 and 3, the value 1 cannot be popped before 2, so this is impossible.
Example 3:
Input: pushed = [1,2], popped = [2,1]
Output: true
Constraints
1 <= pushed.length <= 10000 <= pushed[i] <= 1000- All the elements of
pushedare unique. popped.length == pushed.lengthpoppedis a permutation ofpushed.
Problem 18: Decode String
LeetCode: 394. Decode String
Description
Given an encoded string, return its decoded string. The encoding rule is
k[encoded_string], where the encoded_string inside the square brackets is
repeated exactly k times. You may assume the input is always valid, with no
extra white spaces and well-formed brackets. The digits only indicate repeat
counts and never appear inside the decoded text. Use two stacks (one for counts,
one for partial strings) so that opening a bracket pushes context and closing a
bracket repeats and merges.
Examples
Example 1:
Input: s = "3[a]2[bc]"
Output: "aaabcbc"
3[a] expands to "aaa" and 2[bc] expands to "bcbc".
Example 2:
Input: s = "3[a2[c]]"
Output: "accaccacc"
The inner 2[c] becomes "cc", so a2[c] is "acc", repeated 3 times.
Example 3:
Input: s = "2[abc]3[cd]ef"
Output: "abcabccdcdcdef"
Constraints
1 <= s.length <= 30sconsists of lowercase English letters, digits, and square brackets'[]'.sis guaranteed to be a valid input.- All integers in
sare in the range[1, 300].
Problem 19: Remove K Digits
LeetCode: 402. Remove K Digits
Description
Given a string num representing a non-negative integer and an integer k,
return the smallest possible integer (as a string) after removing exactly k
digits from num. Use a monotonic increasing stack: while you still have
removals left and the current digit is smaller than the top of the stack, pop.
Finally, strip any leading zeros from the result, and return "0" if the result
is empty.
Examples
Example 1:
Input: num = "1432219", k = 3
Output: "1219"
Removing the digits 4, 3, and 2 leaves the smallest number 1219.
Example 2:
Input: num = "10200", k = 1
Output: "200"
Removing the leading 1 gives 0200, which becomes 200 after stripping the leading zero.
Example 3:
Input: num = "10", k = 2
Output: "0"
Removing both digits leaves nothing, so the answer is "0".
Constraints
1 <= k <= num.length <= 10^5numconsists of only digits.numdoes not have any leading zeros except for the zero itself.
Problem 20: Online Stock Span
LeetCode: 901. Online Stock Span
Description
Design an algorithm that collects daily price quotes for a stock and returns the
span of that stock’s price for the current day. The span of the stock’s price on
a given day is the maximum number of consecutive days (starting from that day and
going backward) for which the price was less than or equal to the price of that
day. Implement the Problem20 class:
Problem20()initializes the object.int next(int price)returns the span of the stock’s price given today’sprice.
A monotonic decreasing stack of (price, span) pairs lets each next call run
in amortized O(1).
Examples
Example 1:
Input: next(100), next(80), next(60), next(70), next(60), next(75), next(85)
Output: 1, 1, 1, 2, 1, 4, 6
At price 75, the prior days 60, 70, 60 are all <= 75, giving a span of 4.
At 85, the six most recent days are all <= 85, giving a span of 6.
Example 2:
Input: next(10), next(20), next(30)
Output: 1, 2, 3
Each price is at least the previous, so the span keeps growing.
Example 3:
Input: next(50), next(40), next(30)
Output: 1, 1, 1
Constraints
1 <= price <= 10^5- At most
10^4calls are made tonext.
Problem 21: Score of Parentheses
LeetCode: 856. Score of Parentheses
Description
Given a balanced parentheses string s, return the score of the string based on
the following rules: "()" has a score of 1; AB has a score of A + B
where A and B are balanced parentheses strings; and (A) has a score of
2 * A where A is a balanced parentheses string. Use a stack that holds the
running score of each nesting level: ( pushes a new level, ) pops and folds
the inner score into the level below.
Examples
Example 1:
Input: s = "()"
Output: 1
A single matched pair scores 1.
Example 2:
Input: s = "(())"
Output: 2
The inner () scores 1, and wrapping it in parentheses doubles it to 2.
Example 3:
Input: s = "()()"
Output: 2
Two adjacent pairs each score 1, summing to 2.
Constraints
2 <= s.length <= 50sconsists of only'('and')'.sis a balanced parentheses string.
Problem 22: Sum of Subarray Minimums
LeetCode: 907. Sum of Subarray Minimums
Description
Given an array of integers arr, find the sum of min(b) over every
(contiguous) subarray b of arr. Because the answer may be large, return it
modulo 10^9 + 7. The efficient O(n) approach uses a monotonic stack to find,
for each element, how many subarrays it is the minimum of — namely the span to
the nearest strictly smaller element on the left and the nearest smaller-or-equal
element on the right.
Examples
Example 1:
Input: arr = [3,1,2,4]
Output: 17
The subarray minimums are 3, 1, 2, 4, 1, 1, 2, 1, 1, 1, which sum to 17.
Example 2:
Input: arr = [11,81,94,43,3]
Output: 444
Example 3:
Input: arr = [1]
Output: 1
Constraints
1 <= arr.length <= 3 * 10^41 <= arr[i] <= 3 * 10^4
Problem 23: Largest Rectangle in Histogram
LeetCode: 84. Largest Rectangle in Histogram
Description
Given an array of integers heights representing the histogram’s bar heights
where the width of each bar is 1, return the area of the largest rectangle in
the histogram. Maintain a monotonic increasing stack of bar indices: when a bar
shorter than the stack top appears, pop and compute the area of the rectangle
that used the popped bar as its limiting height, with the width spanning to the
new boundaries. This yields an O(n) solution.
Examples
Example 1:
Input: heights = [2,1,5,6,2,3]
Output: 10
The largest rectangle spans bars of heights 5 and 6, giving area 5 * 2 = 10.
Example 2:
Input: heights = [2,4]
Output: 4
The rectangle of height 4 and width 1, or height 2 and width 2, both give 4.
Example 3:
Input: heights = [1,1,1,1]
Output: 4
Constraints
1 <= heights.length <= 10^50 <= heights[i] <= 10^4
Problem 24: Basic Calculator
LeetCode: 224. Basic Calculator
Description
Given a string s representing a valid expression, implement a basic calculator
to evaluate it and return the result. The expression may contain non-negative
integers, the binary '+' and '-' operators, the unary '-' (negation), and
parentheses '(' and ')', possibly with spaces. You are not allowed to use
any built-in function that evaluates strings as expressions. Use a stack to save
the running result and sign whenever a '(' is opened, restoring them when the
matching ')' is reached.
Examples
Example 1:
Input: s = "1 + 1"
Output: 2
Simple addition.
Example 2:
Input: s = "(1+(4+5+2)-3)+(6+8)"
Output: 23
The inner groups evaluate to 1 + 11 - 3 = 9 and 14, summing to 23.
Example 3:
Input: s = " 2-1 + 2 "
Output: 3
Constraints
1 <= s.length <= 3 * 10^5sconsists of digits,'+','-','(',')', and spaces' '.srepresents a valid expression.- The
'+'and'-'operators always have a valid operand;'-'can also be unary. - Every
')'has a matching'(', and the integer values fit in a 32-bit signed integer.
Recursion Deep
What Recursion Is & When to Reach for It
What kind of problem is naturally “the same problem on smaller input,” and when is recursion clearer than a loop?
Base Case and Recursive Case
What stops the recursion, and what shrinks the problem toward that stop?
Why Every Path Must Reach a Base Case
What guarantees that repeated shrinking actually lands on the base case rather than running forever?
The Two-Part Mental Model
How do you separate “what the smallest case answers” from “how a bigger case builds on a smaller one”?
Trusting the Recursion
Why can you assume the recursive call already works on the smaller input while you write the larger case?
Translating a Recursive Idea to Code
How do you turn “solve smaller, then combine” into a method that calls itself?
The Recursive Leap of Faith in Practice
Where in the method do you make the call, and where do you use its result?
The Call Stack and Frames
What gets pushed on each call, and where do parameters and locals live during the call?
What a Single Frame Holds
What information must a frame keep so it can resume after its recursive call returns?
Unwinding the Stack
What happens to each frame’s pending work as control returns up the chain?
Work on the Way Down vs the Way Up
When does computation happen before the recursive call versus after it returns, and how does that change the result order?
Tail vs Non-Tail Recursion
When is the recursive call the last thing a frame does, and why does that distinction matter for the stack?
Why Tail Calls Can Be Loop-Like
What about a tail call means the current frame has nothing left to do, and what could an optimizer exploit?
Multiple and Tree Recursion
What changes about shape and cost when one call spawns two or more recursive calls?
Redundant Work and Memoization
How does caching results stop recomputing the same subproblem, and how does that collapse the cost?
Recursion to Iteration
How would an explicit stack mimic what the runtime does for you automatically?
What You Have to Track by Hand
Which pieces of frame state must you push manually that the language used to manage for you?
Time Complexity
How do you reason about the cost from the call structure rather than deriving a recurrence?
Counting Calls Times Work per Call
Why is total cost roughly the number of calls multiplied by the non-recursive work each does?
Linear vs Exponential Branching
Why does single recursion tend toward linear cost while unmemoized branching can explode exponentially?
How Memoization Changes the Count
Why does caching cut the cost to roughly the number of distinct subproblems?
Space Complexity
Where does recursion spend memory beyond what an equivalent loop would?
Call-Stack Depth Dominates
Why is the peak stack depth, not the total number of calls, the figure that drives space?
Depth by Recursion Shape
Why does linear recursion cost depth proportional to n while balanced branching costs only depth proportional to its height?
Iterative Versions and Their Own Stack
When you convert to a loop with an explicit stack, where does the space actually go, and is it really saved?
Stack Depth and Overflow
What input depth blows past the available frames and crashes, and how do you bound or restructure it?
Common Bugs & Edge Cases
What goes wrong with a missing base case, a step that doesn’t shrink, off-by-one on the boundary, or shared mutable state across calls?
Real-World & Interview Uses
Where do traversals, backtracking, parsing, and divide-and-conquer lean on recursion?
Implementation Walkthrough
Before writing code, decide each part.
Signature & Setup
What parameters carry the shrinking input and any accumulated result, and what does the caller pass first?
The Base Case Branch
Which condition do you test first, and what do you return immediately without recursing?
The Recursive Branch
How do you build a strictly smaller argument, make the call, and use its return value?
Combining Sub-Results
Where does the work that turns the smaller answer into the bigger one belong, before or after the call?
Termination & Return
What does the top-level call ultimately hand back, and how do you confirm every path returns?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Divide and Conquer
What the Paradigm Is & When It Applies
What signals that a problem can be split, solved in pieces, and merged, and when does that beat a direct approach?
Divide, Conquer, Combine
What are the three phases, and which one usually dominates the cost?
Divide
How do you split the input into smaller, same-shaped subproblems, and what makes a split “clean”?
Conquer
How does recursion solve each piece, and where does the base case end the descent?
Combine
How do you merge the sub-solutions into a full answer, and why is this often the hard part?
Subproblem Independence
Why must the pieces not overlap for this paradigm, rather than dynamic programming, to fit?
Contrast with Overlapping Subproblems
What does it look like when subproblems repeat, and why does that push you toward memoization instead?
The Mental Model
How do you picture the recursion as a tree, with the original problem at the root and base cases at the leaves?
Where Work Happens in the Tree
At which nodes does the divide cost land, and at which does the combine cost land?
Mapping the Structure to Code
How does the divide/conquer/combine shape become a recursive method skeleton?
The Base Case First
Why do you test for the smallest solvable size before attempting to split?
A Worked Example: Merge Sort
How do split-in-half, sort-each, and merge realize each phase?
The Merge Step
How do two sorted halves combine into one sorted whole in a single linear pass?
Why Merge Sort Is Stable and Predictable
What about the merge keeps equal elements in order and avoids a bad worst case?
Another Lens: Binary Search
How is halving the search space a degenerate divide-and-conquer with one recursive call and no combine?
Where the Cost Comes From
Which dominates: the splitting, the recursive calls, or the combining, and how does that decide the total?
Time Complexity
How do you reason about the running time without formally solving a recurrence?
Counting Work per Level
Why is the total often “work per level times number of levels,” and what sets each factor?
When Combine Is the Bottleneck
Why does a linear combine over logarithmically many levels give the classic n log n shape?
When Branching Is the Bottleneck
Why can many subproblems per level make the leaf count, not the combine, dominate the cost?
Best, Average, and Worst Triggers
What kinds of splits keep the balanced bound, and what unbalanced split degrades it?
Space Complexity
Where does divide-and-conquer spend memory, and how much?
Recursion-Stack Depth
Why does the depth of the recursion tree, not its total node count, set the stack space?
Auxiliary Buffers in Combine
Why does a step like merge need scratch space, and how big is it relative to the input?
In-Place vs Out-of-Place Variants
What do you trade in code complexity to shrink the auxiliary space toward constant?
Trade-offs vs Alternatives
When does divide-and-conquer beat a straightforward linear scan or a DP table, and when is its overhead not worth it?
Common Bugs & Edge Cases
What breaks on uneven splits, a wrong or missing base case, an off-by-one in the midpoint, or a combine that mishandles leftovers?
Real-World & Interview Uses
Where do sorting, search, closest-pair, fast multiplication, and parallelizable work use this paradigm?
Implementation Walkthrough
Before writing code, decide each part.
Signature & Setup
What bounds or sub-ranges does the recursive method take, and what does the top-level caller pass?
The Base Case Branch
Which smallest size do you solve directly, and what do you return without recursing?
The Divide Step
How do you compute the split point and form the sub-ranges without losing or duplicating elements?
The Recursive Calls
In what order do you solve the sub-ranges, and where do you store their results?
The Combine Step
How do you walk the sub-results together, and where does any scratch buffer live?
Termination & Return
What does the top-level call hand back, and how do you confirm every element is accounted for?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Backtracking
What It’s For & Why It Matters
Why systematically build candidate solutions one choice at a time and abandon (“backtrack”) a partial solution the moment it can’t possibly work — instead of generating every combination blindly?
The Core Idea
How does backtracking turn “search all configurations” into “grow a partial solution, and retreat the instant it becomes hopeless,” and why is retreating the whole point?
The Mental Model
How do you picture the search as walking down into a choice, exploring everything reachable from it, then stepping back up to try the next choice — like exploring a maze with a piece of chalk?
Choose / Explore / Un-choose
What are the three steps of the backtracking skeleton, and why is the “un-choose” (undo the last choice before returning) the part people forget?
Why Un-choose Restores State
Why must the partial solution look exactly as it did before a choice was made once that branch returns, and what breaks downstream if the undo is skipped?
The State-Space Tree
Why does every backtracking problem correspond to a DFS over a tree of partial choices, what does each node represent, and what do the leaves represent?
Branching Factor & Depth
At a given node, how many children does it have (the branching factor), and how deep can a root-to-leaf path get — and why do those two numbers govern everything about the cost?
Pruning
How does rejecting an invalid partial solution early (a “constraint check”) cut off an entire subtree of work, and why is that where the real speedup comes from even though it doesn’t change the worst-case Big-O?
Where to Place the Check
Why test feasibility as early as possible — at the moment of choosing rather than only at a complete leaf — and how much work does an early prune save versus a late one?
Recursion Structure
What is the base case (a complete candidate or a dead end), what is the recursive case (loop over choices, recurse, undo), and how do those map onto a single function?
Recording Solutions
When you reach a valid leaf, why must you usually copy the current partial solution into the results rather than store a reference to the mutable working state?
Classic Examples
How do subsets, permutations, combinations, and N-queens each instantiate the same skeleton with a different choice set and constraint?
Subsets vs Permutations
How do the choice sets differ — at each step include-or-skip the current element (subsets) vs. pick an unused element (permutations) — and why does that change the tree’s shape and leaf count?
N-Queens
Why is N-queens the canonical backtracking problem — how do column and diagonal constraints let you prune most placements before the board is ever full?
Time Complexity
How do you reason about the running time in terms of the state-space tree without solving a formula?
Counting the Nodes
Why is the cost proportional to the number of nodes the search actually visits, and how does that relate to branching factor raised to the depth in the unpruned worst case?
Work Per Node
Beyond the recursion itself, why does each node cost the time of its constraint check and the work to record a solution at a leaf, and why can that add a factor on top of the node count?
Why Pruning Helps in Practice
Why is the worst case typically exponential, yet aggressive pruning can make a problem like N-queens solve in well under the unpruned bound, even though the Big-O is unchanged?
Space Complexity
Where does backtracking spend memory beyond the input?
Recursion-Stack Depth
Why is the call stack bounded by the depth of the state-space tree (the longest partial solution), not by the total number of nodes explored?
The Working State
Why does the in-progress candidate (the board, the chosen-so-far list, the used-markers) cost space proportional to the depth, and why is reusing one mutable buffer cheaper than copying at every step?
Common Bugs & Edge Cases
What breaks if you forget to undo a choice, mutate shared state without restoring it, copy the result list at the wrong time, check constraints too late, or mishandle the empty-input base case?
Connections
How does backtracking relate to plain recursion, to DFS, to dynamic programming (when overlapping subproblems exist), and to branch-and-bound (when you also carry a bound to prune)?
Real-World & Interview Uses
Where do constraint solvers, Sudoku and crossword fillers, parsing, regex matching, and combinatorial generation rely on backtracking?
Implementation Walkthrough
Before writing code, decide each part.
Setup & The Result Container
What state do you carry into the recursion (the working candidate, any used-markers, the start index), and where does the list that collects finished solutions live?
The Base Case Branch
How do you detect a complete candidate or a dead end, and what do you do — record a copy, or simply return?
The Main Loop Over Choices
How do you iterate the available choices at this node, apply the constraint check to skip infeasible ones, and make a choice before recursing?
The Un-choose Step
Right after the recursive call returns, how do you undo the choice so the next iteration starts from a clean state?
Termination & Return
How does the whole search finish once the root’s loop is exhausted, and what does the top-level call hand back?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Closest Pair of Points
The Problem & Why It Matters
Given points in a plane, what exactly are you trying to find, and where does this come up?
Brute-Force Baseline
What does checking every pair cost, and why is it worth beating?
The Divide-and-Conquer Idea
How does splitting the plane turn one hard search into two smaller ones plus a merge?
The Mental Model
How do you picture the points cut by a vertical line, with each half solved on its own before reconciling the boundary?
Divide by Median x
How does a vertical line through the median x split the points into two balanced halves?
Why the Median Keeps Halves Balanced
What goes wrong for the cost if the split is lopsided instead of even?
Conquer Each Half
How does recursion find the closest pair within the left and the right sets, and what does each call return?
The Strip Problem
Why might the true closest pair straddle the dividing line, and how do you catch it?
Strip of Width Two Delta
Which points near the dividing line still need checking, and why only those within delta of the line?
The Constant-Neighbor Argument
Why does each strip point, scanned in y order, compare against only a fixed number of following points?
Combining the Three Candidates
How do you pick the final answer from the left best, the right best, and the strip best?
Keeping the Combine Linear
How does presorting by y, or merging sorted halves on the way up, avoid re-sorting inside the strip?
Two Sort Orders at Once
Why does the algorithm want points ordered by x for the split but by y for the strip scan, and how do you maintain both?
Recursion Structure
What are the base case and the two recursive calls, and how deep does the recursion go?
The Base Case
At what small point count do you stop recursing and just brute-force, and why is that safe?
Time Complexity
How do you reason about the running time without solving a recurrence formally?
Cost per Level
Why is the combine (strip build plus constant-neighbor scan) linear at each level of the recursion?
Why It Beats Brute Force
How does linear-combine work over logarithmically many balanced levels land below the quadratic baseline?
What the Presort Costs
Why is the one-time sort by x not the dominant term once the recursion is running?
Space Complexity
Where does this algorithm spend memory beyond the input points?
Recursion Depth
Why is the stack depth tied to how many times the point set can be halved?
Strip and Sorted Buffers
Why does building the strip and keeping y-sorted order need extra space proportional to the points involved?
Common Bugs & Edge Cases
What goes wrong with duplicate points, fewer than two points, ties at the median, coincident points giving distance zero, or an unsorted strip?
Real-World & Interview Uses
Where do collision detection, clustering, map labeling, and spatial queries rely on closest-pair?
Implementation Walkthrough
Before writing code, decide each part.
Setup & Presort
Which arrays do you sort up front, and by which coordinate, before the first recursive call?
The Base Case Branch
At what size do you switch to brute force, and how do you compute the minimum directly there?
The Divide Step
How do you find the median x and partition points into left and right without breaking the sorted orders?
The Two Recursive Calls
How do you call each half and capture the smaller of the two returned distances as delta?
Building and Scanning the Strip
How do you collect points within delta of the line, order them by y, and run the bounded-neighbor comparison?
Termination & Return
What single value (and pair) does each call return, and how does the top level report the global closest pair?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Fast Exponentiation
The Problem & When to Use It
When you need a power of something with a huge exponent, why not just loop and multiply?
Naive Repeated Multiplication
Why is multiplying one factor at a time wasteful, and exactly how many multiplications does it spend?
The Squaring Insight
How does \( x^n = (x^{n/2})^2 \) cut the remaining work roughly in half at each step?
Why Halving the Exponent Is the Whole Trick
What is the key realization that lets you reuse one squared value instead of recomputing from scratch?
Odd vs Even Exponents
What extra factor of \( x \) appears when the exponent is odd, and how do you fold it in?
Handling the Parity at Each Step
How does testing the lowest bit of the exponent decide whether you multiply in an extra factor?
Recursive Top-Down Version
How do you code “halve the exponent, square the result, multiply once if odd”?
Base Case
What exponent ends the recursion, and what value does it return?
Why You Square the Returned Value, Not Recompute
What goes wrong for the cost if you call the recursion twice instead of squaring its single result?
Iterative Binary Version
How do the bits of the exponent tell you when to square and when to multiply?
Reading the Exponent’s Bits
How does scanning \( n \) bit by bit drive an accumulator alongside a running square?
Why the Running Square Tracks Powers of Two
What does the repeatedly-squared base represent at the moment you inspect each bit?
The Mental Model
How do you see the exponent’s binary representation as a recipe of “square always, multiply on a set bit”?
Modular Exponentiation
How does taking a remainder at each step keep the intermediate numbers small for cryptographic use?
Why You Can Reduce at Every Step
What property of modular arithmetic lets you take the remainder after each multiply without changing the result?
Time Complexity
How do you reason about the cost without deriving a recurrence?
Counting the Multiplications
Why does halving the exponent each step give a number of multiplications tied to the bit-length of \( n \)?
Recursive vs Iterative Step Count
Why do both forms perform essentially the same number of squarings and multiplies?
When Operand Size Matters
Why does the per-multiply cost stop being constant once the numbers grow into bignum territory?
Space Complexity
Where does each version spend memory?
Iterative Constant Space
Why does the loop need only a couple of accumulator variables regardless of the exponent?
Recursive Call-Stack Depth
Why does the top-down version cost stack depth proportional to the bit-length of \( n \)?
Common Bugs & Edge Cases
What goes wrong with exponent zero, negative exponents, integer overflow, a missing modulo, or squaring before handling the odd factor?
Real-World & Interview Uses
Where do cryptography, hashing, matrix-power tricks, and modular arithmetic rely on this?
Implementation Walkthrough
Before writing code, decide each part.
Signature & Setup
What does the method take, and what accumulator and running-base variables do you initialize?
The Base Case or Loop Guard
What condition ends the recursion, or what condition keeps the loop running over the exponent’s bits?
The Square-and-Maybe-Multiply Step
In what order do you inspect the current bit, multiply into the accumulator if set, and square the base?
Optional Modular Reduction
Where exactly do you take the remainder so numbers stay bounded?
Termination & Return
What does the method return once the exponent is exhausted, and what should it return for exponent zero?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Karatsuba Multiplication
The Problem & When It Pays Off
Why do we need a faster multiply for very large numbers, and at what size does the overhead start to win?
Schoolbook Multiply
Why does the grade-school digit-by-digit method scale quadratically, and exactly what work does it duplicate?
Splitting Numbers in Half
How do you write each factor as \( a \cdot 10^{m} + b \) (or the base-2 equivalent)?
Choosing the Split Point
How does the digit count decide where to cut, and what do you do when the length is odd?
The Four-Product Expansion
When you multiply the split forms out, which four partial products appear, and which two are the “corners”?
Why Naively This Is Still Four Multiplies
What makes the straightforward expansion no better than schoolbook in the number of big multiplications?
The Three-Multiplication Trick
How does computing \( (a+b)(c+d) \) let you recover the middle term and drop one of the four products?
Recovering the Middle Term
Which two already-computed products do you subtract from \( (a+b)(c+d) \) to isolate the cross term?
The Mental Model
How do you see three smaller multiplications replacing four, recursively, as the source of the speedup?
Combining the Pieces
How do the three partial products recombine with shifts and a subtraction into the final number?
Where the Shifts Come From
Why does each partial product get multiplied by a specific power of the base when reassembled?
Recursion Structure
How do the three multiplications become three recursive calls, and what is the base case?
The Call Stack
How deep does the recursion go, and what does each frame compute before returning its partial product?
The Base Case
At what operand size do you stop recursing and just multiply directly, and why?
Mapping It to Code
How do you handle the shifts, the additions, the subtraction, and the carries when assembling the result?
Time Complexity
How do you reason about the cost without formally solving the recurrence?
Three Subproblems of Half Size
Why does replacing four half-size multiplies with three change the growth rate at all?
The Resulting Exponent
Why does three-way branching on half-size inputs land near \( n^{1.585} \) rather than \( n^2 \)?
The Hidden Constant Factor
Why do the extra additions, subtractions, and shifts inflate the constant, hurting small inputs?
Space Complexity
Where does Karatsuba spend memory beyond the operands?
Recursion-Stack Depth
Why is the stack depth tied to how many times the operands can be halved?
Temporary Sums and Partial Products
Why do the intermediate sums \( a+b \), \( c+d \), and the three products need scratch space at each level?
Trade-offs vs Alternatives
Why is Karatsuba slower than schoolbook on small inputs, and where do FFT-based methods eventually take over?
Common Bugs & Edge Cases
What goes wrong with odd splits, leading zeros, a negative middle term, mismatched operand lengths, or a missed shift?
Real-World & Interview Uses
Where do bignum libraries and arbitrary-precision arithmetic use this algorithm?
Implementation Walkthrough
Before writing code, decide each part.
Signature & Setup
What representation do the operands use, and how do you read their length to pick a split point?
The Base Case Branch
At what size do you fall back to direct multiplication, and what do you return there?
Splitting Into High and Low Halves
How do you extract \( a, b \) and \( c, d \) from each operand without losing digits?
The Three Recursive Products
Which three products do you compute, and how do you form the \( (a+b) \) and \( (c+d) \) inputs?
Recombining With Shifts
How do you apply the powers of the base and the middle-term subtraction to assemble the result?
Termination & Return
What final number does the top-level call return, and how do you confirm carries were handled?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Recursion & Divide and Conquer: Problem Set
Each problem is an implementation task: fill in the matching stub in problemset/
and make its test in tests/problemset/ pass. The set opens with Foundational
warm-ups that drill the core recursive shapes — base case, single recursive step,
and divide-and-combine — then moves through Applied Problems that weave real
LeetCode interview questions together with contest-style challenges, ordered easy
to hard. Lean on recursion and divide and conquer throughout: split the input,
solve the pieces, and combine.
Foundational
Problem 1: Sum of Digits
Description
Given a non-negative integer n, return the sum of its base-10 digits. Solve it
recursively: the last digit is n % 10, and the rest of the number is n / 10.
Do not use any loops.
Examples
Example 1:
Input: n = 12345
Output: 15
1 + 2 + 3 + 4 + 5 = 15.
Example 2:
Input: n = 0
Output: 0
Zero has a single digit whose sum is 0 — this is the base case.
Example 3:
Input: n = 9
Output: 9
A single-digit number is its own digit sum.
Constraints
0 <= n <= 2,000,000,000
Problem 2: Recursive Power
Description
Compute b raised to the power e, where e is a non-negative integer, using
exponentiation by squaring. Halve the exponent at each step: b^e = (b^(e/2))^2
for even e, and b * b^(e-1) for odd e. The recursion should make
O(log e) multiplications, not O(e).
Examples
Example 1:
Input: b = 2, e = 10
Output: 1024
2^10 = 1024, reached in about four squarings rather than ten multiplications.
Example 2:
Input: b = 5, e = 0
Output: 1
Any base to the zero power is 1 — the base case of the recursion.
Example 3:
Input: b = 3, e = 3
Output: 27
3^3 = 27.
Constraints
-100 <= b <= 1000 <= e <= 30- The result fits in a 64-bit signed integer.
Problem 3: Reverse a String
Description
Return the reverse of a string using recursion. The empty string is its own reverse (base case); otherwise the reverse is the reverse of everything after the first character, followed by the first character. Do not use a loop or a library reverse helper.
Examples
Example 1:
Input: s = "recursion"
Output: "noisrucer"
Example 2:
Input: s = ""
Output: ""
The empty string is the base case.
Example 3:
Input: s = "a"
Output: "a"
A single character reversed is itself.
Constraints
0 <= s.length <= 10,000sconsists of printable ASCII characters.
Problem 4: Recursive Binary Search
Description
Given an array sorted in ascending order and a target, return the index of
target, or -1 if it is absent. Implement the search recursively as divide and
conquer: compare against the middle element and recurse into the half that can
still contain the target. Run in O(log n).
Examples
Example 1:
Input: a = [-3, 0, 4, 7, 11, 18], target = 7
Output: 3
The middle splits the array; 7 lies in the right half and is found at index 3.
Example 2:
Input: a = [1, 2, 3, 4, 5], target = 6
Output: -1
6 is larger than every element, so the search exhausts and returns -1.
Example 3:
Input: a = [42], target = 42
Output: 0
Constraints
0 <= a.length <= 100,000ais sorted in strictly ascending order.-10^9 <= a[i], target <= 10^9
Problem 5: Greatest Common Divisor
Description
Compute the greatest common divisor of two non-negative integers a and b
using the recursive Euclidean algorithm: gcd(a, b) = gcd(b, a % b), with
gcd(a, 0) = a as the base case. By convention gcd(0, 0) = 0.
Examples
Example 1:
Input: a = 48, b = 18
Output: 6
gcd(48, 18) -> gcd(18, 12) -> gcd(12, 6) -> gcd(6, 0) = 6.
Example 2:
Input: a = 17, b = 5
Output: 1
17 and 5 are coprime.
Example 3:
Input: a = 0, b = 9
Output: 9
Constraints
0 <= a, b <= 2,000,000,000
Problem 6: Power of Two
Description
Decide whether a positive integer n is a power of two. Use a recursive halving
check: n is a power of two if it is 1 (base case), or if it is even and
n / 2 is a power of two. Any odd number greater than one is not.
Examples
Example 1:
Input: n = 16
Output: true
16 -> 8 -> 4 -> 2 -> 1, halving cleanly every step.
Example 2:
Input: n = 12
Output: false
12 -> 6 -> 3, which is odd and greater than one, so 12 is not a power of two.
Example 3:
Input: n = 1
Output: true
2^0 = 1 is the base case.
Constraints
1 <= n <= 2,000,000,000
Applied Problems
Problem 7: Pow(x, n)
LeetCode: 50. Pow(x, n)
Description
Implement pow(x, n), which raises the floating-point base x to the integer
power n. Use fast exponentiation (exponentiation by squaring) so the work is
O(log |n|). Handle negative exponents by raising the reciprocal: x^(-n) = (1/x)^n.
Examples
Example 1:
Input: x = 2.00000, n = 10
Output: 1024.00000
Example 2:
Input: x = 2.10000, n = 3
Output: 9.26100
Example 3:
Input: x = 2.00000, n = -2
Output: 0.25000
2^(-2) = 1 / 2^2 = 1/4 = 0.25.
Constraints
-100.0 < x < 100.0-2^31 <= n <= 2^31 - 1- Either
xis non-zero orn > 0. - The result is within
10^-5of the true value.
Problem 8: Fibonacci Number
LeetCode: 509. Fibonacci Number
Description
The Fibonacci sequence is defined by F(0) = 0, F(1) = 1, and
F(n) = F(n-1) + F(n-2) for n >= 2. Given n, return F(n). Express the
recurrence directly; to stay fast you may memoize or fold the two recursive calls
into a single linear pass.
Examples
Example 1:
Input: n = 2
Output: 1
F(2) = F(1) + F(0) = 1 + 0 = 1.
Example 2:
Input: n = 4
Output: 3
F(4) = F(3) + F(2) = 2 + 1 = 3.
Example 3:
Input: n = 10
Output: 55
Constraints
0 <= n <= 30
Problem 9: Merge Two Sorted Lists
LeetCode: 21. Merge Two Sorted Lists
Description
Given two arrays each sorted in ascending order, merge them into a single sorted array. Solve it recursively in the spirit of the merge step of merge sort: compare the two front elements, emit the smaller, and recurse on the remainder. Do not call a library sort.
Examples
Example 1:
Input: a = [1, 2, 4], b = [1, 3, 4]
Output: [1, 1, 2, 3, 4, 4]
Example 2:
Input: a = [], b = []
Output: []
Example 3:
Input: a = [], b = [0]
Output: [0]
Constraints
0 <= a.length, b.length <= 50,000- Both arrays are sorted in non-decreasing order.
-10^9 <= a[i], b[i] <= 10^9
Problem 10: Find Peak Element
LeetCode: 162. Find Peak Element
Description
A peak element is strictly greater than its neighbors; out-of-bounds neighbors
count as negative infinity. Given an array where no two adjacent elements are
equal, return the index of any peak. Use a divide-and-conquer binary search:
compare the middle to its right neighbor and recurse toward the higher side, which
must contain a peak. Run in O(log n).
Examples
Example 1:
Input: a = [1, 2, 3, 1]
Output: 2
a[2] = 3 is greater than both neighbors, so index 2 is a peak.
Example 2:
Input: a = [1, 2, 1, 3, 5, 6, 4]
Output: 5
Index 5 (value 6) is a peak; index 1 (value 2) is another valid answer.
Example 3:
Input: a = [1]
Output: 0
A single element is a peak with both neighbors treated as negative infinity.
Constraints
1 <= a.length <= 1,000-2^31 <= a[i] <= 2^31 - 1a[i] != a[i + 1]for all validi.
Problem 11: Search in Rotated Sorted Array
LeetCode: 33. Search in Rotated Sorted Array
Description
An ascending sorted array of distinct values has been rotated at an unknown pivot
(for example [0,1,2,4,5,6,7] becomes [4,5,6,7,0,1,2]). Given the rotated array
and a target, return its index, or -1 if absent. Use a modified binary search:
at each step one half is sorted — decide whether the target lies in it and recurse
accordingly. Run in O(log n).
Examples
Example 1:
Input: a = [4, 5, 6, 7, 0, 1, 2], target = 0
Output: 4
Example 2:
Input: a = [4, 5, 6, 7, 0, 1, 2], target = 3
Output: -1
Example 3:
Input: a = [1], target = 0
Output: -1
Constraints
1 <= a.length <= 5,000- All values in
aare distinct. ais a rotation of an ascending array.-10^4 <= a[i], target <= 10^4
Problem 12: Subsets
LeetCode: 78. Subsets
Description
Given an array of distinct integers, return all possible subsets (the power set). The solution set must not contain duplicate subsets; order does not matter. Build the subsets by recursion/backtracking: at each index, choose to include or exclude the element and recurse on the rest.
Examples
Example 1:
Input: nums = [1, 2, 3]
Output: [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
All 2^3 = 8 subsets, in any order.
Example 2:
Input: nums = [0]
Output: [[], [0]]
Example 3:
Input: nums = []
Output: [[]]
The only subset of the empty set is the empty set itself.
Constraints
0 <= nums.length <= 10- All elements of
numsare distinct. -10 <= nums[i] <= 10
Problem 13: Permutations
LeetCode: 46. Permutations
Description
Given an array of distinct integers, return all possible orderings (permutations). Use backtracking: pick an unused element for the current position, recurse to fill the remaining positions, then undo the choice and try the next candidate. Any order of the permutations is accepted.
Examples
Example 1:
Input: nums = [1, 2, 3]
Output: [[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]
All 3! = 6 orderings, in any order.
Example 2:
Input: nums = [0, 1]
Output: [[0, 1], [1, 0]]
Example 3:
Input: nums = [1]
Output: [[1]]
Constraints
1 <= nums.length <= 6- All elements of
numsare distinct. -10 <= nums[i] <= 10
Problem 14: Sort an Array (Merge Sort)
LeetCode: 912. Sort an Array
Description
Given an array of integers, return it sorted in ascending order in O(n log n)
time without using any built-in sort. Implement merge sort as divide and conquer:
split the array in half, recursively sort each half, then merge the two sorted
halves into one.
Examples
Example 1:
Input: nums = [5, 2, 3, 1]
Output: [1, 2, 3, 5]
Example 2:
Input: nums = [5, 1, 1, 2, 0, 0]
Output: [0, 0, 1, 1, 2, 5]
Example 3:
Input: nums = [42]
Output: [42]
Constraints
1 <= nums.length <= 50,000-50,000 <= nums[i] <= 50,000
Problem 15: Majority Element
LeetCode: 169. Majority Element
Description
Given an array of size n, return the majority element — the value that appears
more than n / 2 times. You may assume one always exists. Solve it with divide
and conquer: recursively find the majority of the left and right halves; if they
agree that is the answer, otherwise count which candidate dominates the full
range.
Examples
Example 1:
Input: nums = [3, 2, 3]
Output: 3
3 appears twice in an array of length 3, which is more than 1.5.
Example 2:
Input: nums = [2, 2, 1, 1, 1, 2, 2]
Output: 2
2 appears four times out of seven.
Example 3:
Input: nums = [7]
Output: 7
Constraints
1 <= nums.length <= 50,000- A majority element is guaranteed to exist.
-10^9 <= nums[i] <= 10^9
Problem 16: Different Ways to Add Parentheses
LeetCode: 241. Different Ways to Add Parentheses
Description
Given a string expression of numbers and the operators +, -, and *, return
all possible results of computing it under every way of grouping the operands with
parentheses. Solve it with divide and conquer: split at each operator, recursively
evaluate the left and right sub-expressions, and combine every left result with
every right result using that operator. Results may be returned in any order.
Examples
Example 1:
Input: expression = "2-1-1"
Output: [0, 2]
(2-1)-1 = 0 and 2-(1-1) = 2.
Example 2:
Input: expression = "2*3-4*5"
Output: [-34, -14, -10, -10, 10]
The five distinct parenthesizations evaluate to these values.
Example 3:
Input: expression = "11"
Output: [11]
A bare number has exactly one value.
Constraints
1 <= expression.length <= 20expressioncontains only digits and the operators+,-,*.- Every integer operand is in the range
[0, 99].
Problem 17: Count Inversions
Description
Given an array, count the number of inversions: pairs (i, j) with i < j and
a[i] > a[j]. A brute-force scan is O(n^2); instead piggyback on merge sort —
while merging two sorted halves, every time you take an element from the right
half before the left, it forms an inversion with all remaining left-half elements.
Run in O(n log n).
Examples
Example 1:
Input: a = [2, 4, 1, 3, 5]
Output: 3
The inversions are (2,1), (4,1), and (4,3).
Example 2:
Input: a = [5, 4, 3, 2, 1]
Output: 10
A strictly descending array of length 5 has every one of its 10 pairs inverted.
Example 3:
Input: a = [1, 2, 3]
Output: 0
An already-sorted array has no inversions.
Constraints
1 <= a.length <= 100,000-10^9 <= a[i] <= 10^9- The count fits in a 64-bit signed integer.
Problem 18: Generate Parentheses
LeetCode: 22. Generate Parentheses
Description
Given n pairs of parentheses, generate all combinations of well-formed
parentheses. Use backtracking: at each step you may add an opening bracket if any
remain, or a closing bracket if it would not exceed the opens placed so far.
Results may be returned in any order.
Examples
Example 1:
Input: n = 3
Output: ["((()))", "(()())", "(())()", "()(())", "()()()"]
The five valid arrangements of three pairs.
Example 2:
Input: n = 1
Output: ["()"]
Example 3:
Input: n = 2
Output: ["(())", "()()"]
Constraints
1 <= n <= 8
Problem 19: Combination Sum
LeetCode: 39. Combination Sum
Description
Given an array of distinct positive integers candidates and a target, return
all unique combinations whose elements sum to target. The same number may be
chosen unlimited times. Two combinations are the same regardless of order. Use
backtracking: try each candidate, subtract it from the remaining target, and
recurse — allowing the same index again to permit reuse. Any order is accepted.
Examples
Example 1:
Input: candidates = [2, 3, 6, 7], target = 7
Output: [[2, 2, 3], [7]]
2 + 2 + 3 = 7 and 7 = 7.
Example 2:
Input: candidates = [2, 3, 5], target = 8
Output: [[2, 2, 2, 2], [2, 3, 3], [3, 5]]
Example 3:
Input: candidates = [2], target = 1
Output: []
No multiple of 2 equals 1.
Constraints
1 <= candidates.length <= 30- All elements of
candidatesare distinct. 1 <= candidates[i] <= 2001 <= target <= 500
Problem 20: Median of Two Sorted Arrays
LeetCode: 4. Median of Two Sorted Arrays
Description
Given two arrays sorted in ascending order, return the median of their combined
multiset. The overall run time must be O(log(min(m, n))). Binary-search a
partition of the shorter array so that all elements on the left of the combined
split are no greater than those on the right; the median falls at the boundary.
Examples
Example 1:
Input: a = [1, 3], b = [2]
Output: 2.00000
The merged array is [1, 2, 3], whose median is 2.
Example 2:
Input: a = [1, 2], b = [3, 4]
Output: 2.50000
The merged array is [1, 2, 3, 4]; the median is (2 + 3) / 2 = 2.5.
Example 3:
Input: a = [], b = [1]
Output: 1.00000
Constraints
0 <= m, n <= 1,000and1 <= m + n- Both arrays are sorted in non-decreasing order.
-10^6 <= a[i], b[i] <= 10^6
Problem 21: Relay Station Spacing
Description
Given the planar coordinates of relay stations, return the smallest Euclidean
distance between any two distinct stations. Use the closest-pair-of-points
divide-and-conquer algorithm: sort by x, split into left and right halves, recurse
on each, then check the narrow strip around the dividing line for any closer
cross-pair. Run in O(n log n). Reuses the module’s Point record.
Examples
Example 1:
Input: stations = [(0,0), (5,5), (1,1), (9,2)]
Output: 1.41421356
The closest pair is (0,0) and (1,1), at distance sqrt(2).
Example 2:
Input: stations = [(0,0), (3,4)]
Output: 5.0
With two points the answer is simply the distance between them.
Example 3:
Input: stations = [(2,2), (2,5), (2,2.5), (8,8)]
Output: 0.5
The pair (2,2) and (2,2.5) is the closest, at distance 0.5.
Constraints
2 <= stations.length <= 100,000-10^9 <= x, y <= 10^9- All stations are distinct points.
Problem 22: Skyline Merge
Description
Each building is given as [left, right, height]. The skyline is the silhouette
formed by all the buildings against the horizon, described as a list of “key
points” [x, height] sorted by x, where each point marks a change in the
visible roofline; the last point has height 0. Compute it with divide and
conquer: split the buildings into two groups, recursively compute each group’s
skyline, then merge the two skylines by sweeping their key points and tracking the
running max height of each side. Run in O(n log n).
Examples
Example 1:
Input: buildings = [[2,9,10], [3,7,15], [5,12,12], [15,20,10], [19,24,8]]
Output: [[2,10], [3,15], [7,12], [12,0], [15,10], [20,8], [24,0]]
The merged silhouette rises to 15, steps down past each building, and returns to 0.
Example 2:
Input: buildings = [[0,2,3], [2,5,3]]
Output: [[0,3], [5,0]]
Two equal-height adjacent buildings form a single flat roofline.
Example 3:
Input: buildings = [[1,5,11]]
Output: [[1,11], [5,0]]
A lone building gives its left edge and its right drop to ground.
Constraints
1 <= buildings.length <= 10,0000 <= left < right <= 2^31 - 11 <= height <= 2^31 - 1
Linear Search
What It Is & When to Use It
What problem does linear search solve, and in what situations is it the only option you have?
The Sequential Scan Mental Model
How do you picture walking a collection element by element, and what single piece of state do you carry as you go?
The Loop Invariant
What is guaranteed about every element you’ve already passed at the moment you inspect index \( i \)?
Why Order Doesn’t Matter
Why does this work on unsorted data when binary search cannot, and what freedom does that give you?
The Comparison at the Core
What does it mean to “match” an element — reference equality, value equality, or a predicate — and how does that choice change the code?
Iterative Coding
What does the basic loop look like, and where do you place the equality check and the early return?
Returning First vs Last Match
If duplicates exist, how do you control which occurrence you report, and what loop direction does each need?
Sentinel Optimization
How can placing the target at the end as a sentinel remove the bounds check from the inner loop?
Why the Sentinel Helps
What hidden per-iteration cost does the bounds check carry, and what must you restore afterward?
The Found vs Not-Found Contract
What should you return when the target is absent — an index, a boolean, or \( -1 \) — and why does the caller care?
Time Complexity
What is the cost model — how many comparisons does linear search perform as a function of \( n \)?
Best Case
What input makes it finish in \( O(1) \), and where must the target sit for that to happen?
Average Case
If the target is equally likely at any position, where does a random hit land on average, and why is that still \( \Theta(n) \)?
Worst Case
Which two situations both force a full \( n \)-element scan, and why?
Constant Factors
Why can a plain linear scan beat a fancier algorithm on small \( n \) despite the same or worse big-O?
Space Complexity
Why is linear search \( O(1) \) auxiliary space, and what would you have to add to change that?
Trade-offs vs Binary Search & Hashing
When is an \( O(n) \) scan actually the right call over an \( O(\log n) \) or \( O(1) \) lookup, once you count setup cost?
Common Bugs & Edge Cases
What goes wrong with empty inputs, off-by-one loop bounds, or comparing objects with == instead of .equals?
Real-World Uses
Where do small lists, linked structures, or one-shot scans make linear search the practical choice?
Implementation Walkthrough
Break the algorithm into the parts you must get right before you write a line.
Setup
What inputs and return-type decision do you fix before the loop starts?
The Scan Loop
How do you structure the single pass, and what exactly is compared each iteration?
Termination & Return
What are the two ways the loop can end, and what value does each path return?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Binary Search
What It Is & When to Use It
What precondition must the data satisfy, and what makes binary search worth the setup cost?
How Halving the Search Space Works
How does each comparison let you discard half the remaining range?
The Mental Model of a Shrinking Window
How do you picture [lo, hi] closing in, and what does it mean when it becomes empty?
The Three-Way Decision
At each midpoint, what are the three outcomes (less, equal, greater) and how does each reshape the window?
Iterative Coding with lo/hi
How do you maintain the lo and hi bounds and update them after each midpoint check?
Computing the Midpoint Safely
Why is lo + (hi - lo) / 2 preferred over (lo + hi) / 2?
Recursive Coding
How do you express the same logic as a recursive call on a shrinking subrange?
The Loop Invariant & Termination
What stays true about lo and hi every iteration, and how do you guarantee the loop ends?
Choosing < vs <= and the Interval Convention
Which loop condition matches a closed [lo, hi] versus a half-open [lo, hi) range, and why must they agree?
Why the Window Must Always Shrink
What update mistake leaves lo and hi unchanged, and why does that cause an infinite loop?
Variants: Lower Bound & Upper Bound
How do you find the first element \( \ge \) the target, or the insertion point, instead of just any match?
Leftmost vs Rightmost Match
With duplicates present, how do you bias the search to return the first or last equal element?
Time Complexity
How many times can you halve \( n \) before the window is empty, and why does that count the comparisons?
Best Case
What lets binary search return in \( O(1) \), and how likely is that?
Worst & Average Case
Why are both worst and average \( \Theta(\log n) \), and how few steps is that for a million elements?
Constant Factors vs Linear Search
Why might linear search still win for very small arrays despite \( O(\log n) \)?
Space Complexity
Why is the iterative version \( O(1) \) while the recursive version costs \( O(\log n) \) call-stack space?
Trade-offs vs Linear Search & Hashing
When does the cost of keeping data sorted outweigh the lookup speedup, and when does hashing beat both?
Common Bugs & Edge Cases
Where do infinite loops, off-by-one bounds, and overflow creep in, and how do empty or single-element arrays behave?
Real-World Uses
Where do you see binary search beyond arrays — answer-space search, rotated arrays, library APIs?
Implementation Walkthrough
Break the algorithm into the parts you must get right before you write a line.
Setup
What two bounds do you initialize, and to what values for your chosen interval convention?
The Search Loop
What is the loop condition, how do you compute the midpoint, and how does each comparison branch move a bound?
Termination & Return
How does the loop signal “found” versus “absent,” and what does each case return?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Comparison Lower Bound
What a Comparison Sort Is
Which sorts only ever learn about elements by comparing pairs, and which ones don’t?
The Comparison-Only Rule
Why does “no peeking at the key’s value, only </> results” define this whole class of algorithms?
The Core Claim
What is the headline result — why can no comparison sort beat \( n \log n \) in the worst case?
The Intuition Behind the Bound
Conceptually, why does the number of possible orderings force a minimum amount of work? (idea only, no formal proof)
Counting the Possible Orderings
How many distinct arrangements of \( n \) items exist, and why must a correct sort be able to distinguish all of them?
Information Gained Per Comparison
Why does each yes/no comparison reveal at most one bit, and how does that cap progress per step?
Putting the Two Together
If you must distinguish a huge number of orderings and each step gives one bit, why does the step count grow like \( n \log n \)?
What “Lower Bound” Actually Means
How is a lower bound a statement about every possible algorithm, not the running time of any single one?
Worst-Case vs Average-Case Bounds
Why does the bound hold even for the average case, not just the worst?
Which Sorts Hit the Bound
Why are merge sort and heapsort considered asymptotically optimal comparison sorts?
How Non-Comparison Sorts Escape It
Why do counting, radix, and bucket sort sidestep the \( n \log n \) wall entirely?
What Assumptions They Trade In
What must you know about the keys for a non-comparison sort to be legal, and what do you give up?
Time-Complexity Implications
How does this bound set the floor for the time complexity of any general-purpose comparison sort?
Why \( O(n) \) Comparison Sorting Is Impossible
What would an \( O(n) \) comparison sort have to do that the counting argument forbids?
Where the Best Case Slips Under
Why can a comparison sort still finish in \( O(n) \) on a lucky input without violating the worst-case floor?
Space-Complexity Note
Why does the lower bound concern comparisons and time, and say nothing on its own about memory use?
Why This Matters in Practice
How does knowing the bound stop you from chasing an impossible faster general-purpose comparison sort?
Implementation Walkthrough
Reason about a demonstration rather than a sort.
What There Is to Implement
Why is there no algorithm to code here — what could you instead build to illustrate the idea (e.g. counting comparisons a sort makes)?
Setting Up the Demonstration
What would you measure across input sizes to see the comparison count rise like \( n \log n \)?
Interpreting the Result
How would the measured curve support, but not prove, the lower bound?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Bubble Sort
What It Is & When to Use It
What is the one idea behind bubble sort, and is it ever the right tool outside teaching?
How Bubbling Works
How does each pass of adjacent swaps push the largest remaining element to its final slot?
Watching One Element Bubble
If you track a single large value, why does it rise to the top in one pass while small values drift only one step?
The Pass Invariant
What is guaranteed about the last \( k \) elements after \( k \) completed passes, and why does that prove correctness?
Iterative Coding
How do the outer pass loop and inner adjacent-comparison loop fit together?
The Adjacent Compare-and-Swap
What exactly do you compare, and under what condition do you swap?
The Early-Exit Flag
How can a “no swaps this pass” check turn the best case into \( O(n) \)?
Shrinking the Inner Bound Each Pass
Why can you stop the inner loop one element earlier after every completed pass?
Stability & In-Place
Why does swapping only on strict inequality preserve the order of equal keys, and why is no extra array needed?
Time Complexity
How do you count total comparisons and swaps across all passes, and how do they relate to the number of inversions?
Best Case
Which input lets the early-exit flag finish in a single \( O(n) \) pass?
Average Case
Why does a random permutation cost \( \Theta(n^2) \), and roughly how many swaps is that?
Worst Case
Which input (think reverse-sorted) forces the maximum swaps, and why does each element travel the full distance?
Space Complexity
Why is bubble sort \( O(1) \) auxiliary space with no recursion, and how does that compare to merge sort?
Trade-offs vs Insertion & Selection Sort
Among the quadratic sorts, why is bubble sort usually the slowest in practice despite matching big-O?
Common Bugs & Edge Cases
Where do off-by-one inner bounds, a forgotten swap-flag reset, or single-element arrays trip you up?
Real-World Uses
Where does bubble sort genuinely appear — tiny inputs, nearly-sorted data, or pure pedagogy?
Implementation Walkthrough
Break the algorithm into the parts you must get right before you write a line.
Setup
What outer-loop bound and what swap-tracking flag do you initialize?
The Nested Passes
How does the inner loop walk adjacent pairs, and how does the shrinking bound feed off the outer loop counter?
Early Termination & Return
How does the flag let you break early, and why does the array need no explicit return?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Selection Sort
What It Is & When to Use It
What is the find-the-minimum idea, and what unusual property makes it occasionally useful?
How Find-Min, Swap-to-Front Works
How does each iteration scan the unsorted suffix for its minimum and place it at the front?
Why Each Placement Is Final
Once you place the smallest remaining element, why will you never have to move it again?
The Sorted-Prefix Invariant
Why is the prefix both sorted and final the moment an element is placed there, and how does that prove correctness?
Iterative Coding
How do the outer placement loop and the inner min-tracking loop interact, and what index do you track?
Tracking the Min Index, Not the Value
Why hold onto the position of the smallest element rather than its value during the scan?
Minimal Writes: Exactly \( n-1 \) Swaps
Why does selection sort perform the fewest writes of the quadratic sorts, and when does write-cost matter?
Stability & In-Place
Why is the classic version unstable, and how can a shift-based variant restore stability?
A Tiny Unstable Example
What small input shows the long-distance swap reordering two equal keys?
Time Complexity
Why does the comparison count come out the same regardless of input order — count the inner-loop work across all passes.
Why There’s No Best Case
Why does even an already-sorted array still cost \( \Theta(n^2) \) comparisons?
Average & Worst Case
Why are average and worst case identical at \( \Theta(n^2) \), unlike most other sorts?
Comparisons vs Swaps
Why is the swap count only \( O(n) \) while comparisons stay \( \Theta(n^2) \), and when does that asymmetry matter?
Space Complexity
Why is selection sort \( O(1) \) auxiliary space with no recursion stack?
Trade-offs vs Bubble & Insertion Sort
When does the low swap count win out, and when do the other quadratic sorts beat it?
Common Bugs & Edge Cases
Where do you forget to reset the min index, swap with the wrong slot, or mishandle a single element?
Real-World Uses
Where do minimal writes (e.g. flash memory, costly swaps) actually justify selection sort?
Implementation Walkthrough
Break the algorithm into the parts you must get right before you write a line.
Setup
What does the outer loop range over, and what does index \( i \) mean each iteration?
The Min-Finding Inner Loop
How do you scan the suffix and remember where the minimum sits?
The Swap & Return
When do you perform the swap (and can you skip it), and why is no explicit return needed?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Insertion Sort
What It Is & When to Use It
What is the card-player analogy, and why is insertion sort the go-to for small or nearly-sorted inputs?
How Inserting into the Sorted Prefix Works
How does each step take the next element and slide it back into its place among the sorted left side?
The Sorted-Prefix Invariant
Why is the left part always sorted (though not necessarily final) before each insertion?
Iterative Coding
How do you hold the current key, walk leftward, and find its insertion point?
Shifting vs Swapping
Why is shifting elements rightward into a hole cheaper than repeated three-assignment swaps?
The Hole Mental Model
How does picturing a “hole” that moves left clarify where the held key finally lands?
Adaptivity & Inversions
How does the work insertion sort does map directly onto the number of inversions in the input?
Stability & In-Place
Which comparison choice (> vs >=) keeps equal keys in order while using \( O(1) \) extra space?
Binary Insertion Variant
How does using binary search to find the slot cut comparisons but not the shifting cost?
Time Complexity
How do you count comparisons and shifts, and why does input order drive the result?
Best Case
Why does an already-sorted (or nearly-sorted) array run in \( O(n) \), and what makes the inner loop exit immediately?
Average Case
Why does a random permutation land at \( \Theta(n^2) \), and how does the average inversion count explain it?
Worst Case
Which input (reverse-sorted) forces the maximum shifts, and why does every element travel the full prefix?
Space Complexity
Why is insertion sort \( O(1) \) auxiliary space, and why does it need no recursion stack?
Trade-offs vs Other Quadratic Sorts
Why is insertion sort usually the fastest quadratic sort in practice and the most adaptive to input order?
Common Bugs & Edge Cases
Where do you overwrite the held key, run the inner loop past index 0, or break stability?
Real-World Uses
Why do hybrid sorts (Timsort, introsort) fall back to insertion sort for small subarrays?
Implementation Walkthrough
Break the algorithm into the parts you must get right before you write a line.
Setup
Where does the outer loop start, and why is index 0 already a trivially sorted prefix?
The Inner Shift Loop
How do you hold the key, shift larger elements right, and decide when to stop?
Placing the Key & Return
Where does the held key get dropped once the inner loop stops, and why is no explicit return needed?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Merge Sort
What It Is & When to Use It
What guarantees does merge sort give that quicksort doesn’t, and when do those guarantees matter?
The Divide & Conquer Shape
How does recursively halving the array down to single elements set up the work?
Why a Single Element Is Already Sorted
Why is the base case trivial, and what does it let the recursion assume?
The Recursion Tree Picture
How many levels does the splitting create, and how much total work sits on each level?
The Merge Step
How do two pointers combine two already-sorted runs into one in linear time?
Tie-Breaking During the Merge
When both runs show equal keys, which one do you take first, and why does that choice decide stability?
Draining the Leftover Run
After one run empties, why can you copy the rest of the other run verbatim?
Top-Down Recursive Coding
How do you split at the midpoint, recurse on both halves, and merge the results?
Bottom-Up Iterative Coding
How does merging runs of size 1, then 2, then 4 avoid recursion entirely?
Stability & In-Place Question
Why is merge sort stable, and why is the standard array version not in-place?
Time Complexity
Why is the cost the work-per-level times the number of levels — reason it out from the recursion tree (no formal recurrence solving).
Why Best, Average, and Worst Are All \( \Theta(n \log n) \)
Why does input order not change the cost, unlike quicksort or insertion sort?
Constant Factors vs Quicksort
Why does merge sort often lose to quicksort in practice despite the same big-O?
Space Complexity
Where does the \( O(n) \) auxiliary array come from, and how much call-stack depth does the recursion add?
Reducing the Copying
How can ping-ponging between two buffers cut redundant array copies?
Linked-List Merge Sort
Why can a linked list be merge-sorted with only \( O(1) \) extra node space?
Trade-offs vs Quicksort & Heapsort
When do you pick merge sort’s predictability and stability over quicksort’s cache-friendly speed?
Common Bugs & Edge Cases
Where do midpoint overflow, leftover-run draining, and off-by-one merge bounds bite?
Real-World Uses
Why is merge sort the engine behind external sorting, linked-list sorting, and stable library sorts?
Implementation Walkthrough
Break the algorithm into the parts you must get right before you write a line.
Setup & Base Case
What subrange does each call own, and what range size stops the recursion?
The Recursive Split
How do you compute the midpoint and recurse on the two halves before merging?
The Merge Sub-Step
What pointers and temporary storage does the merge need, and how do you walk them?
Returning / Writing Back
How do merged results make it back into the original array positions?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Quicksort
What It Is & When to Use It
Why is quicksort the default in-memory sort despite a bad worst case?
How Partitioning Works
How does choosing a pivot and rearranging elements around it divide the problem?
Why the Pivot Lands in Its Final Spot
After a partition, why is the pivot already in its correct sorted position forever?
The Two Recursive Subproblems
Why do you recurse on the left and right partitions but never on the pivot itself?
Lomuto vs Hoare Partition Schemes
How does each scheme rearrange around the pivot, and how do their swap counts and pivot placement differ?
Lomuto Partition
How does a single forward scan with one boundary index place the pivot?
Hoare Partition
How do two pointers closing inward reduce swaps, and what does the returned split point mean?
Pivot Selection
Why does first-element pivoting hurt on sorted input, and how do median-of-three or randomization fix it?
Recursion & Tail-Call Optimization
How do you recurse on the smaller side and loop on the larger to cap stack depth?
3-Way Partition for Duplicates
How does Dutch-national-flag partitioning into less/equal/greater regions speed up arrays with many repeats?
In-Place but Unstable
Why is quicksort in-place, and why does long-distance swapping lose stability?
Time Complexity
Why does the cost depend entirely on how balanced each partition is — reason from the partition quality (no recurrence solving).
Best Case
What pivot quality gives even splits, and why does that yield \( \Theta(n \log n) \)?
Average Case
Why does a random or median-of-three pivot make balanced-enough splits the norm, keeping the expected cost \( O(n \log n) \)?
Worst Case
What input plus pivot choice produces maximally unbalanced splits and \( \Theta(n^2) \), and why?
Constant Factors
Why does quicksort’s cache-friendly, low-overhead inner loop beat heapsort and merge sort in practice?
Space Complexity
Why is quicksort \( O(1) \) for data movement but \( O(\log n) \) — or \( O(n) \) — for the recursion stack?
Where the Stack Depth Comes From
How does recursing on the smaller partition first bound the stack at \( O(\log n) \) even in the worst case?
Trade-offs vs Merge Sort & Heapsort
When do you give up quicksort’s speed for merge sort’s stability or heapsort’s worst-case guarantee?
Common Bugs & Edge Cases
Where do infinite recursion, wrong partition bounds, and all-equal inputs break a naive implementation?
Where It’s Used
Why do many standard libraries ship an introsort hybrid built on quicksort?
Implementation Walkthrough
Break the algorithm into the parts you must get right before you write a line.
Setup & Recursion Frame
What subrange does each call own, and what range size ends the recursion?
The Partition Sub-Step
How do you pick the pivot, scan, swap, and return the final pivot index?
Recursing on the Two Sides
How do you split the range around the returned pivot index and recurse?
Base Case & Termination
What stops the recursion, and why must the subproblems strictly shrink?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Heapsort
What It Is & When to Use It
What does heapsort guarantee that quicksort doesn’t, and why is it the in-place worst-case-safe choice?
The Array-Backed Binary Heap
How do parent and child indices map onto a flat array with no pointers?
The Heap Property
What must hold between every parent and its children in a max-heap, and why does that put the max at the root?
Index Arithmetic
For node at index \( i \), what are the formulas for its parent and two children, and where do internal nodes end?
Sift-Down (Heapify)
How does pushing an out-of-place element downward restore the heap property?
Sift-Down vs Sift-Up
Why does heapsort build and shrink the heap using sift-down rather than sift-up?
Picking the Larger Child
Why must sift-down swap with the larger of the two children, not just any child?
Building the Heap
How does heapifying from the last internal node up to the root turn an arbitrary array into a heap?
Why Build-Heap Is Linear
Conceptually, why is bottom-up build \( O(n) \) and not \( O(n \log n) \) — where do most nodes sit, and how far can they sift? (intuition only)
Extract-Max into the Suffix
How does repeatedly swapping the root to the end and re-heapifying grow a sorted suffix in place?
The Two-Phase Algorithm
How do the build phase and the repeated-extract phase combine into the full sort, and what shrinks each round?
In-Place but Unstable
Why does heapsort need only \( O(1) \) extra space, and why does swapping the root away lose stability?
Time Complexity
Why is the total cost the build plus \( n \) extractions — reason out each piece.
Build Phase Cost
Why does the build phase total \( O(n) \) despite touching every node?
Extraction Phase Cost
Why does each of the \( n \) extractions cost \( O(\log n) \) sift-down work, giving \( O(n \log n) \) overall?
Why There’s No Best Case
Why does heapsort stay \( \Theta(n \log n) \) even on sorted or all-equal input?
Constant Factors
Why is heapsort usually slower than quicksort in practice despite matching big-O — think cache behavior?
Space Complexity
Why is heapsort \( O(1) \) auxiliary space, and why does the iterative sift-down avoid a recursion stack?
Trade-offs vs Quicksort & Merge Sort
When do you choose heapsort’s worst-case guarantee and \( O(1) \) space over quicksort’s speed or merge sort’s stability?
Common Bugs & Edge Cases
Where do wrong child-index formulas, an unshrunk heap bound, or a missed final element go wrong?
Real-World Uses
Where does the underlying heap shine — priority queues, top-k, and scheduling — even when heapsort itself isn’t picked?
Implementation Walkthrough
Break the algorithm into the parts you must get right before you write a line.
The Sift-Down Sub-Step
Given an index and a heap-size bound, how do you compare against the larger child and swap downward until settled?
The Build-Heap Phase
From which index do you start, in which direction do you loop, and why?
The Extraction Loop
How do you swap root to the current end, shrink the heap bound, and re-sift the root?
Termination & Return
When does the extraction loop stop, and why is the array sorted in place with no explicit return?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Counting Sort
What It Is & When to Use It
How does counting sort break the \( n \log n \) barrier, and what must be true about the keys to use it?
The Key-Range Assumption
Why must keys be small integers in \( [0, k) \), and what breaks if the range is huge or unbounded?
Why It’s Not a Comparison Sort
Why does counting sort never compare two elements, and how does that let it dodge the \( n \log n \) lower bound?
Building the Tally Histogram
How does a first pass count how many times each key value appears?
Prefix-Sum Placement
How do cumulative counts convert each key into its exact output position?
From Counts to Positions
Why does turning the count array into a running total tell you where each value’s block ends in the output?
Counting Form vs Output Form
When can you just write counts back (no satellite data) versus needing the full stable placement pass?
Stable Placement
Why must you iterate the input in reverse during placement to keep equal keys in original order?
Stability & Why It Matters Here
Why is stability essential when counting sort is used as a digit pass inside radix sort?
Time Complexity
Why does the cost come out as two scans of \( n \) plus one scan of \( k \) — walk each phase.
Why Best, Average, and Worst Are All \( \Theta(n + k) \)
Why does input order never change the running time of counting sort?
When \( k \) Dominates
At what point does a large key range \( k \) make the \( k \) term, not \( n \), the bottleneck?
Space Complexity
Why does counting sort need \( O(n + k) \) extra memory — the count array plus the output array — and why is it not in-place?
The Cost of a Sparse Range
Why does a wide-but-sparse key range waste huge amounts of count-array space?
Trade-offs vs Comparison Sorts
When is counting sort a clear win, and when does its memory cost make it the wrong call?
Common Bugs & Edge Cases
Where do off-by-one count-array sizing, forgotten prefix sums, or negative keys break it?
Real-World Uses
Where do bounded small-integer keys (ages, grades, byte values, digit passes) make counting sort ideal?
Implementation Walkthrough
Break the algorithm into the parts you must get right before you write a line.
Setup & Sizing the Count Array
How big must the count array be, and how do you discover \( k \) from the input?
The Counting Pass
How does the first scan fill the histogram?
The Prefix-Sum & Placement Pass
How do you turn counts into positions, then scan the input (in which direction?) to drop each element into place?
Returning the Output
How does the filled output array get returned or copied back, and why can’t you sort fully in place?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Radix Sort
What It Is & When to Use It
How does sorting by digits avoid comparisons entirely, and what kinds of keys does it suit?
How Digit-by-Digit Sorting Works
How does stably sorting one digit position at a time eventually produce a fully sorted array?
Why Stability Carries Earlier Work Forward
Why must each digit pass preserve the order established by previous passes for the final result to be correct?
LSD vs MSD
Why does least-significant-digit-first need a stable subroutine, and when is most-significant-digit-first better?
LSD Radix Sort
Why does starting from the rightmost digit let earlier passes survive later ones?
MSD Radix Sort
How does starting from the leftmost digit recurse into buckets, and where does it shine for strings?
The Stable Subroutine Requirement
What goes wrong if the per-digit sort (usually counting sort) isn’t stable?
Choosing the Radix \( r \)
How does picking the base trade the number of passes against the work per pass?
Bits-per-Pass Tuning
How does grouping several bits into one “digit” change the pass count and the count-array size?
Handling Different Key Types
How do you adapt radix sort to fixed-width integers, negative numbers, and variable-length strings?
Time Complexity
Why is the cost the number of digits times the per-digit pass cost — break it into its factors.
The \( O(d \cdot (n + r)) \) Bound
Where do the \( d \), the \( n \), and the \( r \) each come from in the total cost?
Why It Can Beat \( n \log n \)
Under what relationship between \( d \), \( r \), and \( n \) does radix sort beat comparison sorts, and when does it not?
Best, Average, Worst
Why is radix sort’s running time essentially insensitive to input order?
Space Complexity
Why does each counting-sort pass need \( O(n + r) \) auxiliary space, and why is radix sort not in-place?
Trade-offs vs Comparison Sorts & Counting Sort
When does the digit count \( d \) or memory overhead make radix sort lose to a plain comparison sort or plain counting sort?
Common Bugs & Edge Cases
Where do digit-extraction errors, an unstable inner sort, or mixed-length keys break correctness?
Real-World Uses
Where do fixed-width keys — IDs, IP addresses, fixed-length strings — make radix sort a strong pick?
Implementation Walkthrough
Break the algorithm into the parts you must get right before you write a line.
Setup & Digit Count
How do you determine how many digit passes \( d \) you need from the maximum key?
Extracting One Digit
How do you pull out the digit at position \( p \) in base \( r \) using division and modulo?
The Stable Per-Digit Pass
How does the counting-sort subroutine run on a single digit, and where does its output go?
Looping Over Digits & Return
In which order do you process digit positions, and how does the array end up fully sorted?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Bucket Sort
What It Is & When to Use It
How does spreading keys into buckets achieve linear expected time, and what input does it assume?
The Uniform-Distribution Assumption
Why does bucket sort rely on inputs spread evenly over a known range, and what happens when they aren’t?
Why Even Spread Means Tiny Buckets
If keys are uniform, why does each bucket hold only a handful of elements on average?
How Scattering into Buckets Works
How do you map each key to a bucket index so that buckets stay in sorted order relative to each other?
The Index Mapping Formula
For a key in a known range and \( b \) buckets, what formula sends it to the right bucket?
Choosing the Number of Buckets
How does bucket count affect the size of each bucket and the work to sort it?
Sorting Within Buckets
Which subroutine sorts each bucket, and why is insertion sort the common choice for small buckets?
Concatenation
Why does gathering the buckets in index order produce a fully sorted array?
Stability & Space
Why does bucket sort need \( O(n + b) \) extra space, and when is it stable?
Time Complexity
Why does the cost split into scatter, per-bucket sort, and gather — reason about each.
Expected (Average) Case
Under uniform input, why does the per-bucket sorting total only \( O(n) \), giving \( O(n + b) \) overall?
Worst Case
What skewed input dumps everything into one bucket, and why does that collapse the cost to the inner sort’s \( O(n^2) \)?
Why the Distribution Drives Everything
Why is bucket sort’s complexity a statement about the input distribution, not just \( n \)?
Space Complexity
Why does bucket sort allocate \( O(n + b) \) for the buckets, and why is it not in-place?
The Cost of Too Many Buckets
Why does choosing far more buckets than elements waste space without helping time?
Trade-offs vs Radix & Counting Sort
When do you reach for bucket sort over the other linear-time sorts, given its distribution assumption?
Common Bugs & Edge Cases
Where do bad index mapping, out-of-range keys, or empty buckets cause problems?
Real-World Uses
Where do uniformly distributed floats or known-range data — like sorting fractions in \( [0, 1) \) — fit bucket sort?
Implementation Walkthrough
Break the algorithm into the parts you must get right before you write a line.
Setup & Bucket Allocation
How many buckets do you create, and what container holds each one?
The Scatter Pass
How do you compute each element’s bucket index and append it?
Sorting Each Bucket
How do you run the inner sort over every non-empty bucket?
The Gather Pass & Return
How do you walk the buckets in order and write elements back to produce the sorted output?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Quickselect
What It Is & When to Use It
Why find the \( k \)-th smallest element in expected \( O(n) \) instead of fully sorting in \( O(n\log n) \) when you only need ONE order statistic, and what problems (median, top-k boundary, percentile) does that buy you?
Selection vs. Sorting
Sorting computes the position of every element; selection asks for only one — why is answering the smaller question fundamentally cheaper, and what work does quickselect skip that quicksort cannot?
The Mental Model of Narrowing on One Side
How do you picture each partition as throwing away a chunk you have proven cannot contain the \( k \)-th element, so the live region keeps shrinking toward the answer?
Partition: the Shared Engine
How does the same partition step that powers quicksort place one pivot in its final sorted position and split the rest into a “smaller” group and a “larger” group, and why is the pivot’s landing index the key fact you read off?
Why You Recurse Into Only One Side
After partitioning, the pivot’s final index \( p \) is compared to \( k \) — why does that tell you which side holds the target, and why can you discard the other half entirely, unlike quicksort which must recurse on both?
Iterative vs. Recursive Formulation
Since there is only ever one recursive call (tail position), how can you rewrite quickselect as a loop that updates lo and hi instead of recursing, and what does that save?
Pivot Choice
Why does a fixed pivot (always first or last) degrade on sorted or reverse-sorted input, and how does a random pivot — or median-of-three — restore good expected behavior?
Median of Medians
How does choosing the pivot as the “median of group-of-five medians” guarantee a constant-fraction split and a worst-case \( O(n) \) bound — and why is it usually slower in practice despite the stronger guarantee, so randomized quickselect is preferred?
Trade-offs vs. Sorting & Heaps
When is a heap-based “top-k” or a full sort actually the right tool instead of quickselect — repeated queries, streaming data, or needing the elements in order rather than just the boundary?
Time Complexity
Why does recursing on one shrinking side give a different total cost than quicksort’s two-sided recursion?
Best Case
What pivot luck lets the target sit at the first partition’s pivot index, returning in a single \( O(n) \) pass?
Average Case
With random pivots the work shrinks geometrically — why does the recurrence \( T(n) = T(n/2) + O(n) \) sum to \( O(n) \) rather than \( O(n\log n) \), since \( n + n/2 + n/4 + \dots = 2n \)?
Worst Case
Why is the worst case \( O(n^2) \) with adversarial pivots that peel off one element at a time, and why does randomization make hitting that astronomically unlikely?
Space Complexity
Why is partitioning in-place \( O(1) \) extra, and what is the recursion-depth cost — \( O(\log n) \) expected versus \( O(n) \) worst-case stack, and why does the iterative form drop it to \( O(1) \)?
Common Bugs & Edge Cases
Where do bugs hide — 0- vs 1-indexed \( k \), duplicate pivots clustering on one side, off-by-one bounds in partition, or arrays of size zero or one?
Implementation Walkthrough
Break the algorithm into the parts you must get right before you write a line.
Setup
What bounds (lo, hi) and target rank do you initialize, and how do you translate the requested \( k \)-th smallest into a 0-indexed target position?
Partition & the Recurse-One-Side Step
How does partition return the pivot index, and how do you compare it to the target to decide whether to keep the left side, the right side, or stop because the pivot IS the answer?
The Tricky Step
How do you keep partition balanced against duplicates and sorted input — random pivot selection, swapping the pivot into place — without corrupting the bounds you pass onward?
Termination & Return
What condition signals “found” (pivot index equals target), and what value do you return when the live region narrows to a single element?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Searching & Sorting: Problem Set
These problems drill the core toolkit of this module: binary search and its
generalization to “binary search on the answer,” comparison and non-comparison
sorting, selection (finding the k-th element without a full sort), custom
comparators, and ranking. Each problem is an implementation task — fill in the
stub in problemset/ and make its test in tests/problemset/ pass. The
Foundational problems isolate one technique at a time; the Applied
Problems weave those techniques into LeetCode and contest-style challenges,
ordered roughly easy to hard.
Foundational
Problem 1: Classic Binary Search
LeetCode: 704. Binary Search
Description
Given an array nums sorted in ascending order with all distinct values, and a
target value target, return the index of target in nums, or -1 if it is
not present. You must run in O(log n) time, so do not scan the array linearly.
Examples
Example 1:
Input: nums = [-1, 0, 3, 5, 9, 12], target = 9
Output: 4
9 lives at index 4.
Example 2:
Input: nums = [-1, 0, 3, 5, 9, 12], target = 2
Output: -1
2 does not appear in nums.
Example 3:
Input: nums = [5], target = 5
Output: 0
Constraints
1 <= nums.length <= 10^4-10^4 < nums[i], target < 10^4- All values in
numsare distinct and sorted ascending.
Problem 2: Search Insert Position
LeetCode: 35. Search Insert Position
Description
Given a sorted array of distinct integers and a target value, return the index
where the target is found. If it is not found, return the index where it would
be inserted to keep the array sorted. You must run in O(log n) time.
Examples
Example 1:
Input: nums = [1, 3, 5, 6], target = 5
Output: 2
5 is found at index 2.
Example 2:
Input: nums = [1, 3, 5, 6], target = 2
Output: 1
2 would be inserted between 1 and 3, at index 1.
Example 3:
Input: nums = [1, 3, 5, 6], target = 7
Output: 4
7 is larger than every element, so it goes at the end.
Constraints
1 <= nums.length <= 10^4-10^4 <= nums[i], target <= 10^4numscontains distinct values sorted in ascending order.
Problem 3: Find First and Last Position
LeetCode: 34. Find First and Last Position of Element in Sorted Array
Description
Given an array of integers sorted in non-decreasing order, find the starting and
ending index of a given target value, returned as a two-element array
[first, last]. If the target is not found, return [-1, -1]. You must run in
O(log n) time, so binary-search the two boundaries separately rather than
scanning the run.
Examples
Example 1:
Input: nums = [5, 7, 7, 8, 8, 10], target = 8
Output: [3, 4]
The run of 8s spans indices 3 through 4.
Example 2:
Input: nums = [5, 7, 7, 8, 8, 10], target = 6
Output: [-1, -1]
6 is absent.
Example 3:
Input: nums = [], target = 0
Output: [-1, -1]
Constraints
0 <= nums.length <= 10^5-10^9 <= nums[i], target <= 10^9numsis sorted in non-decreasing order.
Problem 4: Merge Two Sorted Arrays
LeetCode: 88. Merge Sorted Array
Description
Given two arrays a and b, each sorted in non-decreasing order, return a
single new array containing all elements of both, sorted in non-decreasing
order. Do not call a library sort — merge the two runs with a two-pointer scan
in O(m + n) time.
Examples
Example 1:
Input: a = [1, 2, 3], b = [2, 5, 6]
Output: [1, 2, 2, 3, 5, 6]
Example 2:
Input: a = [], b = [1]
Output: [1]
One input is empty, so the result is the other.
Example 3:
Input: a = [4, 4], b = [1, 4]
Output: [1, 4, 4, 4]
Duplicates across the two arrays are all kept.
Constraints
0 <= a.length, b.length <= 10^5-10^9 <= a[i], b[i] <= 10^9- Both
aandbare sorted in non-decreasing order.
Problem 5: Kth Smallest Element (Quickselect)
LeetCode: 215. Kth Largest Element in an Array
Description
Given an integer array nums and an integer k (1-indexed), return the k-th
smallest element in the array. Aim for average linear time using a
quickselect-style partition — do not perform a full sort. Note this asks for the
k-th smallest: k = 1 is the minimum, k = nums.length is the maximum.
Examples
Example 1:
Input: nums = [3, 2, 1, 5, 6, 4], k = 2
Output: 2
The sorted order is [1, 2, 3, 4, 5, 6]; the 2nd smallest is 2.
Example 2:
Input: nums = [3, 2, 3, 1, 2, 4, 5, 5, 6], k = 4
Output: 3
Duplicates count toward the rank.
Example 3:
Input: nums = [7], k = 1
Output: 7
Constraints
1 <= k <= nums.length <= 10^5-10^4 <= nums[i] <= 10^4
Problem 6: Sort Colors (Dutch National Flag)
LeetCode: 75. Sort Colors
Description
Given an array nums containing only the values 0, 1, and 2 (representing
red, white, and blue), sort it in place so all 0s come first, then all 1s,
then all 2s. Do it in a single pass with O(1) extra space using the
three-way (Dutch national flag) partition. Return the same array after sorting.
Examples
Example 1:
Input: nums = [2, 0, 2, 1, 1, 0]
Output: [0, 0, 1, 1, 2, 2]
Example 2:
Input: nums = [2, 0, 1]
Output: [0, 1, 2]
Example 3:
Input: nums = [0]
Output: [0]
Constraints
1 <= nums.length <= 300nums[i]is0,1, or2.
Applied Problems
Problem 7: First Bad Version
LeetCode: 278. First Bad Version
Description
You are managing n versions 1..n of a product. Each version is built from the
previous one, and once a version is bad every later version is bad too. You are
given the first bad version index bad (the smallest index that is bad). Using
only the monotone predicate “is version v bad?” (v >= bad), return the first
bad version while making O(log n) predicate checks. Binary-search the boundary
between good and bad.
Examples
Example 1:
Input: n = 5, bad = 4
Output: 4
Versions 4 and 5 are bad; 4 is the first.
Example 2:
Input: n = 1, bad = 1
Output: 1
Example 3:
Input: n = 10, bad = 1
Output: 1
Every version is bad, so the first is 1.
Constraints
1 <= bad <= n <= 2^31 - 1- The predicate is monotone: if version
vis bad, every version> vis bad.
Problem 8: Single Element in a Sorted Array
LeetCode: 540. Single Element in a Sorted Array
Description
You are given a sorted array where every element appears exactly twice except for
one element that appears exactly once. Return that single element. You must run
in O(log n) time and O(1) space — binary-search on the parity of indices.
Before the lone element the first of each pair sits at an even index; after it
the pattern flips.
Examples
Example 1:
Input: nums = [1, 1, 2, 3, 3, 4, 4, 8, 8]
Output: 2
Example 2:
Input: nums = [3, 3, 7, 7, 10, 11, 11]
Output: 10
Example 3:
Input: nums = [5]
Output: 5
A single element is itself the unpaired one.
Constraints
1 <= nums.length <= 10^5, andnums.lengthis odd.0 <= nums[i] <= 10^5numsis sorted in non-decreasing order with exactly one unpaired value.
Problem 9: Find Peak Element
LeetCode: 162. Find Peak Element
Description
A peak element is one that is strictly greater than its neighbors. Given an array
nums where nums[i] != nums[i+1] for all valid i, return the index of any
peak. Imagine nums[-1] and nums[n] are both negative infinity, so the ends
can be peaks. You must run in O(log n) time by always walking toward the higher
neighbor.
Examples
Example 1:
Input: nums = [1, 2, 3, 1]
Output: 2
3 at index 2 is greater than both neighbors.
Example 2:
Input: nums = [1, 2, 1, 3, 5, 6, 4]
Output: 5
Index 5 (value 6) is a peak; index 1 (value 2) would also be acceptable.
Example 3:
Input: nums = [1]
Output: 0
Constraints
1 <= nums.length <= 1000-2^31 <= nums[i] <= 2^31 - 1nums[i] != nums[i + 1]for all validi.
Problem 10: Search in Rotated Sorted Array
LeetCode: 33. Search in Rotated Sorted Array
Description
An ascending sorted array of distinct integers is rotated at an unknown pivot, so
[0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]. Given the rotated array nums
and a target, return the index of target, or -1 if it is absent. You must
run in O(log n) time — find the target directly with a modified binary search,
without first locating the pivot in a separate pass.
Examples
Example 1:
Input: nums = [4, 5, 6, 7, 0, 1, 2], target = 0
Output: 4
Example 2:
Input: nums = [4, 5, 6, 7, 0, 1, 2], target = 3
Output: -1
Example 3:
Input: nums = [1], target = 0
Output: -1
Constraints
1 <= nums.length <= 5000-10^4 <= nums[i], target <= 10^4- All values are distinct;
numswas originally ascending then rotated.
Problem 11: Find Minimum in Rotated Sorted Array
LeetCode: 153. Find Minimum in Rotated Sorted Array
Description
An ascending sorted array of distinct integers has been rotated at an unknown
pivot. Return the minimum element. You must run in O(log n) time by comparing
the midpoint against the right end to decide which half holds the rotation point.
Examples
Example 1:
Input: nums = [3, 4, 5, 1, 2]
Output: 1
Example 2:
Input: nums = [4, 5, 6, 7, 0, 1, 2]
Output: 0
Example 3:
Input: nums = [11, 13, 15, 17]
Output: 11
A zero-rotation array still returns its first element.
Constraints
1 <= nums.length <= 5000-5000 <= nums[i] <= 5000- All values are distinct.
Problem 12: Search a 2D Matrix
LeetCode: 74. Search a 2D Matrix
Description
You are given an m x n matrix in which each row is sorted left to right, and
the first integer of each row is greater than the last integer of the previous
row. Given a target, return whether it appears in the matrix. You must run in
O(log(m * n)) time — treat the matrix as one flattened sorted array and binary
search it, mapping a flat index back to (row, col).
Examples
Example 1:
Input: matrix = [[1, 3, 5, 7], [10, 11, 16, 20], [23, 30, 34, 60]], target = 3
Output: true
Example 2:
Input: matrix = [[1, 3, 5, 7], [10, 11, 16, 20], [23, 30, 34, 60]], target = 13
Output: false
Example 3:
Input: matrix = [[1]], target = 1
Output: true
Constraints
1 <= m, n <= 100-10^4 <= matrix[i][j], target <= 10^4- The whole matrix, read row by row, is in ascending order.
Problem 13: Kth Largest Element
LeetCode: 215. Kth Largest Element in an Array
Description
Given an integer array nums and an integer k, return the k-th largest
element (1-indexed), where k = 1 is the maximum. This is the rank in sorted
order, not the k-th distinct value, so duplicates count. Solve it without a
full sort — a quickselect or a size-k heap both meet the bar.
Examples
Example 1:
Input: nums = [3, 2, 1, 5, 6, 4], k = 2
Output: 5
The sorted order is [1, 2, 3, 4, 5, 6]; the 2nd largest is 5.
Example 2:
Input: nums = [3, 2, 3, 1, 2, 4, 5, 5, 6], k = 4
Output: 4
Example 3:
Input: nums = [1], k = 1
Output: 1
Constraints
1 <= k <= nums.length <= 10^5-10^4 <= nums[i] <= 10^4
Problem 14: Top K Frequent Elements
LeetCode: 347. Top K Frequent Elements
Description
Given an integer array nums and an integer k, return the k most frequent
elements, ordered from most frequent to least frequent. The answer is guaranteed
to be unique (no ties at the cutoff). Tally counts, then select the top k by
frequency using a heap or a bucket-by-count pass.
Examples
Example 1:
Input: nums = [1, 1, 1, 2, 2, 3], k = 2
Output: [1, 2]
1 appears three times, 2 appears twice.
Example 2:
Input: nums = [1], k = 1
Output: [1]
Example 3:
Input: nums = [4, 4, 4, 5, 5, 6], k = 2
Output: [4, 5]
Constraints
1 <= nums.length <= 10^5kis between1and the number of distinct elements.- The set of top
kfrequent elements is unique.
Problem 15: H-Index
LeetCode: 274. H-Index
Description
Given an array citations where citations[i] is the number of citations a
researcher’s i-th paper received, return the researcher’s h-index: the largest
value h such that at least h papers each have at least h citations. Sort
the citations and scan, or count by citation value.
Examples
Example 1:
Input: citations = [3, 0, 6, 1, 5]
Output: 3
Three papers have at least 3 citations each (3, 6, 5), and the rest have
no more than 3.
Example 2:
Input: citations = [1, 3, 1]
Output: 1
Example 3:
Input: citations = [0]
Output: 0
A single uncited paper gives an h-index of 0.
Constraints
1 <= citations.length <= 50000 <= citations[i] <= 1000
Problem 16: Relative Ranks
LeetCode: 506. Relative Ranks
Description
You are given an array score of distinct athletes’ scores. Athletes are ranked
by score descending. Return an array answer where answer[i] is the rank of
athlete i as a string: the top three scorers get "Gold Medal", "Silver Medal", and "Bronze Medal"; everyone else gets their 1-indexed placement as a
string ("4", "5", …). Sort indices by score with a custom comparator, then
assign labels by rank.
Examples
Example 1:
Input: score = [5, 4, 3, 2, 1]
Output: ["Gold Medal", "Silver Medal", "Bronze Medal", "4", "5"]
Example 2:
Input: score = [10, 3, 8, 9, 4]
Output: ["Gold Medal", "5", "Bronze Medal", "Silver Medal", "4"]
10 is 1st, 9 is 2nd, 8 is 3rd, 4 is 4th, 3 is 5th.
Example 3:
Input: score = [1]
Output: ["Gold Medal"]
Constraints
1 <= score.length <= 10^40 <= score[i] <= 10^6- All scores are distinct.
Problem 17: Sort Array by Increasing Frequency
LeetCode: 1636. Sort Array by Increasing Frequency
Description
Given an array nums, sort it in increasing order of each value’s frequency. If
two values have the same frequency, sort those values in decreasing order.
Return the rearranged array. This is a classic custom-comparator problem: the
primary key is the count, the tiebreak is the value (reversed).
Examples
Example 1:
Input: nums = [1, 1, 2, 2, 2, 3]
Output: [3, 1, 1, 2, 2, 2]
3 has frequency 1, 1 has frequency 2, 2 has frequency 3.
Example 2:
Input: nums = [2, 3, 1, 3, 2]
Output: [1, 3, 3, 2, 2]
2 and 3 both have frequency 2, so the larger value 3 comes first.
Example 3:
Input: nums = [-1, 1, -6, 4, 5, -6, 1, 4, 1]
Output: [5, -1, 4, 4, -6, -6, 1, 1, 1]
Constraints
1 <= nums.length <= 100-100 <= nums[i] <= 100
Problem 18: Count Inversions
Description
Given an array nums, count the number of inversions: pairs of indices
(i, j) with i < j and nums[i] > nums[j]. A naive double loop is O(n^2);
you must do better by piggybacking the count onto a merge sort, accumulating
cross-pair inversions during each merge step for O(n log n) total. Return the
count as a long, since it can be as large as about n^2 / 2.
Examples
Example 1:
Input: nums = [2, 4, 1, 3, 5]
Output: 3
The inversions are (2,1), (4,1), and (4,3).
Example 2:
Input: nums = [5, 4, 3, 2, 1]
Output: 10
Every pair is inverted: 5 choose 2 = 10.
Example 3:
Input: nums = [1, 2, 3]
Output: 0
An already-sorted array has no inversions.
Constraints
1 <= nums.length <= 10^5-10^9 <= nums[i] <= 10^9- The answer can exceed a 32-bit integer; return a
long.
Problem 19: Find the Duplicate Number
LeetCode: 287. Find the Duplicate Number
Description
Given an array nums of n + 1 integers where each value is in the range
[1, n], exactly one value is repeated (possibly more than once). Return that
repeated value. Solve it without modifying the array and in better than O(n^2)
time — binary-searching on the value range and counting how many elements are
<= mid is one clean approach (by the pigeonhole principle, the count crosses
the threshold at the duplicate).
Examples
Example 1:
Input: nums = [1, 3, 4, 2, 2]
Output: 2
Example 2:
Input: nums = [3, 1, 3, 4, 2]
Output: 3
Example 3:
Input: nums = [2, 2, 2, 2, 2]
Output: 2
A value may repeat many times; still return that value.
Constraints
1 <= n <= 10^5, andnums.length == n + 1.1 <= nums[i] <= n- Exactly one value is repeated.
Problem 20: Merge Intervals
LeetCode: 56. Merge Intervals
Description
Given an array of intervals where intervals[i] = [start_i, end_i], merge all
overlapping intervals and return the non-overlapping intervals that cover all the
input, sorted by start. Two intervals overlap when one’s start is <= the
other’s end (touching endpoints count as overlapping). Sort by start first, then
sweep.
Examples
Example 1:
Input: intervals = [[1, 3], [2, 6], [8, 10], [15, 18]]
Output: [[1, 6], [8, 10], [15, 18]]
[1,3] and [2,6] overlap and merge into [1,6].
Example 2:
Input: intervals = [[1, 4], [4, 5]]
Output: [[1, 5]]
Touching at 4 is treated as overlapping.
Example 3:
Input: intervals = [[1, 4], [0, 4]]
Output: [[0, 4]]
Constraints
1 <= intervals.length <= 10^4intervals[i].length == 2and0 <= start_i <= end_i <= 10^4
Problem 21: K Closest Numbers
LeetCode: 658. Find K Closest Elements
Description
Given a sorted array arr, an integer k, and an integer x, return the k
elements closest to x, as a list sorted in ascending order. An element a is
closer to x than b if |a - x| < |b - x|, or if they tie and a < b. Binary
search for a starting window of width k and shrink toward x from both ends.
Examples
Example 1:
Input: arr = [1, 2, 3, 4, 5], k = 4, x = 3
Output: [1, 2, 3, 4]
3 is exact; among the rest, 2 and 4 tie, and the smaller 2 (and then 1)
fill the window.
Example 2:
Input: arr = [1, 2, 3, 4, 5], k = 4, x = -1
Output: [1, 2, 3, 4]
Example 3:
Input: arr = [1, 1, 1, 10, 10, 10], k = 1, x = 9
Output: [10]
Constraints
1 <= k <= arr.length <= 10^4arris sorted in ascending order.-10^4 <= arr[i], x <= 10^4
Problem 22: Koko Eating Bananas
LeetCode: 875. Koko Eating Bananas
Description
Koko has piles of bananas and h hours before the guards return. Each hour she
picks one pile and eats up to speed bananas from it; if the pile has fewer she
eats the whole pile and stops for that hour (she cannot move to another pile the
same hour). Return the minimum integer eating speed speed such that she can
finish all the bananas within h hours. The “can she finish at speed s?”
predicate is monotone, so binary-search the answer over [1, max(piles)].
Examples
Example 1:
Input: piles = [3, 6, 7, 11], h = 8
Output: 4
At speed 4 the hours used are 1 + 2 + 2 + 3 = 8.
Example 2:
Input: piles = [30, 11, 23, 4, 20], h = 5
Output: 30
With exactly as many hours as piles, she must clear each pile in one hour.
Example 3:
Input: piles = [30, 11, 23, 4, 20], h = 6
Output: 23
Constraints
1 <= piles.length <= 10^4piles.length <= h <= 10^91 <= piles[i] <= 10^9
Problem 23: Capacity to Ship Packages Within D Days
LeetCode: 1011. Capacity To Ship Packages Within D Days
Description
A conveyor belt has weights of packages that must ship in the given order over
days days. Each day a contiguous prefix of the remaining packages is loaded, and
the day’s total cannot exceed the ship’s fixed capacity. Packages cannot be
reordered or split. Return the least capacity that ships everything within days
days. The feasibility predicate is monotone in capacity, so binary-search it over
[max(weights), sum(weights)].
Examples
Example 1:
Input: weights = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], days = 5
Output: 15
The split is [1..5] [6,7] [8] [9] [10], each day’s load at most 15.
Example 2:
Input: weights = [3, 2, 2, 4, 1, 4], days = 3
Output: 6
Example 3:
Input: weights = [1, 2, 3, 1, 1], days = 4
Output: 3
Constraints
1 <= days <= weights.length <= 5 * 10^41 <= weights[i] <= 500
Problem 24: Split Array Largest Sum
LeetCode: 410. Split Array Largest Sum
Description
Given an array nums and an integer k, split nums into k non-empty
contiguous subarrays so that the largest subarray sum among them is minimized.
Return that minimized largest sum. Binary-search the answer over
[max(nums), sum(nums)]: for a candidate cap, greedily count how many subarrays
are needed and check whether it is at most k.
Examples
Example 1:
Input: nums = [7, 2, 5, 10, 8], k = 2
Output: 18
The best split is [7, 2, 5] and [10, 8], whose larger sum is 18.
Example 2:
Input: nums = [1, 2, 3, 4, 5], k = 2
Output: 9
Split as [1, 2, 3, 4] and [5]? No — [1,2,3] and [4,5] gives 9.
Example 3:
Input: nums = [1, 4, 4], k = 3
Output: 4
Each element is its own subarray.
Constraints
1 <= nums.length <= 10000 <= nums[i] <= 10^61 <= k <= nums.length
Problem 25: Median of Two Sorted Arrays
LeetCode: 4. Median of Two Sorted Arrays
Description
Given two sorted arrays a and b of sizes m and n, return the median of
their combined sorted order as a double. The overall run time must be
O(log(min(m, n))) — binary-search a partition of the smaller array so the left
halves of both arrays together form the lower half of the merge.
Examples
Example 1:
Input: a = [1, 3], b = [2]
Output: 2.0
The merged array is [1, 2, 3]; the median is 2.
Example 2:
Input: a = [1, 2], b = [3, 4]
Output: 2.5
The merged array is [1, 2, 3, 4]; the median is (2 + 3) / 2 = 2.5.
Example 3:
Input: a = [], b = [1]
Output: 1.0
Constraints
0 <= m, n <= 1000and1 <= m + n <= 2000-10^6 <= a[i], b[i] <= 10^6- Both arrays are sorted ascending.
Problem 26: Maximum Gap
LeetCode: 164. Maximum Gap
Description
Given an unsorted array nums, return the maximum difference between two
successive elements in its sorted form. If the array has fewer than two elements,
return 0. You must run in linear time and use linear extra space — a comparison
sort is O(n log n), so use bucketing (pigeonhole on the value range) instead.
Examples
Example 1:
Input: nums = [3, 6, 9, 1]
Output: 3
Sorted: [1, 3, 6, 9]; the largest successive gap is 3 (between 3 and 6,
or 6 and 9).
Example 2:
Input: nums = [10]
Output: 0
Fewer than two elements gives 0.
Example 3:
Input: nums = [1, 1, 1]
Output: 0
Constraints
1 <= nums.length <= 10^50 <= nums[i] <= 10^9
Problem 27: Leaderboard Sort
Description
A game leaderboard stores (score, name) entries given as parallel arrays
scores and names of equal length. Rank entries by score descending, breaking
ties by name ascending (lexicographic), and return the names in final ranked
order. Implement the ordering with a single stable sort driven by a custom
comparator over the entry indices.
Examples
Example 1:
Input: scores = [50, 80, 80], names = ["amy", "carol", "bob"]
Output: ["bob", "carol", "amy"]
carol and bob tie at 80; bob sorts before carol by name.
Example 2:
Input: scores = [10, 20, 30], names = ["x", "y", "z"]
Output: ["z", "y", "x"]
Example 3:
Input: scores = [5], names = ["solo"]
Output: ["solo"]
Constraints
1 <= names.length == scores.length <= 10^50 <= scores[i] <= 10^9- Names are non-empty lowercase strings; names may repeat but
(score, name)pairs are distinct.
Problem 28: External K-Way Merge
Description
You are given k already-sorted integer streams (as a list of arrays) and must
merge them into one ascending array. Simulating external merge, use a min-heap
keyed on the current front of each stream so the total work is O(N log k),
where N is the total element count — do not concatenate and re-sort. Return the
merged array.
Examples
Example 1:
Input: streams = [[1, 4, 5], [1, 3, 4], [2, 6]]
Output: [1, 1, 2, 3, 4, 4, 5, 6]
Example 2:
Input: streams = [[], [1]]
Output: [1]
Empty streams contribute nothing.
Example 3:
Input: streams = []
Output: []
Constraints
0 <= k <= 10^4streams.- The total number of elements
Nis at most10^5. - Each stream is individually sorted ascending; values fit in a 32-bit int.
Problem 29: Wiggle Sort
LeetCode: 324. Wiggle Sort II
Description
Given an array nums, reorder it in place so that
nums[0] < nums[1] > nums[2] < nums[3] ... (strictly alternating). It is
guaranteed that a valid arrangement exists. Return the reordered array. A clean
approach finds the median (via selection) and interleaves the lower and upper
halves so equal values never land adjacent.
Examples
Example 1:
Input: nums = [1, 5, 1, 1, 6, 4]
Output: [1, 6, 1, 5, 1, 4]
Any output satisfying the strict wiggle pattern is accepted.
Example 2:
Input: nums = [1, 3, 2, 2, 3, 1]
Output: [2, 3, 1, 3, 1, 2]
Example 3:
Input: nums = [1, 2]
Output: [1, 2]
Constraints
1 <= nums.length <= 5 * 10^40 <= nums[i] <= 5000- A valid wiggle arrangement is guaranteed to exist.
Problem 30: Sliding Window Median
LeetCode: 480. Sliding Window Median
Description
Given an integer array nums and a window size k, return the median of each
contiguous window as it slides one position at a time, as an array of doubles.
For odd k the median is the middle value; for even k it is the average of the
two middle values. Maintain the window’s order efficiently (for example with two
balanced heaps or an ordered multiset) so each median is cheap.
Examples
Example 1:
Input: nums = [1, 3, -1, -3, 5, 3, 6, 7], k = 3
Output: [1.0, -1.0, -1.0, 3.0, 5.0, 6.0]
The windows are [1,3,-1], [3,-1,-3], … and their medians follow.
Example 2:
Input: nums = [1, 2, 3, 4], k = 2
Output: [1.5, 2.5, 3.5]
Even k averages the two middle values per window.
Example 3:
Input: nums = [5], k = 1
Output: [5.0]
Constraints
1 <= k <= nums.length <= 10^5-2^31 <= nums[i] <= 2^31 - 1
Binary Trees
What Problem Binary Trees Solve
Why branch a structure two ways instead of laying data out linearly — what does hierarchy buy you over an array or list?
Structure and the Node Invariant
Each node has at most two children labeled left and right — why is the left/right labeling meaningful even before any sort rule is imposed?
Anatomy: Root, Leaves, Internal Nodes
Naming the Parts
Name every part — root, parent, child, sibling, leaf, internal node — what exactly makes a node a leaf?
Degree and Edges
What is the degree of a node, and why does a tree with \( n \) nodes always have exactly \( n-1 \) edges?
Depth, Height, and Levels
How do depth (measured top-down) and height (measured bottom-up) differ, and how many nodes can live at level \( d \)?
Tree Shapes and Why They Matter
Full, Complete, and Perfect
Sketch one of each — what exact condition defines each, and which one packs into a flat array with no gaps?
Balanced vs Degenerate
What makes one tree “bushy” and another a “stick,” and why does this single distinction dominate every cost downstream?
Representations
Linked Nodes with Child References
How does a node object hold its two children, and where do the null links sit?
Implicit Array Layout
For index \( i \), where do its children and parent live, and which tree shapes waste no slots?
Traversals
Depth-First: Pre / In / Post Order
Trace each on the same tree — what changes is only WHEN you visit the node relative to its two recursive calls?
Level Order (Breadth-First)
Which structure feeds the visit order here, and what makes it fundamentally different from the DFS family?
Recursive vs Iterative Walks
When does the call stack betray you on a deep tree, and how does an explicit stack (DFS) or queue (BFS) replace it?
Building and Reconstructing Trees
Given two traversals, why can you rebuild the tree from pre+in but not from pre+post alone?
Time Complexity
Traversal Cost
Why is every full traversal \( \Theta(n) \) — what is each node charged for exactly once, and why can’t you do better when you must visit all nodes?
Single-Path Descent
Why does following one root-to-leaf path cost \( O(h) \), and what is \( h \) for a balanced vs a degenerate tree?
Search Without an Ordering
With no sort rule to prune branches, why does finding an arbitrary key still cost \( O(n) \) in the worst case?
Space Complexity
Node Storage
Why is the structure itself \( \Theta(n) \), and what per-node overhead do the two child pointers add?
Recursion Call Stack
Why does a recursive walk hold \( O(h) \) frames at peak — and why is that \( O(n) \) for a stick but \( O(\log n) \) when balanced?
Iterative Auxiliary Structures
How much extra memory does the explicit DFS stack or BFS queue hold at its widest point?
Implementation Walkthrough
Node Layout
What fields does a node need, and how do you represent an absent child?
Traversal Routines
For each DFS order, where does the visit statement sit relative to the two recursive calls — and how do you rewrite one with an explicit stack?
Level-Order with a Queue
How do you enqueue children as you dequeue a parent, and how do you detect where one level ends and the next begins?
Reconstruction
How do you locate the root in one traversal and use it to split the other into left and right subtrees?
Common Pitfalls
What goes wrong when you forget the null check, mutate during traversal, or confuse height with depth?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Binary Search Trees
Why Order the Tree
What does enforcing a sort rule on a binary tree let you do that a plain binary tree cannot?
The BST Ordering Invariant
Stating the Rule
State the left-\( < \)-node-\( < \)-right rule — does it hold only between a node and its direct children, or for entire subtrees?
Why It Enables Pruning
Why does one comparison at each node let you discard a whole subtree, and what would break if the rule were only local?
Search
Walk a key down the tree — at each node, what single comparison tells you to go left, go right, or stop?
Insert
Why does an insert always land as a new leaf, and how do you find the parent it attaches to without breaking the invariant?
Delete
Leaf and One-Child Cases
Why are these the easy cases — what do you splice out and what do you reconnect?
Deleting a Node with Two Children
Find the in-order successor — why does swapping with it preserve the invariant, and why is the successor guaranteed to have at most one child?
In-Order Traversal Yields Sorted Output
Why does visiting left-node-right print keys in ascending order, and what does that imply about how a BST relates to a sorted array?
Successor, Predecessor, and Range Queries
Without sorting anything, how do you find the next-larger key or list every key in \( [lo, hi] \) by pruning out-of-range subtrees?
Height Drives Everything
Why does the cost of every operation ride on \( h \), and what makes \( h \) the single number that decides if a BST is fast or slow?
Degeneration into a Linked List
What insertion order turns a BST into a stick, and why is that the worst case for both height and every operation?
Why Balance Matters
What promise does a BST fail to keep on its own, and what do AVL/red-black trees add to rescue it?
Time Complexity
Search / Insert / Delete
Why is each one \( O(h) \), and what makes \( h \) range from \( \Theta(\log n) \) when balanced to \( \Theta(n) \) when degenerate?
Why Balanced Height Is Logarithmic (Intuition)
If each level roughly doubles the node count, why does a balanced tree of \( n \) nodes only need about \( \log_2 n \) levels — no formal proof, just the doubling intuition?
Traversal and Range Queries
Why is a full in-order walk \( \Theta(n) \), and why does a range query cost \( O(h + k) \) for \( k \) reported keys?
Space Complexity
Node Storage
Why is the tree \( \Theta(n) \), and what do the two child pointers (and optional parent pointer) cost per node?
Recursion Call Stack
Why do recursive search/insert/delete use \( O(h) \) stack space — and why does that collapse to \( O(\log n) \) only when the tree stays balanced?
Implementation Walkthrough
Node Layout
What fields does a node hold, and do you store a parent pointer — what does each choice make easier or harder?
Search
How does the comparison at each node decide the next link to follow, and where does the loop terminate?
Insert
How do you descend to the correct null link and attach the new leaf there without losing the parent reference?
Delete and the Two-Child Case
How do you detect which of the three delete cases applies, and how do you locate and splice the in-order successor?
Common Pitfalls
Where do duplicate keys, the two-child delete, and a stale parent pointer trip people up?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
2-3 Trees
Why Allow Bigger Nodes
What problem with plain BSTs does a node that can hold two keys and grow from the leaves up actually fix?
2-Nodes and 3-Nodes
Key and Child Counts
How many keys and children can each node type hold, and how are the keys ordered within a 3-node?
Why Variable-Width Nodes
Why does letting a node flex between one and two keys give the tree room to stay perfectly balanced?
Perfect Balance Invariant
Every leaf sits at the same depth — what structural rule keeps all root-to-leaf paths exactly equal length, no matter the insertion order?
Searching Across Multiple Keys
At a 3-node with two keys, how do the keys carve the value range into three intervals, and which subtree do you descend into for a given key?
Insertion by Splitting Up
Absorbing into a 2-Node
Why is the lucky case just growing a 2-node into a 3-node with no structural change?
Overflow and Promotion
A node temporarily overflows to four keys (a “4-node”) — which key is pushed up to the parent, and what happens to the two leftover key groups?
Splits Propagating to the Root
When does a split cascade all the way up, and why is splitting the root the ONLY way the tree ever gains height?
Deletion and Underflow
Borrowing from a Sibling
When a leaf drops below one key, how does a key rotate down from the parent and up from a sibling to refill it?
Merging Siblings
When no sibling can spare a key, how do you fuse two nodes and pull a separator down — and how can this shrink the tree’s height?
Why It Grows From the Top, Not the Bottom
Contrast with a BST — why must a perfectly balanced tree add height at the root rather than dangling new depth off a leaf?
Time Complexity
Search
Why is search \( O(\log n) \), and how does scanning one or two keys per node add only a constant factor over a binary descent?
Insert and the Cost of Splits
Why is a single insert \( O(\log n) \) even when a split cascades — how many nodes can a cascade touch at most?
Delete and the Cost of Merges
Why does a borrow-or-merge chain on delete also stay within \( O(\log n) \) levels?
Why Height Stays Logarithmic (Intuition)
With every leaf at the same depth and each node branching 2 or 3 ways, why must the height sit between \( \log_3 n \) and \( \log_2 n \) — argue from branching, not a derivation?
Space Complexity
Node Storage
Why is total storage \( \Theta(n) \), and what extra slack do nodes carry by reserving room for a possible second key and third child?
Recursion and the Promotion Path
Why does insert/delete recursion use \( O(\log n) \) stack depth, and what does the promoted/merged key need carried back up?
Implementation Walkthrough
Node Layout
How do you model a node that may hold one or two keys and two or three children — fixed arrays with a count, or distinct node types?
Search
How does the per-node key scan pick among the two or three child links?
Insert with Split and Promotion
How do you return a promoted key and a new sibling up the recursion so the parent can absorb or split in turn?
Delete with Borrow and Merge
How do you detect underflow after removal and decide between borrowing from a sibling and merging?
Relationship to Red-Black Trees
How is a red-black tree just a 2-3 (or 2-3-4) tree wearing a binary disguise, with 3-nodes encoded as a red child?
Common Pitfalls
Where do people botch the split — losing the promoted key, mishandling the cascade, or breaking the equal-depth rule?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
AVL Trees
Why Self-Balancing
What failure mode of a plain BST does AVL exist to prevent, and what price does it pay for the guarantee?
Balance Factor Invariant
Defining the Balance Factor
Define \( bf = h_{left} - h_{right} \) — which values are legal at every node?
Why ±1 Is Enough
Why does capping every node’s imbalance at one keep the whole tree’s height close to \( \log n \) — argue from how much two near-equal subtrees can differ?
Search (Same as BST)
Why does lookup borrow the BST algorithm unchanged, and what does the maintained balance buy the search?
Insert then Rebalance
Walking back up from the inserted leaf, when does the first violated ancestor trigger a fix, and why does fixing that one node suffice for insertion?
Delete then Rebalance
Why might a single deletion force rotations at multiple levels, unlike insertion where one rotation ends it?
The Four Rotation Cases
LL and RR (Single Rotations)
When the imbalance is “straight” (heavy outer grandchild), why does one rotation in the opposite direction fix it?
LR and RL (Double Rotations)
When the imbalance is “bent” (heavy inner grandchild), why must you first straighten the child before the outer rotation?
Recognizing the Case
From the balance factors of the unbalanced node and its taller child, how do you read off which of the four cases you’re in?
Rotation Mechanics
Walk the pointer surgery of a single rotation — which three links move, who becomes the new subtree root, and how does the parent get reattached?
Tracking Heights Without Recomputing
How do you keep stored heights correct after a rotation in \( O(1) \) using only the children’s heights?
How Balance Is Maintained
Why does fixing the lowest unbalanced node restore the balance factor for the whole path above it on insert?
Time Complexity
Search / Insert / Delete
Why are all three \( O(\log n) \) in the worst case — what does the maintained balance guarantee about \( h \) that a plain BST cannot?
Cost of Rebalancing
Why is each rotation \( O(1) \), and why does insert do at most one rotation while delete may do up to \( O(\log n) \)?
Why Height Stays Logarithmic (Intuition)
Given the ±1 rule, why can’t an AVL tree of height \( h \) be “sparse” — intuitively, why does each extra level force a near-doubling of nodes?
Space Complexity
Node Storage and the Height Field
Why is the tree \( \Theta(n) \), and what does the per-node stored height (or balance factor) add?
Recursion Call Stack
Why does recursive insert/delete use \( O(\log n) \) stack space, and why is that bounded here when it isn’t for a plain BST?
Implementation Walkthrough
Node Layout
What fields beyond key and children does an AVL node need, and how do you compute height from null-safe child heights?
Search
Why is this identical to BST search, and where do you NOT touch heights?
Insert and Rebalance
After the recursive insert returns, how do you update height, compute the balance factor, and dispatch to the correct rotation?
Delete and Rebalance
Why must you keep checking balance at every ancestor on the way up after a delete, not stop at the first fix?
The Rotation Helpers
How do you write a single left/right rotation so that height updates and parent relinking happen in the right order?
vs Red-Black Trees
AVL is more rigidly balanced — when does that help read-heavy workloads and when does it cost you on write-heavy ones?
Common Bugs
Where do people slip — stale heights, the wrong rotation case, or forgetting to reattach the parent link?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Red-Black Trees
Why Approximate Balance
Why settle for “roughly balanced” instead of AVL’s strictness — what does looser balance buy on inserts and deletes?
The Five Properties
Listing the Rules
List all five color rules — which one bans two reds in a row and which one fixes the path lengths?
What Each Rule Is For
Map each property to the harm it prevents — which keep the tree from leaning, and which anchor the root and leaves?
Black-Height
Define the black-height of a node — why must it be the same down every path, and how is it the quantity that actually controls balance?
Why the Tree Stays Balanced (Intuition)
With equal black-heights on every path and no two reds in a row, why can the longest root-to-leaf path be at most twice the shortest — argue from the rules, no formal bound?
Search (Color-Blind)
Why does lookup ignore colors entirely and run exactly like a BST search?
Recoloring vs Rotation
When does flipping colors suffice to restore the rules, and when must you actually restructure the tree with a rotation?
Insertion Fix-Up Cases
Why Insert Red
Why is a new node always colored red, and which single property is the only one a red insert can violate?
Red Uncle (Recolor and Recurse)
Why does a red uncle let you just repaint and push the problem up two levels?
Black Uncle (Rotate and Recolor)
Why do the triangle (zig-zag) and line (zig-zig) sub-cases each need a rotation to settle the double-red?
Deletion and the Double-Black
Where the Extra Black Comes From
When you remove a black node, why does a “double-black” token appear, and what rule is it standing in for?
Pushing It Away
What are the sibling-color cases, and how does each one either borrow a black, recolor, or rotate the double-black out of existence?
How Balance Is Maintained
Why does keeping black-heights equal while limiting reds keep the structure within the 2x path bound through every insert and delete?
Time Complexity
Search / Insert / Delete
Why are all three \( O(\log n) \) given the bounded height, and why does the 2x-path bound still leave height logarithmic?
Cost of Fix-Up
Why does insert do at most two rotations and delete at most three, regardless of \( n \) — and why is the recoloring chain still \( O(\log n) \) but cheap?
vs AVL Rebalancing
Why does red-black’s looser balance mean fewer rotations per update, and where does that matter?
Space Complexity
Node Storage and the Color Bit
Why is the tree \( \Theta(n) \), and why is the per-node color just a single bit?
Recursion / Parent Pointers and the Sentinel
Why do iterative implementations keep parent pointers, and how does the single shared nil sentinel save space and simplify the cases?
Implementation Walkthrough
Node Layout and the Nil Sentinel
What fields does a node need, and how does one shared black nil node remove special-casing for null children?
Search
Why is this plain BST search, ignoring color entirely?
Insert and Fix-Up
After a BST-style red insert, how does the fix-up loop classify by the uncle’s color and apply recolor-or-rotate until the root?
Delete and Fix-Up
How do you track the replacing node’s original color and run the double-black resolution loop when a black node leaves?
Rotation Helpers
How do left/right rotations update parent links and the sentinel correctly so the fix-up cases compose cleanly?
vs AVL and 2-3-4 Trees
Why do libraries (TreeMap, std::map) favor red-black over AVL — and how does it mirror a 2-3-4 tree node-for-node?
Common Pitfalls
Where do the sentinel nil node, the root-stays-black rule, and case mislabeling cause bugs?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Splay Trees
Why Self-Adjusting
What does a splay tree optimize for that a balanced BST ignores — and why give up a strict height bound to get it?
Splaying: Move-to-Root
What does it mean to splay a node, and why bring the most-recently-touched key all the way to the top?
Zig, Zig-Zig, and Zig-Zag
Zig (Node Is a Child of the Root)
Why is this the base case, and when is it the very last step of a splay?
Zig-Zig (Same-Side Grandchild)
Why rotate the grandparent first — what makes this different from two plain zigs, and why does it shorten the access path more aggressively?
Zig-Zag (Opposite-Side Grandchild)
Why does this collapse like an AVL double rotation instead?
Why Splaying Rebalances (Intuition)
Why does pulling a deep node to the root roughly halve the depth of everything along its access path — what does that do to a long, skinny branch over repeated accesses?
Splay on Every Access
Why do search, insert, and delete all end by splaying — even a plain lookup, and even a failed one?
Insert and Delete via Splaying
How does splaying a key to the root turn insert into a split and delete into a join of the two resulting subtrees?
Amortized Analysis
What the Amortized Claim Promises
State what “amortized \( O(\log n) \)” guarantees — why is the average over a sequence good even when one operation isn’t?
The Potential Idea (Intuition)
Without grinding the algebra, why does an expensive deep access “pay” by flattening the tree and lowering future costs?
No Strict Height Bound
A single operation can be \( O(n) \) on a degenerate shape — why is that acceptable here, and when is it NOT acceptable?
Self-Adjusting and Locality
How does splaying exploit temporal locality and the working-set of recently accessed keys, and why can it beat a balanced tree on skewed access patterns?
Time Complexity
Per-Operation Worst Case
Why can a single search/insert/delete cost \( O(n) \), and what shape causes it?
Amortized Cost
Why is every operation \( O(\log n) \) amortized over a sequence, so \( m \) operations cost \( O(m \log n) \)?
Access-Pattern Sensitivity
Why does a tree accessed with strong locality beat the \( \log n \) average, approaching \( O(1) \) for a tiny working set?
Space Complexity
Node Storage
Why is the tree \( \Theta(n) \), and why does a splay tree need NO stored height, balance factor, or color?
Recursion / Stack Depth
Why can recursive splaying use \( O(n) \) stack space in the worst case, and how does an iterative bottom-up or top-down splay avoid that?
Implementation Walkthrough
Node Layout
What minimal fields does a splay node need, and what metadata can you deliberately leave out?
The Splay Routine
How do you detect zig vs zig-zig vs zig-zag from the node, its parent, and grandparent, and apply the right rotation pair?
Search via Splay
Why does search descend to the node (or its last-touched ancestor on a miss) and then splay it to the root?
Insert and Delete via Split / Join
How do you splay to expose the root, then split off or join the subtrees to finish the operation?
vs AVL and Red-Black Trees
No stored heights or colors and no worst-case guarantee — when does that simplicity and adaptivity win, and when does it lose?
Common Pitfalls
Where do people stumble — choosing the wrong zig case, forgetting to splay on a failed search, or blowing the recursion depth?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
B-Trees
Why Disk and Block Storage
How does a large branching factor minimize the number of expensive disk-block reads, and why is the cost model here “block transfers,” not comparisons?
Order / Minimum Degree \( t \)
Defining t
Define \( t \) — how many keys and children may a node hold, and what is special about the root’s lower bound?
Why Half-Full
Why does the “at least half-full” floor exist, and what would go wrong if nodes could get arbitrarily sparse?
Keys and Children Layout
With \( k \) keys, why are there exactly \( k+1 \) child pointers, and how do the keys partition the value range so each pointer covers one interval?
The B-Tree Invariants
All leaves at the same depth, every non-root at least half-full, keys sorted within a node — what do these rules guarantee together about balance and node utilization?
Search Within and Across Nodes
How does a key scan (linear or binary) inside one fat node combine with descending a single child pointer to reach the next block?
Split-on-Full Insert
The Proactive Split
Why split any full node you encounter on the way DOWN, before you ever need to — what problem does proactive splitting avoid on the way back up?
Promotion
When a full node splits, which median key is promoted to the parent, and how do the remaining keys divide into two nodes?
Deletion: Borrow and Merge
Borrowing from a Sibling
When a node underflows but an adjacent sibling has spare keys, how does a key rotate up through the parent and another down?
Merging Two Nodes
When neither sibling can spare a key, how do you fuse two nodes and pull the separator key down — and how can this shrink the height?
How Balance Is Maintained
Why does growing only by splitting the root and shrinking only by merging at the root keep every leaf on exactly the same level?
Time Complexity
Search
Why is search \( O(\log_t n) \) block reads with \( O(t) \) work per node — and why do we care far more about the block-read count than the in-node scan?
Insert
Why does proactive split-on-the-way-down keep insert to a single root-to-leaf pass of \( O(\log_t n) \) blocks?
Delete
Why does borrow/merge also stay within \( O(\log_t n) \) blocks, even when a merge cascades upward?
Why a High Branching Factor Flattens the Tree (Intuition)
If each node fans out to \( t \) children, why does height grow like \( \log_t n \) — and why does a large \( t \) make that height tiny in practice?
Space Complexity
Node Storage and Fill Factor
Why is total storage \( \Theta(n) \), and how does the half-full floor bound wasted space inside under-occupied nodes?
Recursion / Path Buffering
Why does a top-down insert or delete only need \( O(\log_t n) \) nodes “in flight,” and why does that matter when nodes are disk blocks?
Implementation Walkthrough
Node Layout
How do you model a node’s sorted key array, child-pointer array, and key count, and how do you mark a leaf?
Search
How does the in-node scan find the first key \( \ge \) the target and pick the child pointer to follow?
Insert with Proactive Split
How do you split a full child before descending into it, and how do you promote the median into the current node?
Delete with Borrow and Merge
How do you ensure a child has at least \( t \) keys before recursing into it, choosing borrow or merge as needed?
B-Tree vs B+ Tree
Why do databases store values only in leaves and chain the leaves together — what range and scan queries does that speed up?
When to Use It / Common Pitfalls
When does a B-tree beat an in-memory balanced BST, and where do node-full edge cases, the half-full rule, and off-by-one key/child counts trip you up?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Trees: Problem Set
Implementation tasks for querying binary trees. Every problem is solved by filling in a stub in problemset/ and making its test in tests/problemset/ pass. Problems operate on the shared TreeNode<Integer> type, where each node carries a value plus left and right children. The theme runs through tree queries: traversals (preorder/inorder/level-order), structural measures (height, diameter, balance), BST operations (search, validation, k-th smallest, range), aggregate queries (subtree sums, path sums), and ancestry (lowest common ancestor). Work the Foundational problems first to lock in traversal mechanics, then the Applied problems, which weave easier LeetCode-style queries through harder contest-style ones.
Foundational
Problem 1: Height of a Binary Tree
Description
Given the root of a binary tree, compute its height: the number of edges on the longest root-to-leaf path. An empty tree (null root) has height -1, and a single node has height 0. Solve it with one post-order recursion that combines the heights of the two subtrees.
Examples
Example 1:
Input: root = []
Output: -1
The empty tree has height -1 by convention.
Example 2:
Input: root = [1]
Output: 0
A single node has no edges below it.
Example 3:
Input: root = [1,2,null,3]
Output: 2
The chain 1 -> 2 -> 3 has two edges.
Constraints
- The number of nodes is in the range
[0, 10^4]. -10^4 <= Node.value <= 10^4.
Problem 2: Count Nodes
Description
Given the root of a binary tree, return the total number of nodes in it. An empty tree has 0 nodes. This is the simplest aggregate query: every node contributes 1, plus the counts of its two subtrees.
Examples
Example 1:
Input: root = []
Output: 0
Example 2:
Input: root = [1,2,3]
Output: 3
Example 3:
Input: root = [1,2,3,4,null,null,5]
Output: 5
Nodes are 1, 2, 3, 4, and 5.
Constraints
- The number of nodes is in the range
[0, 10^4].
Problem 3: Preorder Traversal
LeetCode: 144. Binary Tree Preorder Traversal
Description
Given the root of a binary tree, return its node values in preorder: visit the current node first, then recurse left, then recurse right. An empty tree produces an empty list.
Examples
Example 1:
Input: root = [1,null,2,3]
Output: [1,2,3]
Visit 1, then go right to 2, then 2’s left child 3.
Example 2:
Input: root = []
Output: []
Example 3:
Input: root = [1,2,3]
Output: [1,2,3]
Root 1, then left subtree (2), then right subtree (3).
Constraints
- The number of nodes is in the range
[0, 100]. -100 <= Node.value <= 100.
Problem 4: Inorder Traversal
LeetCode: 94. Binary Tree Inorder Traversal
Description
Given the root of a binary tree, return its node values in inorder: recurse left, visit the current node, then recurse right. For a binary search tree this yields the values in sorted order.
Examples
Example 1:
Input: root = [1,null,2,3]
Output: [1,3,2]
Example 2:
Input: root = []
Output: []
Example 3:
Input: root = [2,1,3]
Output: [1,2,3]
A BST’s inorder traversal is sorted.
Constraints
- The number of nodes is in the range
[0, 100]. -100 <= Node.value <= 100.
Problem 5: Validate a BST
LeetCode: 98. Validate Binary Search Tree
Description
Given the root of a binary tree, determine whether it is a valid binary search tree. A valid BST requires that every node’s value is strictly greater than all values in its left subtree and strictly less than all values in its right subtree. Pass down an open interval (lo, hi) to each node and check membership.
Examples
Example 1:
Input: root = [2,1,3]
Output: true
Example 2:
Input: root = [5,1,4,null,null,3,6]
Output: false
The node 3 lies in the right subtree of 5 but is less than 5.
Example 3:
Input: root = [1]
Output: true
Constraints
- The number of nodes is in the range
[1, 10^4]. -2^31 <= Node.value <= 2^31 - 1.
Problem 6: Level-Order Traversal
LeetCode: 102. Binary Tree Level Order Traversal
Description
Given the root of a binary tree, return its values grouped level by level from the root downward, left to right within each level. Use a breadth-first queue, processing one full level per outer iteration. An empty tree returns an empty list.
Examples
Example 1:
Input: root = [3,9,20,null,null,15,7]
Output: [[3],[9,20],[15,7]]
Example 2:
Input: root = [1]
Output: [[1]]
Example 3:
Input: root = []
Output: []
Constraints
- The number of nodes is in the range
[0, 2000]. -1000 <= Node.value <= 1000.
Applied Problems
Problem 7: Maximum Depth of a Binary Tree
LeetCode: 104. Maximum Depth of Binary Tree
Description
Given the root of a binary tree, return its maximum depth: the number of nodes along the longest path from the root down to the farthest leaf. An empty tree has depth 0.
Examples
Example 1:
Input: root = [3,9,20,null,null,15,7]
Output: 3
The path 3 -> 20 -> 15 has three nodes.
Example 2:
Input: root = [1,null,2]
Output: 2
Example 3:
Input: root = []
Output: 0
Constraints
- The number of nodes is in the range
[0, 10^4]. -100 <= Node.value <= 100.
Problem 8: Invert a Binary Tree
LeetCode: 226. Invert Binary Tree
Description
Given the root of a binary tree, invert it: swap the left and right child of every node, and return the new root. The mirror image of the original tree is produced.
Examples
Example 1:
Input: root = [4,2,7,1,3,6,9]
Output: [4,7,2,9,6,3,1]
Example 2:
Input: root = [2,1,3]
Output: [2,3,1]
Example 3:
Input: root = []
Output: []
Constraints
- The number of nodes is in the range
[0, 100]. -100 <= Node.value <= 100.
Problem 9: Same Tree
LeetCode: 100. Same Tree
Description
Given the roots of two binary trees p and q, return true if they are structurally identical and every corresponding pair of nodes holds the same value. Two empty trees are considered equal.
Examples
Example 1:
Input: p = [1,2,3], q = [1,2,3]
Output: true
Example 2:
Input: p = [1,2], q = [1,null,2]
Output: false
Same values but different structure.
Example 3:
Input: p = [], q = []
Output: true
Constraints
- The number of nodes in each tree is in the range
[0, 100]. -10^4 <= Node.value <= 10^4.
Problem 10: Symmetric Tree
LeetCode: 101. Symmetric Tree
Description
Given the root of a binary tree, return true if it is a mirror of itself about its center — that is, the left subtree is the mirror reflection of the right subtree. Compare the two subtrees in opposite order: left-of-one against right-of-other.
Examples
Example 1:
Input: root = [1,2,2,3,4,4,3]
Output: true
Example 2:
Input: root = [1,2,2,null,3,null,3]
Output: false
Example 3:
Input: root = [1]
Output: true
Constraints
- The number of nodes is in the range
[1, 1000]. -100 <= Node.value <= 100.
Problem 11: Diameter of a Binary Tree
LeetCode: 543. Diameter of Binary Tree
Description
Given the root of a binary tree, return the length in edges of the longest path between any two nodes. This path may or may not pass through the root. At each node, the longest path through it equals the sum of the heights of its two subtrees; track the maximum while computing heights bottom-up.
Examples
Example 1:
Input: root = [1,2,3,4,5]
Output: 3
The path 4 -> 2 -> 1 -> 3 (or 5 -> 2 -> 1 -> 3) has three edges.
Example 2:
Input: root = [1,2]
Output: 1
Example 3:
Input: root = [1]
Output: 0
Constraints
- The number of nodes is in the range
[1, 10^4]. -100 <= Node.value <= 100.
Problem 12: Balanced Binary Tree
LeetCode: 110. Balanced Binary Tree
Description
Given the root of a binary tree, determine whether it is height-balanced: for every node, the heights of its two subtrees differ by at most one. A single post-order pass that returns subtree heights (and short-circuits on imbalance) runs in linear time.
Examples
Example 1:
Input: root = [3,9,20,null,null,15,7]
Output: true
Example 2:
Input: root = [1,2,2,3,3,null,null,4,4]
Output: false
Example 3:
Input: root = []
Output: true
Constraints
- The number of nodes is in the range
[0, 5000]. -10^4 <= Node.value <= 10^4.
Problem 13: Path Sum
LeetCode: 112. Path Sum
Description
Given the root of a binary tree and an integer target, return true if the tree has a root-to-leaf path whose node values add up exactly to target. A leaf is a node with no children. An empty tree has no such path.
Examples
Example 1:
Input: root = [5,4,8,11,null,13,4,7,2], target = 22
Output: true
The path 5 -> 4 -> 11 -> 2 sums to 22.
Example 2:
Input: root = [1,2,3], target = 5
Output: false
Example 3:
Input: root = [], target = 0
Output: false
Constraints
- The number of nodes is in the range
[0, 5000]. -1000 <= Node.value <= 1000.
Problem 14: Minimum Depth of a Binary Tree
LeetCode: 111. Minimum Depth of Binary Tree
Description
Given the root of a binary tree, return its minimum depth: the number of nodes along the shortest path from the root to the nearest leaf. A leaf has no children. Beware the trap: a node with one child is not a leaf, so you must not take the missing child’s depth of zero.
Examples
Example 1:
Input: root = [3,9,20,null,null,15,7]
Output: 2
The nearest leaf is 9.
Example 2:
Input: root = [2,null,3,null,4]
Output: 3
The only leaf is at depth 3; single-child nodes do not count as leaves.
Example 3:
Input: root = []
Output: 0
Constraints
- The number of nodes is in the range
[0, 10^5]. -1000 <= Node.value <= 1000.
Problem 15: Sum of Left Leaves
LeetCode: 404. Sum of Left Leaves
Description
Given the root of a binary tree, return the sum of all left leaves. A left leaf is a node that is the left child of its parent and itself has no children. Traverse the tree carrying a flag of whether the current node is a left child.
Examples
Example 1:
Input: root = [3,9,20,null,null,15,7]
Output: 24
The left leaves are 9 and 15, summing to 24.
Example 2:
Input: root = [1]
Output: 0
The root is not a left child, so there are no left leaves.
Example 3:
Input: root = [1,2,3,4,5]
Output: 4
Only 4 is a left leaf.
Constraints
- The number of nodes is in the range
[1, 1000]. -1000 <= Node.value <= 1000.
Problem 16: Search in a BST
LeetCode: 700. Search in a Binary Search Tree
Description
Given the root of a binary search tree and a target value, return the subtree rooted at the node whose value equals the target, or null if it is absent. Exploit the BST ordering: go left when the target is smaller, right when it is larger — O(h) time.
Examples
Example 1:
Input: root = [4,2,7,1,3], target = 2
Output: [2,1,3]
Example 2:
Input: root = [4,2,7,1,3], target = 5
Output: []
5 is not present.
Example 3:
Input: root = [8,3,10], target = 10
Output: [10]
Constraints
- The number of nodes is in the range
[1, 5000]. - All values are unique.
Problem 17: Range Sum of a BST
LeetCode: 938. Range Sum of BST
Description
Given the root of a binary search tree and an inclusive range [lo, hi], return the sum of values of all nodes whose value lies within [lo, hi]. Prune entire subtrees that cannot contribute (when the current value is below lo, skip the left subtree; above hi, skip the right) so work stays proportional to the relevant region.
Examples
Example 1:
Input: root = [10,5,15,3,7,null,18], lo = 7, hi = 15
Output: 32
The in-range values are 7, 10, and 15.
Example 2:
Input: root = [10,5,15,3,7,13,18,1,null,6], lo = 6, hi = 10
Output: 23
The in-range values are 6, 7, and 10.
Example 3:
Input: root = [5], lo = 1, hi = 4
Output: 0
Constraints
- The number of nodes is in the range
[1, 2 * 10^4]. 1 <= lo <= hi <= 10^5.
Problem 18: Lowest Common Ancestor of a BST
LeetCode: 235. Lowest Common Ancestor of a Binary Search Tree
Description
Given the root of a binary search tree and two values p and q both present in it, return the value of their lowest common ancestor. Use the ordering property: descend left while both targets are smaller, right while both are larger; the first node that splits them (or equals one of them) is the LCA, found in O(h) time.
Examples
Example 1:
Input: root = [6,2,8,0,4,7,9], p = 2, q = 8
Output: 6
Example 2:
Input: root = [6,2,8,0,4,7,9], p = 2, q = 4
Output: 2
A node can be a descendant of itself.
Example 3:
Input: root = [2,1], p = 2, q = 1
Output: 2
Constraints
- The number of nodes is in the range
[2, 10^5]. - All values are unique, and
p != q.
Problem 19: Kth Smallest Element in a BST
LeetCode: 230. Kth Smallest Element in a BST
Description
Given the root of a binary search tree and an integer k, return the k-th smallest value (1-indexed). An inorder traversal visits values in sorted order; stop as soon as the k-th value is reached rather than materializing the entire order.
Examples
Example 1:
Input: root = [3,1,4,null,2], k = 1
Output: 1
Example 2:
Input: root = [5,3,6,2,4,null,null,1], k = 3
Output: 3
Example 3:
Input: root = [2,1,3], k = 3
Output: 3
Constraints
- The number of nodes is
n, with1 <= k <= n <= 10^4. 0 <= Node.value <= 10^4.
Problem 20: Right Side View
LeetCode: 199. Binary Tree Right Side View
Description
Given the root of a binary tree, imagine standing on its right side and return the values of the nodes you can see, ordered top to bottom. These are the rightmost node of each level. A level-order traversal that records the last node of every level solves it directly.
Examples
Example 1:
Input: root = [1,2,3,null,5,null,4]
Output: [1,3,4]
Example 2:
Input: root = [1,null,3]
Output: [1,3]
Example 3:
Input: root = []
Output: []
Constraints
- The number of nodes is in the range
[0, 100]. -100 <= Node.value <= 100.
Problem 21: Zigzag Level-Order Traversal
LeetCode: 103. Binary Tree Zigzag Level Order Traversal
Description
Given the root of a binary tree, return its values in zigzag level order: level 0 left-to-right, level 1 right-to-left, alternating each level downward. Run a normal level-order traversal but reverse the collected values on odd levels.
Examples
Example 1:
Input: root = [3,9,20,null,null,15,7]
Output: [[3],[20,9],[15,7]]
Example 2:
Input: root = [1]
Output: [[1]]
Example 3:
Input: root = []
Output: []
Constraints
- The number of nodes is in the range
[0, 2000]. -100 <= Node.value <= 100.
Problem 22: Count Complete Tree Nodes
LeetCode: 222. Count Complete Tree Nodes
Description
Given the root of a complete binary tree, return the number of nodes. Every level except possibly the last is full, and the last level is filled left to right. A plain traversal counts in O(n); using left-spine and right-spine heights to detect perfect subtrees achieves O(log^2 n).
Examples
Example 1:
Input: root = [1,2,3,4,5,6]
Output: 6
Example 2:
Input: root = []
Output: 0
Example 3:
Input: root = [1]
Output: 1
Constraints
- The number of nodes is in the range
[0, 5 * 10^4]. - The tree is guaranteed to be complete.
Problem 23: Construct Tree from Preorder and Inorder
LeetCode: 105. Construct Binary Tree from Preorder and Inorder Traversal
Description
Given two integer arrays preorder and inorder representing the preorder and inorder traversals of a binary tree with distinct values, reconstruct and return the tree. The first preorder element is the root; its position in inorder splits the values into left and right subtrees, which you build recursively.
Examples
Example 1:
Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
Output: [3,9,20,null,null,15,7]
Example 2:
Input: preorder = [-1], inorder = [-1]
Output: [-1]
Example 3:
Input: preorder = [1,2,3], inorder = [3,2,1]
Output: [1,2,null,3]
A left-leaning chain.
Constraints
1 <= preorder.length <= 3000.- The values are unique, and the two arrays describe the same tree.
Problem 24: Subtree of Another Tree
LeetCode: 572. Subtree of Another Tree
Description
Given the roots of two binary trees root and subRoot, return true if there is a node in root such that the subtree rooted there is structurally identical (same values, same shape) to subRoot. The whole tree counts as a subtree of itself.
Examples
Example 1:
Input: root = [3,4,5,1,2], subRoot = [4,1,2]
Output: true
Example 2:
Input: root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2]
Output: false
The candidate subtree has an extra node 0.
Example 3:
Input: root = [1,1], subRoot = [1]
Output: true
Constraints
- The number of nodes in
rootis in the range[1, 2000]. - The number of nodes in
subRootis in the range[1, 1000].
Problem 25: Path Sum II
LeetCode: 113. Path Sum II
Description
Given the root of a binary tree and an integer target, return all root-to-leaf paths whose node values sum to target. Each path is returned as the list of values from root to leaf. Backtrack: append a node on the way down and remove it on the way back up, recording paths that reach a leaf with the exact remaining sum.
Examples
Example 1:
Input: root = [5,4,8,11,null,13,4,7,2,null,null,5,1], target = 22
Output: [[5,4,11,2],[5,8,4,5]]
Example 2:
Input: root = [1,2,3], target = 5
Output: []
Example 3:
Input: root = [1,2], target = 0
Output: []
Constraints
- The number of nodes is in the range
[0, 5000]. -1000 <= Node.value <= 1000.
Problem 26: Lowest Common Ancestor of a Binary Tree
LeetCode: 236. Lowest Common Ancestor of a Binary Tree
Description
Given the root of a binary tree (not a BST) and two values p and q both present, return the value of their lowest common ancestor — the deepest node that has both as descendants (a node is a descendant of itself). Without ordering to exploit, recurse: a node whose two subtrees each contain one target, or which is itself a target with the other below, is the LCA.
Examples
Example 1:
Input: root = [3,5,1,6,2,0,8], p = 5, q = 1
Output: 3
Example 2:
Input: root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 4
Output: 5
4 lies under 5, so 5 is its own ancestor and the LCA.
Example 3:
Input: root = [1,2], p = 1, q = 2
Output: 1
Constraints
- The number of nodes is in the range
[2, 10^5]. - All values are unique, and
p != q.
Problem 27: Maximum Path Sum
LeetCode: 124. Binary Tree Maximum Path Sum
Description
Given the root of a binary tree whose nodes carry values (possibly negative), return the maximum path sum over all paths. A path is any sequence of nodes connected by parent-child edges; it need not pass through the root, and must contain at least one node. At each node, the best path through it is its value plus the best downward gains (clamped at zero) of its two subtrees; track the global maximum while returning the single best downward branch. Accumulate the answer as a long.
Examples
Example 1:
Input: root = [1,2,3]
Output: 6
The path 2 -> 1 -> 3 sums to 6.
Example 2:
Input: root = [-10,9,20,null,null,15,7]
Output: 42
The path 15 -> 20 -> 7 sums to 42.
Example 3:
Input: root = [-3]
Output: -3
A single negative node must still be its own path.
Constraints
- The number of nodes is in the range
[1, 3 * 10^4]. -1000 <= Node.value <= 1000.
Problem 28: Goblin Treasure Subtree Sums
Description
In the “Goblin Caves” map, each chamber is a node of a binary tree holding the gold stored there (possibly negative). For every chamber, the total loot of its subtree is the sum of gold over all chambers in that subtree. Return the maximum subtree total over all chambers. An empty tree gives 0. There are up to 10^5 chambers; values fit a 32-bit int, but accumulate the answer as a long.
Examples
Example 1:
Input: root = [1,2,3]
Output: 6
The whole-tree subtree at the root sums to 6, the largest.
Example 2:
Input: root = [-5,4,-8]
Output: 4
The single-node subtree at 4 beats the whole tree (-9) and the subtree at -8.
Example 3:
Input: root = []
Output: 0
Constraints
- The number of nodes is in the range
[0, 10^5]. -2^31 <= Node.value <= 2^31 - 1.
Problem 29: Archivist’s BST Range Tally
Description
Scroll IDs are stored in a binary search tree. Given the root and an inclusive window [lo, hi], return the sum of all stored IDs that fall inside the window. Prune subtrees that cannot contribute so the work is proportional to the visited region. An empty tree or empty window gives 0. There are up to 10^5 keys; accumulate the sum as a long.
Examples
Example 1:
Input: root = [10,5,15,3,7,null,18], lo = 7, hi = 15
Output: 32
The in-window IDs are 7, 10, and 15.
Example 2:
Input: root = [10,5,15], lo = 20, hi = 30
Output: 0
No keys fall in the window.
Example 3:
Input: root = [], lo = 1, hi = 100
Output: 0
Constraints
- The number of nodes is in the range
[0, 10^5]. -10^9 <= lo <= hi <= 10^9.
Problem 30: Wizard Tower Boundary Patrol
Description
A wizard patrols a binary tree of tower floors, walking its boundary counterclockwise and reporting each floor once. The boundary is: the root, then the left boundary (top-down, excluding leaves), then all leaves left to right, then the right boundary (bottom-up, excluding leaves). The root is included once even if it is also a boundary endpoint; no node appears twice. An empty tree gives an empty list, and a single node gives just that node.
Examples
Example 1:
Input: root = [1,2,3,4,5,6,7]
Output: [1,2,4,5,6,7,3]
Root 1, left boundary 2, leaves 4,5,6,7, right boundary 3.
Example 2:
Input: root = [1,null,2,3,4]
Output: [1,3,4,2]
Root 1, no left boundary, leaves 3,4, right boundary 2.
Example 3:
Input: root = [1]
Output: [1]
Constraints
- The number of nodes is in the range
[0, 10^4]. -1000 <= Node.value <= 1000.
Binary Heap
What a Heap Is & When to Use It
What problem does a heap solve that a sorted array or BST doesn’t — when do you only ever need fast access to the single smallest (or largest) element rather than full ordering?
Heap-Order Property
What local rule must hold between every parent and its children, and why is “parent beats its kids” strictly weaker than “the whole array is sorted”?
Partial vs. Total Order
Why can two siblings sit in any relative order, and what cheaper maintenance does that freedom buy you?
The Shape (Completeness) Invariant
Why must a heap also be a COMPLETE tree — every level full except possibly the last, filled left to right — and what would break if you allowed holes?
Array-Backed Complete Tree
Why does the completeness invariant let you store the whole tree in a contiguous array with zero pointers and zero wasted slots?
Index Math: Parent & Children
Given a node at index i, how do you compute parent, left child, and right child — and how does every formula shift between 0-based and 1-based arrays?
Where the Next Slot Goes
Why is “append at the end of the array” always the unique spot that preserves completeness on insert?
Sift-Up (Insert)
After appending the new value at the tail, which direction does it travel and exactly what condition halts the climb?
The Climb Loop
At each step which two elements do you compare, and when do you stop — element reaches the root, or its parent already dominates it?
Sift-Down (Extract / Remove)
Why do you overwrite the root with the last leaf and shrink the array FIRST, before doing any swapping?
Picking the Right Child
With two candidate children, how do you choose which one to swap toward so you don’t violate the order on the other branch?
Why You Swap Toward the Dominant Child
In a max-heap, what goes wrong if you swap with the smaller child instead of the larger one?
Peek vs. Poll
Why is reading the top \( O(1) \) while removing it forces \( O(\log n) \) — what structural repair does removal trigger that a read never does?
Min-Heap vs. Max-Heap
What is the single thing you change to flip one into the other, and where in code does that decision actually live — the comparator?
decrease-key / increase-key
If you change a key already inside the heap, why must you re-sift, and how do you decide whether to sift up or down from that position?
Time Complexity
Tie every operation’s cost to the tree’s height, which is about \( \log_2 n \) precisely because the tree is complete.
Peek
Why is reading min/max always \( O(1) \) no matter the size — what does it actually touch?
Insert (Sift-Up)
Worst case the new element climbs the full height — what bound is that? What’s the best case when it stops at its parent, and why is the average closer to \( O(1) \) given most nodes live near the bottom?
Extract (Sift-Down)
Why is the worst case the full height again, and why can’t you rely on an early stop the way insert sometimes can?
Building From n Inserts
If you build by inserting one at a time, why does that land at \( O(n\log n) \) — and how is that different from the bottom-up build in the build-heap note?
Space Complexity
Why does the array-backed heap need only \( O(n) \) total and \( O(1) \) auxiliary space — no node objects, no child pointers?
Iterative vs. Recursive Sifting
If you code sift-down recursively, what call-stack depth do you pay, and how does rewriting it as a loop drop that back to \( O(1) \)?
Dynamic Array Resizing
When the backing array grows, what amortized cost does doubling add, and does it change the asymptotic space?
Priority-Queue Uses
Where does a heap show up — task scheduling, Dijkstra, top-k selection, k-way merge of sorted streams — and why is it the natural fit each time?
Pitfalls
What breaks with off-by-one index math, forgetting to shrink the array on poll, mutating a key in place without re-sifting, or storing comparables that don’t define a total order?
Implementation Walkthrough
Plan the code in parts before you write it — each sub-section tells you what to work out, not the answer.
Layout & Index Helpers
What backing storage and size counter do you need, and which tiny helpers (parent, left, right) should everything call so the index math lives in exactly one place?
The Sift-Up Routine
What loop condition climbs from a given index, what do you swap, and how do you stop cleanly at the root?
The Sift-Down Routine
Starting at an index, how do you select the dominant child, decide whether a swap is needed, and continue until the node settles?
Insert & Extract Wired Together
How does insert append-then-sift-up, and how does extract save-the-top, move-last-to-root, shrink, then sift-down? Which edge cases (empty heap, single element) must each guard?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Build-Heap
What Build-Heap Is For
When you already hold an unordered array, why heapify it in place instead of inserting elements one at a time into an empty heap?
Top-Down (Insert-by-Insert) Baseline
If you insert all \( n \) elements one at a time, what’s the total cost and why is repeatedly sifting up the slower approach?
Bottom-Up Heapify
Why start from the last internal node and walk backward toward index 0 instead of going front to back?
The Subtree Invariant
When you arrive at a node, why are both of its subtrees already valid heaps, so a single sift-down on that node fully fixes it?
What Holds When You Reach Index 0
By the time the loop processes the root, what is guaranteed about every node beneath it?
Leaves Need No Work
Why can you skip the entire second half of the array, and exactly which index marks the first leaf?
Why It’s O(n), Not O(n log n)
Most nodes sit near the bottom and sift down only a step or two; only the rare high nodes travel far. Give the intuition for why those costs sum to a constant times \( n \) — no summation, just the “cheap nodes are common, expensive nodes are rare” argument.
Where the Insert-by-Insert Version Loses
The top-down build pays the most for the LAST elements (deepest) and there are many of them — why does that flip the cost to \( O(n\log n) \)?
In-Place Heapify
How does bottom-up build rearrange the original array directly, and why does that matter for memory-tight code?
Heapsort Connection
Once the array is a max-heap, how do repeated extract-max steps sort it ascending, and what’s the total running time?
Sorting In the Same Array
Where does each extracted max go so heapsort stays \( O(1) \) extra space — what shrinking boundary separates “heap” from “sorted tail”?
Time Complexity
Separate the linear build phase from anything built on top of it.
Build Phase
Why is bottom-up build \( O(n) \) best, average, AND worst — what about the structure makes the input order irrelevant to the bound?
Heapsort Phase
Why does the extraction phase cost \( O(n\log n) \) — \( n \) extractions each sifting down up to the full height?
Best/Worst Triggers
Does heapsort have a meaningfully better best case, or is it \( O(n\log n) \) regardless of input order? Why?
Space Complexity
Why is bottom-up build \( O(1) \) auxiliary, and what would push it higher?
Recursion Depth of Sift-Down
If sift-down is recursive, what stack depth does the build incur, and how does an iterative sift-down keep the whole build \( O(1) \) space?
Pitfalls
What breaks if you sift up instead of down during build, start the loop at the wrong index, iterate forward instead of backward, or assume build-heap leaves the array fully sorted?
Implementation Walkthrough
Plan the code in parts before you write it — each sub-section tells you what to figure out, not the answer.
The First-Internal-Node Index
How do you compute the index of the last node that has a child, so your loop starts there and skips all leaves?
The Backward Loop
Why does the loop count down to and include index 0, and what single call does it make at each node?
Reusing Sift-Down
Why can build-heap delegate entirely to the same sift-down routine the heap already has — what must that routine accept as a starting index?
Layering Heapsort On Top
After build, how does the extract-and-place-at-the-shrinking-end loop turn the heap into a sorted array without new memory?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
d-ary Heaps
What a d-ary Heap Is & Why Tune d
Why would you give each node \( d \) children instead of 2 — what single knob are you turning and what does it buy you?
Layout: Still an Array, Wider Nodes
How does the same flat-array trick extend when every node has \( d \) children packed contiguously, and why is there still no pointer overhead?
The Completeness Invariant Still Holds
Why does a d-ary heap remain a complete tree, and why does that keep the array gap-free?
Index Math for d Children
Given index i, how do you compute its parent and the contiguous block of its \( d \) children — and how does each formula generalize the binary 2i+1 / 2i+2 case?
Off-by-One Across Bases
How does the child-block formula shift between 0-based and 1-based arrays, and why is this the easiest place to introduce a bug?
Shorter, Bushier Tree
Why does raising \( d \) shrink the height to roughly \( \log_d n \), and what gets more expensive lower down as a direct consequence?
Sift-Up (Insert / decrease-key)
Why do insert and decrease-key speed up as \( d \) grows — what work do they NOT have to do that sift-down does?
Sift-Down (Extract-Min)
Why must extract-min scan all \( d \) children at each level to find the smallest, and how does that scanning cost fight against the shorter height?
Finding the Min Child
With \( d \) candidates instead of 2, how do you code the inner loop that picks the swap target, and how do you handle the last node having fewer than \( d \) children?
Choosing d for a Workload
If your algorithm fires far more decrease-keys than extract-mins (think Dijkstra on a dense graph), should \( d \) go up or down — and what does that do to the two operations?
Time Complexity
Express every operation in terms of \( d \): the height is \( \log_d n \), but each sift-down level now costs \( d \) comparisons.
Insert & decrease-key (Sift-Up)
Why is the bound \( O(\log_d n) \), and why does it shrink as \( d \) grows — what does sift-up never pay that sift-down does?
Extract-Min (Sift-Down)
Why is the bound \( O(d\log_d n) \), and which factor grows with \( d \) while the other shrinks? Where does the sweet-spot \( d \) come from intuitively?
Peek & Build
Why is peek still \( O(1) \), and does bottom-up build stay linear for a d-ary heap?
Space Complexity
Why is a d-ary heap still \( O(n) \) total and \( O(1) \) auxiliary, and does a larger \( d \) change the storage at all?
Recursion vs. Iteration
If sift-down recurses, what stack depth (\( \log_d n \)) do you pay, and how does iterating remove it?
Trade-offs vs. Binary Heap
When is a binary heap simply the right default, and what concrete cache-friendliness or operation-mix win justifies the extra index bookkeeping of a d-ary heap?
Pitfalls
What breaks if you mis-index the child block, forget the last node may have fewer than \( d \) children, scan past the array end, or pick \( d \) without measuring the real workload?
Implementation Walkthrough
Plan the code in parts before you write it — each sub-section tells you what to work out, not the answer.
Parameterizing d & the Index Helpers
How do you make d a field and write parent(i) and kthChild(i, k) so the rest of the code never hardcodes 2?
The Min-Child Scan
Inside sift-down, how do you loop over the up-to-\( d \) children, clamp at the array boundary, and track the dominant one?
Sift-Up vs. Sift-Down Symmetry
Why does sift-up stay almost identical to the binary case while sift-down gains the inner scan — what’s the structural reason?
Insert & Extract Wired Together
How do append-then-sift-up and move-last-to-root-then-sift-down carry over unchanged except for the helpers they call?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Fibonacci Heaps
What It’s For & Why It Exists
Which operation does a Fibonacci heap make dramatically cheaper (amortized) than a binary heap, and which classic algorithm — Dijkstra, Prim — cares about that?
Forest of Heap-Ordered Trees
Why is the structure a loose collection of trees in a circular root list instead of one rigid complete tree, and what flexibility does that looseness enable?
The Min Pointer
Why keep a single pointer to the minimum root, and how does that alone make find-min \( O(1) \)?
Pointers & Node Layout
What links does each node carry — parent, one child, left/right siblings, degree, mark — and why does a circular doubly linked sibling list make splicing \( O(1) \)?
Lazy Insert & Union
Why can insert and union be \( O(1) \) — what work is deliberately postponed instead of done immediately?
Insert as a Trivial Union
Why is inserting one element just merging a one-node heap into the root list, and why does that avoid any sifting?
Extract-Min: Paying the Bill
When you finally remove the min, how does promoting all its children to the root list and then scanning trigger the deferred cleanup?
Consolidation
How does repeatedly linking trees of equal degree collapse a long root list into at most one tree per degree, and what temporary array indexes “by degree”?
Decrease-Key & Cascading Cuts
Why does cutting a node loose from its parent — when its key drops below the parent’s — sometimes set off a chain of cuts marching up the tree?
The Mark Bit
What does marking a node mean, why does a node lose its mark when it becomes a root, and why does losing a SECOND child force a cut?
Why Cuts Keep Trees Bushy
How does the “lose at most one child before you’re cut” rule stop trees from degenerating into long thin paths?
The Fibonacci Bound (Intuition)
Why do subtree sizes grow at least like Fibonacci numbers \( F_k \), and why does that cap the maximum degree at \( O(\log n) \)? Reason about it, don’t prove it.
Time Complexity
Separate amortized bounds from worst-case single-operation cost — this is the whole point of the structure.
Amortized vs. Worst-Case
What does “amortized” mean here — why can one extract-min be slow yet the average over a sequence stay \( O(\log n) \)? Give the potential-function intuition (roots + marks as stored-up work) without formal algebra.
O(1) Operations
Why are insert, find-min, union, and decrease-key all \( O(1) \) amortized — what cost did each defer, and onto which later operation?
O(log n) Operations
Why do extract-min and delete cost \( O(\log n) \) amortized, and what single quantity (max degree) bounds the consolidation work?
Space Complexity
Why is the heap \( O(n) \) in nodes, but with a much larger per-node constant than a binary heap — how many pointers and flags per node?
Consolidation Scratch Space
Why does consolidation need an auxiliary array sized to the maximum degree, \( O(\log n) \), and why is that the only notable extra allocation?
Trade-offs: Theory vs. Practice
Why do real systems often still pick a binary or d-ary heap despite the better Big-O — what hidden constants, pointer-chasing, and cache misses bite in practice?
Pitfalls
What goes wrong if you forget to clear mark bits on promotion, mishandle circular-list splicing, update the min pointer wrong after consolidation, or trust the worst-case bound for a single operation?
Implementation Walkthrough
Plan the code in parts before you write it — each sub-section tells you what to work out, not the answer.
Node & Circular List Plumbing
What fields does a node need, and what helper does “splice a node (and its ring) into a circular doubly linked list” so insert/cut reuse it?
Insert, Union & the Min Pointer
How do insert and union just merge root lists and then compare against the current min — what is the ONLY comparison each does?
Extract-Min & Consolidate
How do you move children up, remove the min, then walk the root list linking equal-degree trees via a degree-indexed array, rebuilding the min pointer at the end?
Decrease-Key, Cut & Cascading-Cut
How does decrease-key check the heap order, cut to the root list if violated, and recursively cascade up while parents are already marked?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Heaps & Priority Queues: Problem Set
Heaps give you the cheapest possible answer to one recurring question: which element matters most right now? The foundational problems build the machine — sift-down, the array-as-tree layout, heapsort, the heap-select. The applied problems put that machine to work on the patterns heaps were born for: top-k selection, the running median with two balanced heaps, interval and deadline scheduling, k-way merging of sorted streams, and online/streaming computation where the data never stops arriving. Each problem is an implementation task: fill in the stub in problemset/ and make its test in tests/problemset/ pass.
Foundational
Problem 1: Sift Down
Description
Given an integer array interpreted as a complete binary tree (index i has children 2i + 1 and 2i + 2), a heap size n, and an index i whose two child subtrees already satisfy the max-heap property, sift the element at index i downward until the subtree rooted at i is a valid max-heap. Operate in place and consider only indices in [0, n).
This is the single primitive that powers heapify, extract-max, and heapsort. Each step swaps the current element with its larger child and descends; it stops when the element is at least as large as both children (or becomes a leaf).
Examples
Example 1:
Input: heap = [1, 9, 8, 5, 6, 7, 4], n = 7, i = 0
Output: [9, 6, 8, 5, 1, 7, 4]
The root 1 swaps with its larger child 9, then with 6, settling into a valid max-heap.
Example 2:
Input: heap = [3, 1, 2], n = 3, i = 0
Output: [3, 1, 2]
The root 3 already dominates both children, so nothing moves.
Example 3:
Input: heap = [4, 10, 9, 8, 3, 2, 1], n = 7, i = 0
Output: [10, 8, 9, 4, 3, 2, 1]
4 sinks past 10 and then 8.
Constraints
1 <= n <= heap.length <= 10^40 <= i < n- The subtrees rooted at the children of
iare already valid max-heaps.
Problem 2: Is It a Min-Heap?
Description
Given an integer array using the standard array-as-complete-binary-tree layout, decide whether it satisfies the min-heap property: every node is less than or equal to each of its children. Return true if the array is a valid min-heap and false otherwise. An empty or single-element array is trivially a valid heap.
Examples
Example 1:
Input: [1, 3, 2, 7, 4, 5, 6]
Output: true
Each parent is <= both children.
Example 2:
Input: [1, 3, 2, 7, 4, 0, 6]
Output: false
Node 2 at index 2 has a child 0 at index 5, violating the property.
Example 3:
Input: [5]
Output: true
A single element is always a valid heap.
Constraints
0 <= array.length <= 10^5- Values fit in a 32-bit signed integer.
Problem 3: Heapsort
Description
Given an integer array, sort it in ascending order in place using heapsort: build a max-heap over the whole array, then repeatedly swap the root (current maximum) to the end of the active heap region and sift the new root down over the shrunken region. Do not call a library sort.
Examples
Example 1:
Input: [5, 1, 4, 2, 8]
Output: [1, 2, 4, 5, 8]
Example 2:
Input: [3, 3, 1, 2, 3]
Output: [1, 2, 3, 3, 3]
Duplicates are handled correctly.
Example 3:
Input: [9]
Output: [9]
Constraints
0 <= array.length <= 10^5- Values fit in a 32-bit signed integer.
Problem 4: K Smallest Elements
Description
Given an integer array and an integer k, return the k smallest values in ascending order, using a heap. If k >= array.length, return all elements sorted ascending. Duplicates count individually (so the result may contain repeats).
A classic approach keeps a max-heap of size k: push each element, and whenever the heap exceeds k evict its maximum, so the heap always holds the k smallest seen so far.
Examples
Example 1:
Input: array = [7, 10, 4, 3, 20, 15], k = 3
Output: [3, 4, 7]
Example 2:
Input: array = [1, 1, 1, 2], k = 2
Output: [1, 1]
Example 3:
Input: array = [5, 2], k = 5
Output: [2, 5]
k exceeds the length, so all elements are returned sorted.
Constraints
0 <= k0 <= array.length <= 10^5- Values fit in a 32-bit signed integer.
Problem 5: Heap Index Arithmetic
Description
For a 0-indexed d-ary heap with branching factor d (each node has up to d children), implement two index helpers:
parent(i, d)returns the parent index of nodei, or-1ifiis the root (i == 0).child(i, j, d)returns the index of thej-th child (0-indexed,0 <= j < d) of nodei.
For a d-ary heap the parent of node i is (i - 1) / d (integer division) and the j-th child is d * i + j + 1.
Examples
Example 1:
Input: parent(i = 5, d = 2)
Output: 2
In a binary heap, index 5’s parent is (5 - 1) / 2 = 2.
Example 2:
Input: child(i = 2, j = 0, d = 3)
Output: 7
In a ternary heap, node 2’s first child is 3*2 + 0 + 1 = 7.
Example 3:
Input: parent(i = 0, d = 4)
Output: -1
The root has no parent.
Constraints
d >= 2i >= 0,0 <= j < d- Results fit in a 32-bit signed integer.
Applied Problems
Problem 6: Kth Largest Element in an Array
LeetCode: 215. Kth Largest Element in an Array
Description
Given an integer array nums and an integer k, return the k-th largest element in the array. Note that it is the k-th largest in sorted order, not the k-th distinct element. Aim for an O(n log k) solution using a size-k min-heap (the heap’s root is the answer once all elements are processed).
Examples
Example 1:
Input: nums = [3, 2, 1, 5, 6, 4], k = 2
Output: 5
Example 2:
Input: nums = [3, 2, 3, 1, 2, 4, 5, 5, 6], k = 4
Output: 4
Example 3:
Input: nums = [1], k = 1
Output: 1
Constraints
1 <= k <= nums.length <= 10^5-10^4 <= nums[i] <= 10^4
Problem 7: Last Stone Weight
LeetCode: 1046. Last Stone Weight
Description
You are given an array of integer stone weights. On each turn, smash together the two heaviest stones with weights x <= y: if x == y both are destroyed; otherwise the stone of weight x is destroyed and the stone of weight y becomes weight y - x. Repeat until at most one stone remains. Return the weight of the last remaining stone, or 0 if none remain. Use a max-heap.
Examples
Example 1:
Input: stones = [2, 7, 4, 1, 8, 1]
Output: 1
Smash 8 and 7 -> 1; stones [2,4,1,1,1]. Smash 4 and 2 -> 2; [2,1,1,1]. Smash 2 and 1 -> 1; [1,1,1]. Smash 1 and 1 -> 0; [1]. Last stone is 1.
Example 2:
Input: stones = [1]
Output: 1
Example 3:
Input: stones = [3, 3]
Output: 0
Equal stones annihilate each other.
Constraints
1 <= stones.length <= 3 * 10^41 <= stones[i] <= 10^4
Problem 8: K Closest Points to Origin
LeetCode: 973. K Closest Points to Origin
Description
Given an array of 2-D points points[i] = [xi, yi] and an integer k, return the k points closest to the origin (0, 0) by Euclidean distance. The answer may be returned in any order. Compare squared distances to avoid floating point. Use a size-k max-heap keyed on squared distance.
Examples
Example 1:
Input: points = [[1, 3], [-2, 2]], k = 1
Output: [[-2, 2]]
(-2,2) has squared distance 8 versus 10 for (1,3).
Example 2:
Input: points = [[3, 3], [5, -1], [-2, 4]], k = 2
Output: [[3, 3], [-2, 4]]
Returned order may vary.
Example 3:
Input: points = [[0, 1], [1, 0]], k = 2
Output: [[0, 1], [1, 0]]
Constraints
1 <= k <= points.length <= 10^4-10^4 <= xi, yi <= 10^4
Problem 9: Top K Frequent Elements
LeetCode: 347. Top K Frequent Elements
Description
Given an integer array nums and an integer k, return the k most frequent elements, ordered by descending frequency. It is guaranteed the answer is unique. Tally counts in a map, then select the top k by frequency with a heap.
Examples
Example 1:
Input: nums = [1, 1, 1, 2, 2, 3], k = 2
Output: [1, 2]
1 appears 3 times, 2 twice.
Example 2:
Input: nums = [1], k = 1
Output: [1]
Example 3:
Input: nums = [4, 4, 4, 6, 6, 7], k = 2
Output: [4, 6]
Constraints
1 <= nums.length <= 10^5kis in the range[1, number of distinct elements].- The answer is unique.
Problem 10: Kth Largest Element in a Stream
LeetCode: 703. Kth Largest Element in a Stream
Description
Design a class that, given k and an initial array of integers, supports an add(val) operation returning the k-th largest element in the stream after inserting val. The same value may appear multiple times. Maintain a size-k min-heap so each add runs in O(log k) and its root is the current k-th largest.
Examples
Example 1:
Input: k = 3, initial = [4, 5, 8, 2]; add(3), add(5), add(10), add(9), add(4)
Output: [4, 5, 5, 8, 8]
Each call reports the 3rd-largest after the insertion.
Example 2:
Input: k = 1, initial = []; add(-3), add(-2), add(-4), add(0), add(4)
Output: [-3, -2, -2, 0, 4]
With k = 1 the answer is the running maximum.
Example 3:
Input: k = 2, initial = [0]; add(-1), add(1), add(-2), add(-4), add(3)
Output: [-1, 0, 0, 0, 1]
Constraints
1 <= k <= 10^40 <= initial.length <= 10^4-10^4 <= each value <= 10^4- At most
10^4calls toadd.
Problem 11: Find Median from Data Stream
LeetCode: 295. Find Median from Data Stream
Description
Design a data structure that supports adding integers from a stream and finding the median of all elements added so far. Implement addNum(int num) and findMedian() (which returns a double; for an even count it is the average of the two middle values). Use a max-heap for the lower half and a min-heap for the upper half, kept balanced so each operation is O(log n).
Examples
Example 1:
Input: addNum(1), addNum(2), findMedian(), addNum(3), findMedian()
Output: 1.5, 2.0
After 1, 2 the median is 1.5; after 1, 2, 3 it is 2.0.
Example 2:
Input: addNum(5), findMedian()
Output: 5.0
Example 3:
Input: addNum(-1), addNum(-2), addNum(-3), findMedian()
Output: -2.0
Constraints
-10^5 <= num <= 10^5findMedianis only called after at least oneaddNum.- At most
5 * 10^4calls in total.
Problem 12: Sort a Nearly Sorted Array
Description
Given an array in which every element is at most k positions away from its correct sorted position, sort it in ascending order in O(n log k) time using a bounded-size min-heap of capacity k + 1. Push elements into the heap; once it holds k + 1 elements, pop the minimum into the output before pushing the next, then drain the heap at the end.
Examples
Example 1:
Input: array = [6, 5, 3, 2, 8, 10, 9], k = 3
Output: [2, 3, 5, 6, 8, 9, 10]
Example 2:
Input: array = [2, 1, 3], k = 1
Output: [1, 2, 3]
Example 3:
Input: array = [10, 9, 8, 7, 4, 70, 60, 50], k = 4
Output: [4, 7, 8, 9, 10, 50, 60, 70]
Constraints
0 <= k < array.length <= 10^5- Values fit in a 32-bit signed integer.
Problem 13: Meeting Rooms II
LeetCode: 253. Meeting Rooms II
Description
Given an array of half-open meeting intervals intervals[i] = [start, end), return the minimum number of conference rooms required so that no two overlapping meetings share a room. Sort by start time and use a min-heap of end times: for each meeting, if the earliest-ending room frees by its start, reuse it; otherwise open a new room. The answer is the maximum simultaneous occupancy.
Examples
Example 1:
Input: intervals = [[0, 30], [5, 10], [15, 20]]
Output: 2
[0,30] overlaps both others, but [5,10] and [15,20] don’t overlap each other.
Example 2:
Input: intervals = [[7, 10], [2, 4]]
Output: 1
Disjoint meetings reuse one room.
Example 3:
Input: intervals = [[1, 5], [2, 6], [3, 7]]
Output: 3
All three overlap.
Constraints
0 <= intervals.length <= 10^40 <= start < end <= 10^6
Problem 14: Task Scheduler
LeetCode: 621. Task Scheduler
Description
Given an array of CPU tasks identified by uppercase letters and a non-negative integer n representing the cooldown, return the minimum number of time units (each unit runs one task or idles) the CPU needs to finish all tasks. Two identical tasks must be separated by at least n units. A heap-driven greedy schedule always runs the most-frequent ready task.
Examples
Example 1:
Input: tasks = ['A', 'A', 'A', 'B', 'B', 'B'], n = 2
Output: 8
One valid order is A B idle A B idle A B.
Example 2:
Input: tasks = ['A', 'A', 'A', 'B', 'B', 'B'], n = 0
Output: 6
No cooldown means no idling.
Example 3:
Input: tasks = ['A', 'A', 'A', 'B', 'C', 'D'], n = 2
Output: 7
A ? ? A ? ? A with the others filling the gaps -> length 7.
Constraints
1 <= tasks.length <= 10^4tasks[i]is an uppercase English letter.0 <= n <= 100
Problem 15: Reorganize String
LeetCode: 767. Reorganize String
Description
Given a string s, rearrange its characters so that no two adjacent characters are the same, and return any valid arrangement. If no such arrangement exists, return the empty string "". Greedily place the most frequent remaining character using a max-heap, never picking the character placed immediately before.
Examples
Example 1:
Input: s = "aab"
Output: "aba"
Example 2:
Input: s = "aaab"
Output: ""
a appears too often to separate.
Example 3:
Input: s = "vvvlo"
Output: "vlvov"
Any arrangement with no equal adjacent pair is accepted.
Constraints
1 <= s.length <= 500sconsists of lowercase English letters.
Problem 16: Merge k Sorted Lists
LeetCode: 23. Merge k Sorted Lists
Description
Given k ascending-sorted integer lists (each as an int[]), merge them into a single ascending-sorted array in O(N log k) time using a min-heap, where N is the total number of elements. Seed the heap with the head of each non-empty list; repeatedly pop the smallest and push the next element from the same list.
Examples
Example 1:
Input: lists = [[1, 4, 5], [1, 3, 4], [2, 6]]
Output: [1, 1, 2, 3, 4, 4, 5, 6]
Example 2:
Input: lists = []
Output: []
Example 3:
Input: lists = [[], [0]]
Output: [0]
Empty lists are skipped.
Constraints
0 <= k <= 10^4- The total number of elements across all lists is at most
10^5. - Each list is sorted in ascending order.
Problem 17: Smallest Range Covering Elements from K Lists
LeetCode: 632. Smallest Range Covering Elements from K Lists
Description
You have k lists of integers, each sorted in ascending order. Find the smallest range [a, b] that includes at least one number from each of the k lists. A range [a, b] is smaller than [c, d] if b - a < d - c, or if b - a == d - c and a < c. Use a min-heap holding one candidate per list (like a k-way merge), tracking the current maximum to size each window.
Examples
Example 1:
Input: lists = [[4, 10, 15, 24, 26], [0, 9, 12, 20], [5, 18, 22, 30]]
Output: [20, 24]
24 is in list 1, 20 in list 2, 22 in list 3; width 4 is minimal.
Example 2:
Input: lists = [[1, 2, 3], [1, 2, 3], [1, 2, 3]]
Output: [1, 1]
Example 3:
Input: lists = [[10], [11]]
Output: [10, 11]
Constraints
1 <= k <= 35001 <= lists[i].length <= 50-10^5 <= lists[i][j] <= 10^5- Each list is sorted in ascending order.
Problem 18: Find K Pairs with Smallest Sums
LeetCode: 373. Find K Pairs with Smallest Sums
Description
Given two ascending-sorted integer arrays nums1 and nums2 and an integer k, return the k pairs (u, v) with u from nums1 and v from nums2 that have the smallest sums u + v. Return at most k pairs (fewer if the total number of pairs is smaller). Use a min-heap over candidate pairs, expanding neighbors lazily so you never materialize all m * n pairs.
Examples
Example 1:
Input: nums1 = [1, 7, 11], nums2 = [2, 4, 6], k = 3
Output: [[1, 2], [1, 4], [1, 6]]
Example 2:
Input: nums1 = [1, 1, 2], nums2 = [1, 2, 3], k = 2
Output: [[1, 1], [1, 1]]
Example 3:
Input: nums1 = [1, 2], nums2 = [3], k = 3
Output: [[1, 3], [2, 3]]
Only two pairs exist, so all are returned.
Constraints
1 <= nums1.length, nums2.length <= 10^5-10^9 <= nums1[i], nums2[i] <= 10^91 <= k <= 10^4- Both arrays are sorted in ascending order.
Problem 19: Sliding Window Median
LeetCode: 480. Sliding Window Median
Description
Given an integer array nums and a window size k, return the median of each contiguous window of size k as the window slides one step at a time. For an even k the median is the average of the two middle values (a double); for odd k it is the single middle value. Maintain the window with two balanced heaps and lazy deletion so each step is roughly O(log k).
Examples
Example 1:
Input: nums = [1, 3, -1, -3, 5, 3, 6, 7], k = 3
Output: [1.0, -1.0, -1.0, 3.0, 5.0, 6.0]
Example 2:
Input: nums = [1, 2, 3, 4], k = 2
Output: [1.5, 2.5, 3.5]
Example 3:
Input: nums = [5, 5, 5], k = 1
Output: [5.0, 5.0, 5.0]
Constraints
1 <= k <= nums.length <= 10^5-2^31 <= nums[i] <= 2^31 - 1
Problem 20: IPO
LeetCode: 502. IPO
Description
You can complete at most k distinct projects. Project i has a one-time capital[i] requirement and a profits[i] payoff added to your working capital on completion. Starting with capital w, repeatedly pick the most profitable project you can currently afford. Return the maximum capital after finishing at most k projects. Sort projects by capital and use a max-heap keyed on profit over the currently affordable set.
Examples
Example 1:
Input: k = 2, w = 0, profits = [1, 2, 3], capital = [0, 1, 1]
Output: 4
Take project 0 (capital 0 -> 1), then the profit-3 project (capital 1 -> 4).
Example 2:
Input: k = 3, w = 0, profits = [1, 2, 3], capital = [0, 1, 2]
Output: 6
All three become affordable in turn.
Example 3:
Input: k = 1, w = 2, profits = [1, 2, 3], capital = [1, 1, 2]
Output: 5
Pick the most profitable affordable project (profit 3).
Constraints
1 <= k <= 10^50 <= w <= 10^91 <= profits.length == capital.length <= 10^50 <= profits[i], capital[i] <= 10^9
Problem 21: Single-Threaded CPU
LeetCode: 1834. Single-Threaded CPU
Description
You are given n tasks where tasks[i] = [enqueueTime, processingTime]. A single-threaded CPU, whenever idle, picks the available task (enqueue time <= current time) with the shortest processing time, breaking ties by smaller original index. If no task is available it idles to the next enqueue time. Return the order of task indices in which the CPU processes them. Use a min-heap keyed on (processingTime, index).
Examples
Example 1:
Input: tasks = [[1, 2], [2, 4], [3, 2], [4, 1]]
Output: [0, 2, 3, 1]
At time 1 only task 0 is ready; by time 3 the shortest ready task is 2, then 3, then 1.
Example 2:
Input: tasks = [[7, 10], [7, 12], [7, 5], [7, 4], [7, 2]]
Output: [4, 3, 2, 0, 1]
All arrive at time 7, so they run by ascending processing time.
Example 3:
Input: tasks = [[1, 2]]
Output: [0]
Constraints
1 <= tasks.length <= 10^51 <= enqueueTime, processingTime <= 10^9
Problem 22: Cargo Merge Cost
Description
A freighter combines n cargo piles into a single pile. Merging two piles costs the sum of their weights and produces a new pile of that combined weight. Always merge the two lightest piles (Huffman-style). Return the total merge cost of reducing weights to one pile, or 0 if there are fewer than two piles. Use a min-heap.
Examples
Example 1:
Input: weights = [4, 3, 2, 6]
Output: 29
Merge 2+3=5 (cost 5), then 4+5=9 (cost 9), then 6+9=15 (cost 15); total 5+9+15 = 29.
Example 2:
Input: weights = [1, 2, 3]
Output: 9
Merge 1+2=3 (cost 3), then 3+3=6 (cost 6); total 9.
Example 3:
Input: weights = [5]
Output: 0
A single pile needs no merging.
Constraints
0 <= weights.length <= 10^51 <= weights[i] <= 10^4
Problem 23: Running Median Checksum
Description
A sensor reports a stream of integer readings. After each reading, take the running lower median: the element at index (count - 1) / 2 of the readings-so-far in sorted order (so for an even count it is the lower of the two middle values). Return the sum of all running lower medians across the whole stream. Maintain the medians online with two heaps.
Examples
Example 1:
Input: stream = [2, 1, 5, 7, 2, 0, 5]
Output: 13
The running lower medians are 2, 1, 2, 2, 2, 2, 2, which sum to 13.
Example 2:
Input: stream = [5]
Output: 5
The only median is 5.
Example 3:
Input: stream = [3, 1]
Output: 4
Medians are 3 then lower-of(1, 3) = 1; sum 4.
Constraints
1 <= stream.length <= 10^5-10^4 <= stream[i] <= 10^4
Problem 24: Galactic Relay Merge
Description
Given k strictly-ascending id streams (each an int[]), consider their merged ascending sequence with duplicates kept across streams. Return the id at 0-indexed position target in that merged sequence, or -1 if the merged sequence has fewer than target + 1 elements. Do not fully materialize the merge; advance a min-heap target + 1 times.
Examples
Example 1:
Input: streams = [[1, 4, 7], [2, 5, 8], [3, 6, 9]], target = 4
Output: 5
Merged: 1, 2, 3, 4, 5, ...; index 4 is 5.
Example 2:
Input: streams = [[1, 2, 3]], target = 0
Output: 1
Example 3:
Input: streams = [[10], [11]], target = 5
Output: -1
Only two elements exist, so index 5 is out of range.
Constraints
1 <= k <= 10^40 <= target- Total elements across all streams is at most
10^5. - Each stream is strictly ascending.
kd-Trees
What It’s For
What kinds of queries — nearest neighbor, k-NN, range/box search over points in space — make a kd-tree worth building over a plain list?
Alternating Split Dimensions
Why does the splitting axis cycle through \( x, y, z, \dots \) as you descend, and how does the node’s depth (mod \( k \)) pick the axis?
What a Split Plane Means Geometrically
Why does each node carve space into “everything left of the plane” and “everything right,” and why is the plane axis-aligned?
Node Layout & Representation
What does each node store — a point, an implicit splitting axis from its depth, and two children — and why does the plane NOT need to be stored explicitly?
Building a Balanced Tree
Why does choosing the MEDIAN point along the current axis at each level keep the tree balanced, and what’s the build cost?
Median Selection
How do you find the median along an axis without fully sorting every level — what does quickselect / nth-element buy you, and why does it matter for total build time?
Why Balance Decays Under Inserts
Why does building once from a median split stay balanced, but inserting points one at a time afterward can skew the tree?
Insertion & Deletion
How do you descend axis-by-axis to place a new point, and why is deletion awkward enough that people often mark-and-rebuild instead of truly removing?
Range Queries
How do you decide whether a node’s implied region is fully inside, fully outside, or straddling the query box — and which subtrees can you skip entirely?
The Region-Box Test
As you recurse, how do you carry and shrink the bounding region for each child so the inside/outside/straddle test is cheap?
Nearest-Neighbor Search
After diving to a leaf for a first candidate, why might you still have to back up and explore the OTHER side of a split you already passed?
Pruning with the Splitting Plane
What single distance comparison — distance from query to the split plane vs. best-distance-so-far — lets you discard an entire subtree unvisited?
Maintaining Best-So-Far
Why does the order in which you visit the near vs. far child matter for how much you can prune?
Curse of Dimensionality
Why does search degrade toward \( O(n) \) as the number of dimensions \( k \) grows — why does almost everything become “the far side might be closer” so pruning fails?
vs. Quadtrees & Other Structures
When is a kd-tree the better choice than a quadtree, a uniform grid, or a brute-force scan, and when is it the worse one?
Time Complexity
Separate the one-time build from per-query costs, and be explicit about what makes the worst case bite.
Build
Why is building \( O(n\log n) \) when you use linear-time median selection at each level — what’s the recurrence shape intuitively?
Range Query
Why is a balanced range query roughly \( O(\sqrt{n} + m) \) in 2D (\( m \) reported points), and what about the query box size and dimension changes it?
Nearest-Neighbor: Best / Average / Worst
Why is NN often close to \( O(\log n) \) in low dimensions, but \( O(n) \) worst case — what query position or dimensionality triggers visiting nearly every node?
Space Complexity
Why is the tree itself \( O(n) \) — one node per point — and what does a pointer-based representation cost over a flat array?
Recursion Call-Stack
Why does query and build recursion cost \( O(\text{height}) \) stack — \( O(\log n) \) balanced, \( O(n) \) degenerate — and how does that interact with deep skewed trees?
Pitfalls
What breaks if you forget to alternate axes, compare on the wrong coordinate at a level, let the tree skew after many inserts, or skip the back-up/other-side step in nearest-neighbor?
Implementation Walkthrough
Plan the code in parts before you write it — each sub-section tells you what to work out, not the answer.
Node Type & Axis-by-Depth
What does the node hold, and how do you derive the comparison axis from depth without storing it?
Recursive Median Build
How does the build pick the axis, select the median along it, set that as the node, and recurse on the two halves?
Range-Query Traversal
How do you pass the query box down, test each node’s point for inclusion, and prune children whose region can’t overlap?
Nearest-Neighbor Traversal & Pruning
How do you descend to the near child first, update best-so-far, then decide via the plane-distance test whether the far child is worth visiting?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Quadtrees
What It’s For
What 2D problems — spatial indexing, collision/broad-phase detection, image region compression, level-of-detail — make a quadtree the natural structure?
Recursive Four-Way Subdivision
How does each node carve its square region into four equal quadrants, and what condition makes the recursion stop dividing?
Why Four (and the 3D Cousin)
Why does 2D split into four, and how does the same idea become an octree’s eight in 3D?
Node Layout & Representation
What does a node store — its bounding box, a point bucket OR four child pointers — and how do you label and order the quadrants (NW, NE, SW, SE)?
Point vs. Region Quadtrees
What distinguishes storing points at leaves with a capacity threshold (point quadtree) from partitioning the space itself by value (region quadtree, e.g. image pixels)?
Insertion & Bucket Splitting
When a leaf quadrant overflows its capacity, how do you create four children and redistribute the existing points down into them?
Which Quadrant Does a Point Fall In?
Given a point and a node’s center, how do you compute the correct child in two comparisons (above/below, left/right of center)?
Subdivide-Then-Reinsert
Why, on a split, do you re-route every stored point through the same quadrant test rather than guessing where they go?
Deletion & Merging
How do you remove a point, and when can four sparse children collapse back into a single leaf to keep the tree from staying needlessly deep?
Spatial Queries & Collision Checks
Why does testing a query region against four quadrants prune most of the plane quickly, and which children do you recurse into vs. skip?
Region Overlap Test
How do you decide a child’s box is disjoint from, contained in, or straddling the query region, and how does each verdict steer the recursion?
Depth, Balance & Clustering
Why can tightly clustered points drive a quadtree very deep and unbalanced — splitting again and again with points still together — unlike a median-split kd-tree?
The Coincident-Points Trap
Why can two identical (or sub-resolution) points make subdivision recurse forever, and what guard (max depth) stops it?
Time Complexity
Tie costs to tree DEPTH, and be explicit that depth depends on the point distribution, not just \( n \).
Insert & Point Query
Why are these \( O(\text{depth}) \) — \( O(\log n) \) when points spread evenly, but degrading toward \( O(n) \) when they clump into one corner?
Range Query
Why is the cost output-sensitive — \( O(\text{visited nodes} + m) \) reported points — and what query/region shapes make it visit a lot?
Best / Average / Worst Triggers
What point distribution gives the balanced best case, and what distribution (heavy clustering, coincident points) triggers the degenerate worst case?
Space Complexity
Why is total space \( O(n) \) in the well-spread case but potentially worse with deep clustering — internal nodes can outnumber points?
Empty-Child Overhead & Recursion Stack
Why do allocating four children even for sparse quadrants waste space, and what stack depth (\( O(\text{depth}) \)) does recursive insert/query pay?
vs. kd-Trees & Grids
When do you reach for a quadtree over a kd-tree or a uniform grid — and what’s the trade-off in each direction (adaptivity, balance guarantees, simplicity)?
Pitfalls
What goes wrong with points exactly on quadrant boundaries, duplicate/coincident points exceeding capacity forever, never merging empty children, or forgetting a max-depth cap?
Implementation Walkthrough
Plan the code in parts before you write it — each sub-section tells you what to work out, not the answer.
Node, Bounds & Capacity
What does a node hold for its bounding box, point bucket, and four child slots, and where does the capacity threshold live?
The Quadrant Selector
How do you compare a point to the node’s center to return NW / NE / SW / SE, and how do you compute each child’s bounding box?
Insert With Split
How does insert add to the bucket, detect overflow, subdivide, and push every point (old and new) down through the selector?
Range-Query Recursion
How do you walk the tree, skip children whose box can’t overlap the query, collect points inside, and recurse only into straddling children?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Spatial Trees: Problem Set
Every problem is an implementation task: fill in the stub in problemset/ and make
its test in tests/problemset/ pass. The work is 2-D geometry — squared distances,
points inside rectangles, nearest neighbours, closest pairs, bounding boxes, and
quadrant counts — the same primitives a k-d tree or quadtree leans on. Problems are
grouped into Foundational warm-ups and Applied Problems, numbered
contiguously and ordered roughly easy to hard.
Foundational
Problem 1: Squared Euclidean Distance
Description
Given two points a = [ax, ay] and b = [bx, by] in the plane, return the squared Euclidean distance between them, (ax - bx)^2 + (ay - by)^2. The square root is deliberately omitted: keeping the squared value avoids floating-point error and is all that nearest-neighbour comparisons ever need.
Examples
Example 1:
Input: a = [0, 0], b = [3, 4]
Output: 25
The legs are 3 and 4, so the squared distance is 9 + 16 = 25.
Example 2:
Input: a = [7, -2], b = [7, -2]
Output: 0
A point is distance zero from itself.
Example 3:
Input: a = [-1, -1], b = [2, 3]
Output: 25
(-3)^2 + (-4)^2 = 9 + 16 = 25.
Constraints
-10^4 <= ax, ay, bx, by <= 10^4- Coordinates are integers.
Problem 2: Point Inside a Rectangle
Description
An axis-aligned rectangle is given by its inclusive bounds [xMin, yMin, xMax, yMax] with xMin <= xMax and yMin <= yMax. Given a query point [px, py], return true if the point lies inside the rectangle or on its boundary, and false otherwise.
Examples
Example 1:
Input: rect = [0, 0, 4, 4], point = [2, 2]
Output: true
The point sits strictly inside.
Example 2:
Input: rect = [0, 0, 4, 4], point = [4, 1]
Output: true
A point on the right edge counts as inside.
Example 3:
Input: rect = [0, 0, 4, 4], point = [5, 2]
Output: false
px = 5 exceeds xMax = 4.
Constraints
-10^4 <= coordinates <= 10^4xMin <= xMaxandyMin <= yMax.
Problem 3: Bounding Box of Points
Description
Given a non-empty list of points points[i] = [x, y], return the smallest axis-aligned rectangle that contains them all, as [xMin, yMin, xMax, yMax]. This is the tightest bounding box — exactly what a spatial tree stores at each node to prune searches.
Examples
Example 1:
Input: points = [[1, 2], [3, 1], [2, 5]]
Output: [1, 1, 3, 5]
The minimum x is 1, minimum y is 1, maximum x is 3, maximum y is 5.
Example 2:
Input: points = [[4, 4]]
Output: [4, 4, 4, 4]
A single point’s bounding box is degenerate.
Example 3:
Input: points = [[-2, 3], [5, -1], [0, 0]]
Output: [-2, -1, 5, 3]
Constraints
1 <= points.length <= 10^5-10^4 <= x, y <= 10^4
Problem 4: Quadrant of a Point
Description
A quadtree node splits its region at a center [cx, cy] into four quadrants. Given the center and a query point [px, py] (not equal to the center on the axis being tested), return which quadrant the point falls in as one of the strings "NE", "NW", "SE", "SW". Use the convention that a point with px >= cx is on the East side and py >= cy is on the North side.
Examples
Example 1:
Input: center = [0, 0], point = [3, 5]
Output: "NE"
Right of center and above it.
Example 2:
Input: center = [0, 0], point = [-2, 4]
Output: "NW"
Left of center and above it.
Example 3:
Input: center = [10, 10], point = [4, 2]
Output: "SW"
Left of center and below it.
Constraints
-10^4 <= coordinates <= 10^4
Problem 5: Brute-Force Range Query
Description
Given a list of points points[i] = [x, y] and an axis-aligned query rectangle [xMin, yMin, xMax, yMax] with inclusive bounds, return the indices of all points that lie inside the rectangle (boundary included), in increasing index order. This linear scan is the baseline a k-d tree range query must beat.
Examples
Example 1:
Input: points = [[1, 1], [5, 5], [2, 3]], rect = [0, 0, 3, 3]
Output: [0, 2]
Points 0 and 2 fall inside [0,0]..[3,3]; point 1 does not.
Example 2:
Input: points = [[0, 0], [3, 0]], rect = [0, 0, 3, 0]
Output: [0, 1]
Both points lie on the rectangle’s boundary.
Example 3:
Input: points = [[4, 4]], rect = [0, 0, 1, 1]
Output: []
No point is inside.
Constraints
1 <= points.length <= 10^5-10^4 <= coordinates <= 10^4xMin <= xMaxandyMin <= yMax.
Applied Problems
Problem 6: K Closest Points to Origin
LeetCode: 973. K Closest Points to Origin
Description
Given a list of points points[i] = [x, y] and an integer k, return the k points closest to the origin [0, 0] by Euclidean distance. Compare points by squared distance x*x + y*y; the answer may be returned in any order. Assume 0 <= k <= points.length.
Examples
Example 1:
Input: points = [[1, 3], [-2, 2]], k = 1
Output: [[-2, 2]]
(-2,2) has squared distance 8 versus 10 for (1,3).
Example 2:
Input: points = [[3, 3], [5, -1], [-2, 4]], k = 2
Output: [[3, 3], [-2, 4]]
Squared distances are 18, 26, 20; the two smallest are picked (any order).
Example 3:
Input: points = [[1, 1]], k = 0
Output: []
Constraints
1 <= points.length <= 10^40 <= k <= points.length-10^4 <= x, y <= 10^4
Problem 7: Count Points in a Rectangle
Description
Given a list of points points[i] = [x, y] and an axis-aligned query rectangle [xMin, yMin, xMax, yMax] with inclusive bounds, return how many points lie inside the rectangle (boundary counts as inside). This is the range-count primitive a range tree answers in logarithmic time.
Examples
Example 1:
Input: points = [[1, 1], [5, 5], [2, 3], [3, 3]], rect = [0, 0, 3, 3]
Output: 3
Points (1,1), (2,3), (3,3) qualify.
Example 2:
Input: points = [[0, 0], [4, 4]], rect = [1, 1, 3, 3]
Output: 0
Neither point is inside.
Example 3:
Input: points = [[2, 2], [2, 2]], rect = [2, 2, 2, 2]
Output: 2
Duplicates are each counted.
Constraints
1 <= points.length <= 10^5-10^4 <= coordinates <= 10^4xMin <= xMaxandyMin <= yMax.
Problem 8: Nearest Neighbour Among Points
Description
Given a list of points points[i] = [x, y] and a query point [qx, qy], return the index of the point closest to the query by Euclidean distance. The query point may coincide with a listed point. Break ties by smallest index.
Examples
Example 1:
Input: points = [[0, 0], [5, 5], [1, 1]], query = [2, 2]
Output: 2
Point 2 (1,1) is squared distance 2 away, the nearest.
Example 2:
Input: points = [[3, 4], [3, 4]], query = [0, 0]
Output: 0
Two equally-near points; the smaller index wins.
Example 3:
Input: points = [[10, 10]], query = [10, 10]
Output: 0
The query lands exactly on the only point.
Constraints
1 <= points.length <= 10^5-10^4 <= coordinates <= 10^4
Problem 9: Points Within a Radius
Description
Given a list of points points[i] = [x, y], a center [cx, cy], and an integer radius, return how many points lie within (inclusive) Euclidean distance radius of the center. Compare squared distances against radius * radius to stay in integer arithmetic.
Examples
Example 1:
Input: points = [[0, 0], [3, 0], [5, 0]], center = [0, 0], radius = 3
Output: 2
(0,0) and (3,0) are within distance 3; (5,0) is not.
Example 2:
Input: points = [[1, 1], [-1, -1]], center = [0, 0], radius = 1
Output: 0
Both points are distance sqrt(2) > 1 away.
Example 3:
Input: points = [[2, 0]], center = [0, 0], radius = 2
Output: 1
A point exactly on the circle counts as inside.
Constraints
1 <= points.length <= 10^50 <= radius <= 2 * 10^4-10^4 <= coordinates <= 10^4
Problem 10: Quadrant Counts
Description
Given a list of points points[i] = [x, y] and a center [cx, cy], count how many points fall in each of the four quadrants relative to the center and return the counts as [ne, nw, sw, se]. Use the convention px >= cx is East and py >= cy is North, so a point with px >= cx and py >= cy is North-East. Every point lands in exactly one quadrant under this rule.
Examples
Example 1:
Input: points = [[1, 1], [-1, 1], [-1, -1], [1, -1]], center = [0, 0]
Output: [1, 1, 1, 1]
One point per quadrant.
Example 2:
Input: points = [[2, 3], [4, 5]], center = [0, 0]
Output: [2, 0, 0, 0]
Both points are North-East.
Example 3:
Input: points = [[0, 0]], center = [0, 0]
Output: [1, 0, 0, 0]
A point on the center counts as North-East (>= on both axes).
Constraints
1 <= points.length <= 10^5-10^4 <= coordinates <= 10^4
Problem 11: Closest Pair (Brute Force)
Description
Given at least two points points[i] = [x, y], return the smallest squared Euclidean distance between any two distinct points, checking all pairs. This O(n^2) answer is the reference value the divide-and-conquer closest-pair algorithm must match.
Examples
Example 1:
Input: points = [[0, 0], [3, 4], [1, 1]]
Output: 2
The closest pair is (0,0) and (1,1), squared distance 2.
Example 2:
Input: points = [[5, 5], [5, 6]]
Output: 1
Only one pair; vertical distance 1.
Example 3:
Input: points = [[1, 1], [1, 1], [9, 9]]
Output: 0
Two coincident points give distance 0.
Constraints
2 <= points.length <= 2000-10^4 <= x, y <= 10^4
Problem 12: Rectangle Overlap
LeetCode: 836. Rectangle Overlap
Description
Each axis-aligned rectangle is given as [x1, y1, x2, y2], where [x1, y1] is the bottom-left corner and [x2, y2] is the top-right corner. Two rectangles overlap if the area of their intersection is positive; rectangles that touch only along an edge or corner do not overlap. Given two rectangles, return true if they overlap.
Examples
Example 1:
Input: rec1 = [0, 0, 2, 2], rec2 = [1, 1, 3, 3]
Output: true
They share a 1-by-1 square of positive area.
Example 2:
Input: rec1 = [0, 0, 1, 1], rec2 = [1, 0, 2, 1]
Output: false
They touch along the line x = 1 but share no area.
Example 3:
Input: rec1 = [0, 0, 1, 1], rec2 = [2, 2, 3, 3]
Output: false
The rectangles are disjoint.
Constraints
-10^4 <= coordinates <= 10^4x1 < x2andy1 < y2for each rectangle.
Problem 13: Manhattan Nearest Neighbour
Description
Given a list of points points[i] = [x, y] and a query point [qx, qy], return the index of the point closest to the query under the Manhattan (L1) distance |x - qx| + |y - qy|. Break ties by smallest index. Manhattan distance is the metric a k-d tree uses for grid-like data.
Examples
Example 1:
Input: points = [[1, 1], [4, 4], [0, 3]], query = [0, 0]
Output: 0
L1 distances are 2, 8, 3; point 0 is nearest.
Example 2:
Input: points = [[2, 0], [0, 2]], query = [0, 0]
Output: 0
Both are distance 2; the smaller index wins.
Example 3:
Input: points = [[5, 5]], query = [5, 5]
Output: 0
Constraints
1 <= points.length <= 10^5-10^4 <= coordinates <= 10^4
Problem 14: Median Split Point
Description
Building a balanced k-d tree means splitting points at the median of one coordinate. Given a list of points points[i] = [x, y] and an axis (0 for x, 1 for y), return the index of the point whose chosen coordinate is the lower median: if the coordinates are sorted, this is the element at position (n - 1) / 2. Break ties on equal coordinate by smaller original index.
Examples
Example 1:
Input: points = [[3, 1], [1, 2], [2, 9]], axis = 0
Output: 1
The x-coordinates are 3, 1, 2; sorted they are 1, 2, 3, lower median 2 belongs to point 1.
Example 2:
Input: points = [[0, 5], [0, 2], [0, 8], [0, 4]], axis = 1
Output: 3
The y-coordinates sorted are 2, 4, 5, 8; lower median of 4 elements is the 2nd value, 4, at point 3.
Example 3:
Input: points = [[7, 7]], axis = 0
Output: 0
A single point is its own median.
Constraints
1 <= points.length <= 10^5axisis0or1.-10^4 <= coordinates <= 10^4
Problem 15: Maximum Points in a Unit-Side Square
Description
Given a list of points points[i] = [x, y] and an integer side length s, place an axis-aligned s-by-s square anywhere (real-valued position allowed) and return the maximum number of points it can cover, counting points on the boundary. With n <= 2000 an O(n^2) sweep over candidate square positions is fine.
Examples
Example 1:
Input: points = [[0, 0], [1, 1], [2, 2]], s = 1
Output: 2
A 1-by-1 square covering (0,0) and (1,1) (e.g. corners at [0,0] and [1,1]) holds two points.
Example 2:
Input: points = [[0, 0], [0, 0], [5, 5]], s = 2
Output: 2
The two coincident points fit in one square.
Example 3:
Input: points = [[0, 0], [3, 0], [6, 0]], s = 1
Output: 1
No two points are within a unit square.
Constraints
1 <= points.length <= 20000 <= s <= 2 * 10^4-10^4 <= x, y <= 10^4
Problem 16: Closest Pair (Sorted Sweep)
Description
Given at least two points, return the smallest squared Euclidean distance between any two distinct points, but aim to beat the naive all-pairs scan. Sort the points by x, sweep left to right maintaining a candidate strip whose width is the current best distance, and only compare points within that strip. The returned value must equal the brute-force answer; the strip technique is the heart of divide-and-conquer closest pair.
Examples
Example 1:
Input: points = [[0, 0], [10, 10], [11, 11], [3, 4]]
Output: 2
The closest pair is (10,10) and (11,11), squared distance 2.
Example 2:
Input: points = [[1, 1], [2, 2]]
Output: 2
Example 3:
Input: points = [[0, 0], [0, 0], [100, 100]]
Output: 0
Coincident points give distance 0.
Constraints
2 <= points.length <= 10^5-10^4 <= x, y <= 10^4
Problem 17: K Nearest Neighbours to a Query
Description
Given a list of points points[i] = [x, y], a query point [qx, qy], and an integer k, return the indices of the k points nearest the query by Euclidean distance, ordered from nearest to farthest. Break ties on equal distance by smaller index. Assume 0 <= k <= points.length.
Examples
Example 1:
Input: points = [[1, 0], [2, 0], [5, 0]], query = [0, 0], k = 2
Output: [0, 1]
Distances are 1, 2, 5; the two nearest are points 0 and 1.
Example 2:
Input: points = [[3, 4], [3, 4], [0, 1]], query = [0, 0], k = 1
Output: [2]
Point 2 at distance 1 is nearest.
Example 3:
Input: points = [[9, 9]], query = [0, 0], k = 0
Output: []
Constraints
1 <= points.length <= 10^50 <= k <= points.length-10^4 <= coordinates <= 10^4
Problem 18: Count Pairs Within Distance
Description
Given a list of points points[i] = [x, y] and an integer d, return the number of unordered pairs of distinct points whose Euclidean distance is at most d. Compare squared distances against d * d to stay in integer arithmetic. With n <= 3000 an all-pairs check is acceptable.
Examples
Example 1:
Input: points = [[0, 0], [1, 0], [2, 0]], d = 1
Output: 2
Pairs (0,1) and (1,2) are within distance 1; (0,2) is distance 2 apart.
Example 2:
Input: points = [[0, 0], [3, 4]], d = 5
Output: 1
The single pair is exactly distance 5 apart, which counts.
Example 3:
Input: points = [[0, 0], [10, 10], [20, 20]], d = 1
Output: 0
No pair is close enough.
Constraints
2 <= points.length <= 30000 <= d <= 5 * 10^4-10^4 <= x, y <= 10^4
Union-Find
What It Is & When To Use It
What does a structure that maintains a partition of \( n \) elements into non-overlapping groups let you answer, and which problems (dynamic connectivity, cycle detection in Kruskal’s MST, percolation, image-blob labeling, account-merging) reach for it?
The Core Idea: Tracking Membership, Not Contents
Why is “which group is this element in?” the only question union-find answers well, and why doesn’t it support listing a set’s members, ordering them, or splitting sets apart afterward?
The Two Operations That Define It
Why do find (which group?) and union (merge two groups) form a complete interface, and how does connected reduce to two finds?
Representative Elements
How does each set pick one canonical member to stand for the whole group, and why does “same representative” become the test for “same set”?
Equivalence As Same-Root
Why is connected(a, b) exactly find(a) == find(b), and what makes that correct regardless of how the trees underneath are shaped?
The Parent-Pointer Forest
How is the whole structure encoded as a single parent[] array where each entry points at a parent, and what value marks a node as a root?
Why A Forest, Not A Single Tree
Why does each disjoint set become its own tree, and why is the collection of trees (a forest) the natural picture for a partition?
Initialization: Every Element Its Own Set
Why does makeSet start each element pointing at itself as a singleton root, and what partition does that initial state represent?
Invariants That Must Always Hold
What properties must stay true after every operation — every node reaches exactly one root, roots self-reference, no cycles ever form — and why does union linking roots (not arbitrary nodes) preserve all three?
Coding find
How do you walk parent pointers up until a node that points at itself, and why is that self-pointer the loop’s stopping condition rather than a null check?
Coding union
Why must you first find both roots, and why is linking one root under the other (rather than linking the two input nodes directly) the only move that keeps the forest valid?
The No-Op Union
What should happen when both elements already share a root, and why must you skip the link entirely instead of creating a self-loop?
Naive Cost Without Optimizations
How can repeatedly unioning in an adversarial order build a single long chain, and why does that make find walk a path of length proportional to \( n \)?
Time Complexity
Why does every operation’s cost reduce to “how far is this node from its root?”, making tree height the single quantity that controls everything?
find — Best, Average, Worst
Why is find \( O(1) \) when a node sits just under its root, but \( O(n) \) in a degenerate chain — and what determines where on that spectrum you land?
union — Cost Dominated By find
Why is union itself just two finds plus \( O(1) \) of pointer rewiring, so its asymptotic cost equals that of find?
Why Plain Union-Find Isn’t Good Enough
Why does the \( O(n) \) worst case motivate path compression and union by rank, and what target per-operation bound do they together aim for?
Space Complexity
Why is the whole structure just one parent[] array of \( n \) integers — \( O(n) \) total — and why is there no per-operation extra allocation in the basic version?
Cost Of Adding Rank Or Size
Why does layering on union by rank or size add only one more length-\( n \) array, leaving the asymptotic space at \( O(n) \)?
Trade-offs & What It Can’t Do
What do you give up — ordered iteration, deletion, set enumeration, un-union — in exchange for near-constant connectivity queries, and when does that make union-find the wrong tool?
Pitfalls
Where do bugs hide — calling union on raw nodes instead of their roots, forgetting the self-parent base case, assuming you can undo a union, or off-by-one errors on element indices?
Implementation Walkthrough
Before writing code, plan the pieces below — each prompt tells you what to work out, not the answer.
The parent[] Array
How will you size and initialize the backing array so every element begins as its own root, and what sentinel marks a root?
find By Pointer-Walking
What loop walks from a node to its root, and how do you know you’ve arrived?
union By Linking Roots
How do you resolve both roots, detect the already-connected case, and link one under the other?
connected As A Thin Wrapper
How does this method reuse find without duplicating any traversal logic?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Path Compression
What It Is & Why It Matters
What does path compression do to the tree during a find, and why does flattening structure pay off on every future query rather than just the current one?
The Problem It Fixes
How do long parent-pointer chains form, and why does walking them repeatedly make find the bottleneck of the whole structure?
Core Idea: Re-Point Nodes To The Root
After a find discovers the root, why does it help to make every node you touched point straight at that root, and why is this safe — does re-pointing ever change which set a node belongs to?
The Invariant It Preserves
Why does compression change tree shape but never tree membership, so find still returns the same root for every node?
Full Compression (Two-Pass)
How does the classic version first walk up to find the root, then walk the path a second time rewiring each node to it — and what does that double traversal cost in code?
Recursive One-Liner
How does the recursive find compress the path on the way back up, and what is the trade-off in stack usage for very deep trees?
Why Recursion Reaches The Root First
Why must the recursive call return the root before any reassignment happens, and how does the unwinding stack do the rewiring for free?
Path Splitting & Path Halving
How do these cheaper one-pass variants flatten the tree partially by re-pointing each node to its grandparent, and why are they often preferred in practice over full compression?
Halving vs. Splitting
What is the difference between updating every node to its grandparent (halving) versus alternate nodes (splitting), and why do both still shorten future paths?
How It Reshapes The Forest
Why do trees get dramatically flatter over a run of operations, so most nodes end up just one hop from the root, and why does that mean early expensive finds make later ones cheap?
Time Complexity
Why can’t you judge compression by a single operation, and why must you reason over a whole sequence of finds to see its real benefit?
A Single find — Worst Case
Why can one individual find still cost \( O(n) \) the first time it traverses a long chain, even though it shortens that chain afterward?
Amortized Over Many Finds (Intuition Only)
Why does each costly traversal permanently destroy the long path it walked, so the total work over many finds is far below “worst case times count” — with the precise near-constant bound deferred to the inverse-Ackermann note?
Compression Alone vs. With Union By Rank
Why does compression by itself already give a strong amortized improvement, but the celebrated near-constant per-op bound needs it paired with rank/size linking?
Space Complexity
Why does compression add no extra storage at all — it only rewrites existing parent[] entries — so space stays \( O(n) \)? What hidden space does the recursive variant use that the iterative one avoids?
Trade-offs
Why might you pick path halving over full compression despite slightly less flattening, and when does the recursive form’s call-stack depth become a real risk?
Pitfalls
Where do mistakes happen — forgetting the second pass in the iterative version, accidentally compressing toward a non-root, mutating during traversal incorrectly, or stack overflow on adversarial chains?
Implementation Walkthrough
Before writing code, plan the pieces below — each prompt tells you what to work out, not the answer.
Iterative find With Two Passes
How do you find the root in one loop, then re-point every node on the path in a second loop?
Recursive find With Compression
How does a recursive call let you both find the root and rewire each node as the stack unwinds, in a single tidy method?
Path-Halving One-Pass find
How can a single loop re-point each node to its grandparent while still climbing to the root?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Union by Rank
What It Is & Why It Matters
What does union by rank decide at each merge, and why does that single choice keep trees short enough to make find fast?
The Problem It Fixes
Without a linking rule, how can union accidentally stack a tall tree onto a short one and grow height needlessly, and why is height the enemy?
Rank As A Stand-In For Height
What does a node’s rank represent, and why is it only an upper bound on the true height — especially once path compression starts flattening trees behind rank’s back?
Why We Don’t Track Exact Height
Why is it acceptable (and cheaper) to let rank drift above the real height after compression, rather than recomputing exact heights?
The Linking Rule
When merging two roots, why attach the smaller-rank root under the larger-rank root, and what does that do to the combined tree’s height?
Tie-Breaking & Rank Increments
When two roots have equal rank, why does one absorb the other and the survivor’s rank tick up by exactly one — and why is that the only situation in which any rank ever changes?
Storing Rank Alongside Parents
How do you keep a parallel rank[] array initialized to zero, and why does only a root’s rank entry ever matter for decisions?
Coding union With Rank
How does the merge read both roots’ ranks, branch on the comparison, link accordingly, and bump the rank only on a tie — and why does this stay \( O(1) \) on top of the two finds?
Logarithmic Height Guarantee
Intuitively, why does rank-based linking keep any tree’s height at \( O(\log n) \) even before path compression — what relationship between a root’s rank and the number of nodes beneath it forces this?
Why Rank r Implies At Least 2^r Nodes
Conceptually, why must a root of rank \( r \) have at least \( 2^r \) descendants, and why does that cap rank — and therefore height — at about \( \log_2 n \)?
Union By Size As An Alternative
How does tracking subtree size (attach the smaller-count root under the larger) compare to rank, why does it give the same \( O(\log n) \) height guarantee, and why is it often simpler to reason about?
Time Complexity
Why does union by rank turn the structure’s per-operation cost from \( O(n) \) into something logarithmic, even with no compression?
find — With Rank Only
Why does bounding height at \( O(\log n) \) directly bound a find traversal at \( O(\log n) \)?
union — With Rank Only
Why is union still two finds plus \( O(1) \), so it inherits the \( O(\log n) \) bound from find?
What Changes When You Add Compression
Why does combining rank with path compression push the amortized per-op cost below logarithmic toward near-constant — with the precise bound covered in the inverse-Ackermann note?
Space Complexity
Why does union by rank add exactly one length-\( n \) array (the ranks) on top of parent[], keeping total space at \( O(n) \), and why is rank a small integer rather than a large counter?
Trade-offs
Why choose rank vs. size — does either ever beat the other in practice, and why do many libraries just pick one and move on?
Pitfalls
Where do bugs creep in — comparing ranks of non-roots, forgetting to bump rank on ties, bumping rank when ranks differ, or mixing up the rank-update vs. size-update rules?
Implementation Walkthrough
Before writing code, plan the pieces below — each prompt tells you what to work out, not the answer.
The rank[] Array
How do you allocate and initialize ranks, and what starting value does every singleton get?
Comparing Roots And Linking
How does union branch on the three cases — left rank smaller, right rank smaller, equal — and what does each branch do?
The Single Rank Increment
In which branch, and by how much, does a rank change, and why never anywhere else?
Swapping To Union By Size
What would you change to track sizes instead of ranks, and which array and update rule replaces the rank logic?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Amortized Analysis & the Inverse Ackermann Function
What This Section Answers
Why is union-find with both optimizations described as “near-constant per operation” rather than truly \( O(1) \), and what does that distinction actually mean for a program that runs millions of operations?
Amortized vs. Worst-Case, Intuitively
Why can a single find still be slow while a long sequence of operations averages out to almost-constant per op — and why is the per-sequence average the honest number to quote here?
Why The Average Is What Matters In Practice
If you run \( m \) operations back to back, why does total-time-divided-by-\( m \) describe real performance better than the cost of the single worst operation?
How The Two Optimizations Combine
Why does path compression flatten trees over time while union by rank stops them getting tall in the first place — and why is it their teamwork, not either alone, that produces the famous bound?
Compression Pays For Itself
Why does each expensive deep find permanently shorten the paths it touched, so the very work it did makes all future finds along that path cheaper?
Rank Caps The Damage A Single Find Can Do
Why does rank-bounded height ensure that even an “expensive” find can’t be longer than about \( \log n \) before compression then collapses it?
The “Self-Funding” Picture (Accounting Intuition)
Imagine each operation pre-paying a tiny surplus that later covers the occasional costly find — why does this bookkeeping idea explain the low average without any formal potential-function proof?
Meet \( \alpha(n) \), Conceptually
Treat \( \alpha(n) \) as “how many times you can repeatedly apply a tower-of-exponentials shrink before reaching 1” — why does that make it grow almost imperceptibly slowly as \( n \) rises?
Why It’s At Most ~4 For Any Real Input
For any \( n \) you could ever store — far beyond the number of atoms in the universe — why does \( \alpha(n) \) stay below five, so engineers round it to “a small constant”?
\( \alpha(n) \) vs. \( \log n \) vs. \( \log^* n \)
Why is \( \alpha(n) \) even slower-growing than the already-tiny iterated logarithm \( \log^ n \), and where does it sit on the hierarchy of slow functions?*
The Headline Result (State, Don’t Derive)
State the \( O(m,\alpha(n)) \) bound for \( m \) operations on \( n \) elements — what two optimizations does it assume, and whose analysis (Tarjan) established it?
Time Complexity, Reconsidered
Why is it correct to say each operation is “amortized \( O(\alpha(n)) \)” but misleading to say “\( O(1) \) worst case”, and how do you state the bound precisely?
Per-Operation Amortized Cost
Why does dividing the \( O(m,\alpha(n)) \) total by \( m \) give \( O(\alpha(n)) \) per operation, and why treat that as effectively constant?
Single-Operation Worst Case
Why can one individual operation still cost up to \( O(\log n) \) even under the amortized bound, and why doesn’t that contradict the near-constant average?
Space Complexity
Why does achieving this near-constant time cost only the two length-\( n \) arrays (parent and rank/size), so the celebrated speed needs just \( O(n) \) space and no auxiliary bookkeeping?
Why We Just Call It Constant
Since \( \alpha(n) \) never exceeds a tiny constant for any realistic \( n \), why is it honest engineering practice to treat each union-find op as effectively \( O(1) \) in back-of-the-envelope estimates?
What Would Break The Bound
Which omission — dropping compression, dropping rank, or naive root-linking — sends you back toward \( O(\log n) \) or even \( O(n) \) per op, and why does each one matter?
Takeaways To Remember
If you forget every proof detail, what three facts about union-find’s time and space cost should stay with you forever?
Implementation Walkthrough
Before writing code, plan the pieces below — each prompt tells you what to work out, not the answer.
Combining Both Optimizations In One Class
How do parent[] and rank[] live together, and how do find and union each touch both to realize the near-constant bound?
Where Compression Lives
In which method does the flattening happen, and why is that the only place it’s needed?
Where Rank Lives
In which method does the rank comparison and increment happen, and why is that the only place it’s needed?
A Counter Worth Tracking
How might you maintain a running count of disjoint sets so you can answer “how many groups remain?” in \( O(1) \)?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Disjoint Sets: Problem Set
Every problem is an implementation task: fill in the stub in problemset/ and make
its test in tests/problemset/ pass. The theme throughout is connectivity, merging,
and grouping — friend circles, islands, redundant connections, account merges, and
Kruskal-style spanning trees — all powered by a union-find (disjoint set) structure.
The set opens with foundational union-find usage, then weaves through applied LeetCode
and contest-style problems, easy to hard.
Foundational
Problem 1: Count Connected Components
LeetCode: 323. Number of Connected Components in an Undirected Graph
Description
You are given n nodes labelled 0 to n - 1 and a list of undirected edges,
where each edge [u, v] connects nodes u and v. Return the number of connected
components in the graph.
Build a union-find over the n nodes, union the endpoints of every edge, then count
the number of distinct representatives (roots).
Examples
Example 1:
Input: n = 5, edges = [[0,1],[1,2],[3,4]]
Output: 2
Nodes {0,1,2} form one component and {3,4} form another.
Example 2:
Input: n = 5, edges = [[0,1],[1,2],[2,3],[3,4]]
Output: 1
All nodes are chained into a single component.
Example 3:
Input: n = 4, edges = []
Output: 4
With no edges, every node is its own component.
Constraints
1 <= n <= 20000 <= edges.length <= 5000edges[i].length == 20 <= u, v < n- There are no self-loops or repeated edges.
Problem 2: Detect a Cycle in an Undirected Graph
LeetCode: 261. Graph Valid Tree
Description
Given n nodes labelled 0 to n - 1 and a list of undirected edges, return
true if the graph contains a cycle, and false otherwise.
Process edges one at a time with a union-find. If the two endpoints of an edge already share a representative before you union them, that edge closes a cycle.
Examples
Example 1:
Input: n = 3, edges = [[0,1],[1,2],[2,0]]
Output: true
The third edge connects two nodes already joined through 0-1-2, forming a cycle.
Example 2:
Input: n = 4, edges = [[0,1],[1,2],[2,3]]
Output: false
The edges form a simple path with no cycle.
Example 3:
Input: n = 2, edges = []
Output: false
No edges means no cycle.
Constraints
1 <= n <= 20000 <= edges.length <= 5000edges[i].length == 20 <= u, v < n
Problem 3: Redundant Connection
LeetCode: 684. Redundant Connection
Description
A graph started as a tree with n nodes labelled 1 to n, then exactly one extra
edge was added. The resulting graph has n edges. Return the one edge that can be
removed so the remaining graph is a tree. If multiple answers qualify, return the edge
that appears last in the input.
Union endpoints in input order; the first edge whose endpoints are already connected is the redundant one.
Examples
Example 1:
Input: edges = [[1,2],[1,3],[2,3]]
Output: [2,3]
The edge [2,3] connects two nodes already joined through node 1.
Example 2:
Input: edges = [[1,2],[2,3],[3,4],[1,4],[1,5]]
Output: [1,4]
[1,4] closes the cycle 1-2-3-4-1.
Example 3:
Input: edges = [[1,2],[2,3],[1,3]]
Output: [1,3]
[1,3] is the last edge whose endpoints were already connected.
Constraints
3 <= n <= 1000edges.length == nedges[i].length == 21 <= ai < bi <= n- The given graph is connected and contains exactly one extra edge.
Problem 4: Largest Set Size
Description
You are given n elements labelled 0 to n - 1 and a sequence of union operations,
each a pair [a, b] that merges the sets containing a and b. After applying all
unions, return the size of the largest resulting set.
Track set sizes as you union (union by size makes this natural), then return the maximum size over all roots.
Examples
Example 1:
Input: n = 6, unions = [[0,1],[1,2],[3,4]]
Output: 3
Set {0,1,2} has size 3; {3,4} has size 2; {5} has size 1.
Example 2:
Input: n = 4, unions = []
Output: 1
With no unions, every set is a singleton.
Example 3:
Input: n = 5, unions = [[0,1],[2,3],[3,4],[2,4]]
Output: 3
Set {2,3,4} reaches size 3; the redundant union [2,4] does not change sizes.
Constraints
1 <= n <= 10^50 <= unions.length <= 10^50 <= a, b < n
Problem 5: Are All Elements Connected?
Description
You are given n elements labelled 0 to n - 1 and a list of union operations.
After applying all of them, return true if every element belongs to a single set,
and false otherwise.
Apply the unions, then check that the number of distinct representatives is exactly one.
Examples
Example 1:
Input: n = 3, unions = [[0,1],[1,2]]
Output: true
All three elements end up in one set.
Example 2:
Input: n = 4, unions = [[0,1],[2,3]]
Output: false
Two separate sets {0,1} and {2,3} remain.
Example 3:
Input: n = 1, unions = []
Output: true
A single element is trivially fully connected.
Constraints
1 <= n <= 10^50 <= unions.length <= 10^50 <= a, b < n
Applied Problems
Problem 6: Number of Provinces
LeetCode: 547. Number of Provinces
Description
There are n cities. Some are directly connected. A province is a group of cities
that are directly or indirectly connected, with no city outside the group. You are
given an n x n matrix isConnected where isConnected[i][j] = 1 means city i and
city j are directly connected, and 0 means they are not. Return the total number
of provinces.
The matrix is symmetric with ones on the diagonal. Union city i with city j
whenever isConnected[i][j] == 1, then count the components.
Examples
Example 1:
Input: isConnected = [[1,1,0],[1,1,0],[0,0,1]]
Output: 2
Cities 0 and 1 form one province; city 2 forms another.
Example 2:
Input: isConnected = [[1,0,0],[0,1,0],[0,0,1]]
Output: 3
No city is connected to any other, so each is its own province.
Example 3:
Input: isConnected = [[1,1,1],[1,1,1],[1,1,1]]
Output: 1
Every city is connected, forming a single province.
Constraints
1 <= n <= 200isConnected[i].length == nisConnected[i][j]is0or1isConnected[i][i] == 1isConnected[i][j] == isConnected[j][i]
Problem 7: Find if Path Exists in Graph
LeetCode: 1971. Find if Path Exists in Graph
Description
There is a bidirectional graph with n vertices labelled 0 to n - 1, described by
a list of edges where each edges[i] = [ui, vi]. Given a source and a
destination vertex, return true if a path exists between them.
Union every edge, then return whether source and destination share a representative.
Examples
Example 1:
Input: n = 3, edges = [[0,1],[1,2],[2,0]], source = 0, destination = 2
Output: true
A path 0 -> 2 exists directly through the edge [2,0].
Example 2:
Input: n = 6, edges = [[0,1],[0,2],[3,5],[5,4],[4,3]], source = 0, destination = 5
Output: false
Vertices 0 and 5 lie in different components.
Example 3:
Input: n = 1, edges = [], source = 0, destination = 0
Output: true
A vertex always has a path to itself.
Constraints
1 <= n <= 2 * 10^50 <= edges.length <= 2 * 10^5edges[i].length == 20 <= ui, vi, source, destination < n
Problem 8: Satisfiability of Equality Equations
LeetCode: 990. Satisfiability of Equality Equations
Description
You are given an array of strings equations representing relationships between
single-lowercase-letter variables. Each string is either "a==b" or "a!=b". Return
true if it is possible to assign integers to the variables so that every equation is
satisfied.
Union all variables joined by ==. Then for every != equation, verify its two
variables are not in the same set.
Examples
Example 1:
Input: equations = ["a==b","b!=a"]
Output: false
a == b forces them equal, but b != a requires them different.
Example 2:
Input: equations = ["b==a","a==b"]
Output: true
Both equations agree; assigning a = b = 1 works.
Example 3:
Input: equations = ["a==b","b==c","a!=c"]
Output: false
Transitivity forces a == c, contradicting a != c.
Constraints
1 <= equations.length <= 500equations[i].length == 4equations[i][0]andequations[i][3]are lowercase lettersequations[i][1]is either=or!equations[i][2]is=
Problem 9: Number of Operations to Make Network Connected
LeetCode: 1319. Number of Operations to Make Network Connected
Description
There are n computers labelled 0 to n - 1 connected by connections, where each
connections[i] = [ai, bi] is a cable between two computers. You may unplug any cable
and reconnect it between any pair of computers. Return the minimum number of cables you
must move to connect all computers, or -1 if it is impossible.
You need at least n - 1 cables. Count components c with union-find; the answer is
c - 1, since each spare (cycle-forming) cable can bridge one extra component.
Examples
Example 1:
Input: n = 4, connections = [[0,1],[0,2],[1,2]]
Output: 1
There is one redundant cable; moving it joins the isolated computer 3.
Example 2:
Input: n = 6, connections = [[0,1],[0,2],[0,3],[1,2],[1,3]]
Output: 2
Two redundant cables connect the two remaining isolated computers.
Example 3:
Input: n = 6, connections = [[0,1],[0,2],[0,3],[1,2]]
Output: -1
With only 4 cables for 6 computers (fewer than n - 1 = 5), connection is impossible.
Constraints
1 <= n <= 10^51 <= connections.length <= min(n * (n - 1) / 2, 10^5)connections[i].length == 20 <= ai, bi < n- There are no repeated connections or self-loops.
Problem 10: Number of Islands
LeetCode: 200. Number of Islands
Description
Given an m x n binary grid of '1' (land) and '0' (water), return the number of
islands. An island is a maximal group of land cells connected 4-directionally
(horizontally or vertically).
Map each land cell (r, c) to an index r * n + c. Union adjacent land cells (right
and down neighbours suffice), then count the distinct roots among land cells.
Examples
Example 1:
Input: grid = [["1","1","0"],
["1","0","0"],
["0","0","1"]]
Output: 2
The top-left block of land is one island; the bottom-right cell is another.
Example 2:
Input: grid = [["1","1","1"],
["0","1","0"],
["1","1","1"]]
Output: 1
All land cells connect into a single H-shaped island.
Example 3:
Input: grid = [["0","0"],
["0","0"]]
Output: 0
There is no land, so there are no islands.
Constraints
m == grid.lengthn == grid[i].length1 <= m, n <= 300grid[i][j]is'0'or'1'
Problem 11: Most Stones Removed with Same Row or Column
LeetCode: 947. Most Stones Removed with Same Row or Column
Description
On a 2D plane there are n stones, each at an integer coordinate. A stone can be
removed if it shares its row or its column with another stone that has not been removed.
Return the largest possible number of stones you can remove.
Two stones in the same row or column belong to the same group. The maximum number
removable is n - (number of groups). Union stones by shared row and shared column,
then subtract the component count from n.
Examples
Example 1:
Input: stones = [[0,0],[0,1],[1,0],[1,2],[2,1],[2,2]]
Output: 5
All six stones form one group, so five can be removed.
Example 2:
Input: stones = [[0,0],[0,2],[1,1],[2,0],[2,2]]
Output: 3
The stones form two groups; 5 - 2 = 3 can be removed.
Example 3:
Input: stones = [[0,0]]
Output: 0
A lone stone shares no row or column, so none can be removed.
Constraints
1 <= stones.length <= 10000 <= xi, yi <= 10^4- No two stones share the same coordinate.
Problem 12: Accounts Merge
LeetCode: 721. Accounts Merge
Description
You are given a list of accounts, where accounts[i][0] is a name and the remaining
entries are emails belonging to that account. Two accounts belong to the same person if
they share at least one common email (names alone do not identify a person). Merge such
accounts and return the result: each merged account is the name followed by all its
emails in sorted order. Return the merged accounts in any order.
Union accounts that share an email by keying union-find on emails. Then collect emails per representative, sort them, and prepend the owning name.
Examples
Example 1:
Input: accounts = [["John","a@x.com","b@x.com"],
["John","b@x.com","c@x.com"],
["Mary","m@x.com"]]
Output: [["John","a@x.com","b@x.com","c@x.com"],
["Mary","m@x.com"]]
The two “John” accounts share b@x.com, so they merge; “Mary” stays separate.
Example 2:
Input: accounts = [["Alex","alex@x.com"],
["Alex","alex2@x.com"]]
Output: [["Alex","alex2@x.com"],
["Alex","alex@x.com"]]
The two “Alex” accounts share no email, so they remain distinct.
Example 3:
Input: accounts = [["Sam","s1@x.com","s2@x.com"],
["Sam","s2@x.com"],
["Sam","s1@x.com","s3@x.com"]]
Output: [["Sam","s1@x.com","s2@x.com","s3@x.com"]]
All three accounts link transitively through shared emails into one account.
Constraints
1 <= accounts.length <= 10002 <= accounts[i].length <= 101 <= accounts[i][j].length <= 30accounts[i][0]consists of English letters- All emails are valid and consist of lowercase letters and symbols.
Problem 13: Number of Provinces from Friend Pairs
LeetCode: 547. Number of Provinces
Description
There are n people labelled 0 to n - 1. You are given a list of friendships,
where each [a, b] means a and b are direct friends. A friend circle is a maximal
group of people connected directly or indirectly by friendships. Return the number of
friend circles.
This is the edge-list form of “number of provinces”: union each friendship pair, then count the resulting components.
Examples
Example 1:
Input: n = 4, friendships = [[0,1],[1,2]]
Output: 2
People {0,1,2} form one circle; person 3 is alone.
Example 2:
Input: n = 5, friendships = [[0,1],[2,3],[3,4]]
Output: 2
Circles {0,1} and {2,3,4} remain.
Example 3:
Input: n = 3, friendships = []
Output: 3
With no friendships, each person is their own circle.
Constraints
1 <= n <= 10^50 <= friendships.length <= 10^50 <= a, b < n
Problem 14: The Earliest Moment When Everyone Become Friends
LeetCode: 1101. The Earliest Moment When Everyone Become Friends
Description
There are n people in a social group labelled 0 to n - 1. You are given logs,
where each logs[i] = [timestamp, a, b] records that a and b became friends at the
given time. Friendship is symmetric and transitive. Return the earliest timestamp at
which every person is connected (directly or indirectly), or -1 if that never happens.
Sort the logs by timestamp, union friends as you go, and return the timestamp of the union that drops the component count to one.
Examples
Example 1:
Input: n = 4, logs = [[5,0,1],[10,1,2],[15,2,3]]
Output: 15
Only after the log at time 15 does everyone belong to a single group.
Example 2:
Input: n = 4, logs = [[3,0,1],[4,2,3]]
Output: -1
The group never becomes fully connected.
Example 3:
Input: n = 3, logs = [[20,1,2],[5,0,1]]
Output: 20
Sorted by time, the union at time 20 connects the final pair.
Constraints
2 <= n <= 1001 <= logs.length <= 10^4logs[i].length == 30 <= timestamp <= 10^9- All timestamps are distinct
0 <= a, b < nanda != b
Problem 15: Redundant Connection (Last Removable Edge)
LeetCode: 684. Redundant Connection
Description
A network of n planets labelled 1 to n began as a tree, then exactly one extra
lane was added, creating a single cycle. Given edges in insertion order, return the
one edge whose removal restores a tree. If several edges lie on the cycle, return the
one appearing last in the input.
Union endpoints in order; the edge whose two endpoints are already connected when you reach it is the one to remove.
Examples
Example 1:
Input: edges = [[1,2],[2,3],[3,1]]
Output: [3,1]
The edge [3,1] closes the cycle 1-2-3-1.
Example 2:
Input: edges = [[1,2],[1,3],[2,3],[3,4]]
Output: [2,3]
[2,3] is the edge that finds its endpoints already connected.
Example 3:
Input: edges = [[1,4],[2,4],[3,4],[1,2]]
Output: [1,2]
[1,2] is the last edge to close the cycle through node 4.
Constraints
3 <= n <= 1000edges.length == nedges[i].length == 21 <= ai < bi <= nis not guaranteed; treat edges as unordered pairs.
Problem 16: Smallest String With Swaps
LeetCode: 1202. Smallest String With Swaps
Description
You are given a string s and an array pairs of index pairs, where each
pairs[i] = [a, b] means you may swap the characters at indices a and b any number
of times. Return the lexicographically smallest string obtainable through such swaps.
Indices connected by swaps form a group; within a group, characters can be freely rearranged. Union the index pairs, then for each group sort its characters and place the smallest ones at the smallest indices.
Examples
Example 1:
Input: s = "dcab", pairs = [[0,3],[1,2]]
Output: "bacd"
Group {0,3} holds {d,b} and group {1,2} holds {c,a}; sorting each gives bacd.
Example 2:
Input: s = "dcab", pairs = [[0,3],[1,2],[0,2]]
Output: "abcd"
All four indices merge into one group, so the whole string can be sorted.
Example 3:
Input: s = "cba", pairs = [[0,1],[1,2]]
Output: "abc"
Indices {0,1,2} form one group; the characters sort to abc.
Constraints
1 <= s.length <= 10^50 <= pairs.length <= 10^50 <= a, b < s.lengthsconsists of lowercase English letters.
Problem 17: Kruskal’s Minimum Spanning Tree Weight
Description
You are given n nodes labelled 0 to n - 1 and a list of weighted undirected
edges, where each edges[i] = [u, v, w]. Return the total weight of a minimum
spanning tree connecting all nodes, or -1 if the graph is not connected.
Sort edges by ascending weight (Kruskal’s algorithm). For each edge, union its
endpoints if they lie in different components and add its weight; skip cycle-forming
edges. The MST exists only if you accept exactly n - 1 edges.
Examples
Example 1:
Input: n = 4, edges = [[0,1,1],[1,2,2],[2,3,3],[0,3,4]]
Output: 6
Accepting weights 1 + 2 + 3 spans all four nodes; the weight-4 edge would form a cycle.
Example 2:
Input: n = 3, edges = [[0,1,5]]
Output: -1
Node 2 is unreachable, so no spanning tree exists.
Example 3:
Input: n = 1, edges = []
Output: 0
A single node needs no edges; the MST weight is zero.
Constraints
1 <= n <= 10^50 <= edges.length <= 2 * 10^5edges[i].length == 30 <= u, v < n1 <= w <= 10^6
Problem 18: Bridge Building Budget
Description
There are n islands labelled 0 to n - 1 and a list of candidate bridges, each
[u, v, cost]. Bridges are considered in ascending order of cost; a bridge is built
only if its endpoints are not already connected. Return the total cost to connect every
island, or -1 if it is impossible to connect all of them.
This is Kruskal’s algorithm framed as a budget: greedily accept the cheapest component-joining bridges until everything is connected.
Examples
Example 1:
Input: n = 3, bridges = [[0,1,1],[1,2,2],[0,2,5]]
Output: 3
Building the cost-1 and cost-2 bridges connects all islands; the cost-5 bridge is skipped.
Example 2:
Input: n = 4, bridges = [[0,1,3],[2,3,4]]
Output: -1
The two pairs {0,1} and {2,3} can never be joined, so connection is impossible.
Example 3:
Input: n = 1, bridges = []
Output: 0
A single island is already connected; no bridges are needed.
Constraints
1 <= n <= 10^50 <= bridges.length <= 2 * 10^5bridges[i].length == 30 <= u, v < n1 <= cost <= 10^9
Problem 19: Galactic Senate Faction Queries
Description
The Galactic Senate has n senators labelled 0 to n - 1. You are given alliances,
where each [a, b] means a and b belong to the same faction (alliances are
transitive), and a list of queries, each [x, y]. For every query, report whether
x and y end up in the same faction. Return a boolean array, one entry per query.
Process all alliances into a union-find first, then answer each query by comparing representatives.
Examples
Example 1:
Input: n = 4, alliances = [[0,1],[1,2]], queries = [[0,2],[0,3]]
Output: [true,false]
0 and 2 share a faction through 1; 3 is alone.
Example 2:
Input: n = 3, alliances = [], queries = [[0,1],[2,2]]
Output: [false,true]
No alliances exist, so distinct senators differ; a senator always matches itself.
Example 3:
Input: n = 5, alliances = [[0,1],[2,3],[3,4]], queries = [[2,4],[1,3]]
Output: [true,false]
2 and 4 share a faction; 1 and 3 lie in different factions.
Constraints
1 <= n <= 2 * 10^50 <= alliances.length <= 2 * 10^51 <= queries.length <= 2 * 10^50 <= a, b, x, y < n
Problem 20: Advent Grid Flood
Description
You are handed a rows x cols grid of characters. Two orthogonally adjacent cells (up,
down, left, right) are joined when they hold the same character. A region is a maximal
group of joined cells. Return the size (cell count) of the single largest region.
Map each cell to an index, union equal adjacent cells (right and down neighbours suffice), then return the maximum component size.
Examples
Example 1:
Input: grid = ["AAB",
"ABB",
"BBB"]
Output: 5
The five 'B' cells form the largest region; the 'A' cells split into smaller ones.
Example 2:
Input: grid = ["AB",
"BA"]
Output: 1
No two equal cells are adjacent, so every region has size 1.
Example 3:
Input: grid = ["CCCC"]
Output: 4
All four 'C' cells join into one region.
Constraints
1 <= rows, cols <= 1000- The grid is non-empty and rectangular.
- Each cell holds a single uppercase English letter.
Graph Representations
What a Graph Is & What We Store
What are the raw pieces — vertices, edges, weights, direction — that any representation must encode?
The Core Question: What Operations Must Be Fast?
Before choosing a structure, which operations dominate your algorithm — neighbor iteration, edge lookup, or edge insertion?
Adjacency Matrix
When is a V x V grid the right call, and what does each cell encode?
Cell Semantics
What does M[u][v] hold for an unweighted graph vs. a weighted one, and what marks “no edge”?
Symmetry for Undirected Graphs
Why is the matrix symmetric when edges have no direction, and what does that duplication cost?
Adjacency List
How does a list-of-lists trade space for traversal speed, and what’s its footprint?
Choosing the Inner Container
Array, linked list, or hash set per vertex — what does each buy you for lookup vs. iteration?
Iterating Neighbors
How does walking one vertex’s list give you exactly its degree’s worth of work?
Edge List
What problems want edges as a flat collection rather than per-vertex grouping?
Directed vs. Undirected
How do you store an undirected edge so both endpoints see it, and what changes when direction matters?
Encoding Weights & Multi-Edges
Where do weights live in each representation, and how do you handle parallel edges or self-loops?
Core Operations Compared
For “is there an edge u-v?”, “list neighbors of u”, and “add an edge” — which representation wins each?
Sparse vs. Dense Trade-offs
Sketch when |E| ~ |V| vs. |E| ~ |V|^2 should steer your choice of structure.
Mapping Vertices to Indices
How do you turn arbitrary labels (strings, objects) into the integer ids your structure expects?
Time Complexity
State the cost of each core operation per representation, and reason about why.
Edge Lookup vs. Neighbor Scan
Why is M[u][v] an O(1) check but listing neighbors O(V) in a matrix, while a list flips both costs?
Building the Structure
How long does ingesting all E edges take in each representation, and what dominates?
Iterating the Whole Graph
Why does scanning every adjacency list total O(V + E), while scanning the matrix is always O(V^2)?
Space Complexity
Compare the memory footprints and reason about when each is wasteful.
Matrix O(V^2) vs. List O(V + E)
Why does the matrix pay for absent edges while the list pays only for present ones?
The Cost of Per-Vertex Containers
What fixed overhead does each adjacency list’s container header add on top of the raw edge data?
Pitfalls & Edge Cases
Where do self-loops, duplicate edges, and out-of-range vertex ids quietly break your code?
Implementation Walkthrough
Plan the structure before you write it — what does each piece need to do?
Storing the Vertex Set
How will you size and index your vertices, and what id range do you assume?
The Edge-Adding Routine
What does addEdge(u, v) touch, and how does it differ for directed vs. undirected, weighted vs. not?
Neighbor Iteration API
What method exposes a vertex’s neighbors, and what shape does it return for clean traversal code downstream?
Guarding Against Bad Input
Where do you validate vertex ids and decide how to treat duplicate or self edges?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Breadth-First Search
What It Is & When to Use It
What problem shape — shortest hops, level order, nearest target — makes BFS the right tool?
Representation It Needs
What does BFS expect from your graph, and how do you iterate a vertex’s neighbors cheaply?
The Key Idea: Expanding Outward in Rings
Why does processing the closest vertices first guarantee you reach everything in nondecreasing distance order?
How the Traversal Works
Walk one expansion: dequeue a vertex, scan its edges, enqueue the new ones — what happens each step?
Queue-Based Frontier
Why a FIFO queue, and what does the frontier represent at any moment?
Why FIFO and Not LIFO
What would swapping the queue for a stack do to the order vertices come out, and why does that break BFS?
Visited Set & Marking Timing
Why mark a vertex visited when you enqueue it, not when you dequeue it?
The Double-Enqueue Bug
What goes wrong if two neighbors both push the same unvisited vertex before either is processed?
Layered Levels & Distances
How do vertices partition into distance layers from the source, and how do you record each level?
Tracking the Current Level Size
How does snapshotting the queue size at the start of a level let you process one ring at a time?
Shortest Paths in Unweighted Graphs
Why does first-discovery give the minimum hop count, and where does this break with weights?
Parent Tracking & Path Reconstruction
How does a parent array let you rebuild the actual path after the search ends?
Multi-Source & Variant Forms
How does seeding the queue with many starts, or running 0-1 BFS, extend the basic algorithm?
Time Complexity
Reason about why BFS is O(V + E), not by memorizing it.
Why Each Vertex Is Processed Once
What guarantees a vertex is dequeued exactly once, contributing O(V) across the whole run?
Why Each Edge Is Scanned a Bounded Number of Times
How many times is an edge examined across the traversal, and how does that total to O(E)?
How Representation Changes the Bound
Why does an adjacency list give O(V + E) while a matrix forces O(V^2) regardless of edge count?
Space Complexity
Account for every structure BFS keeps in memory.
Visited Set & Distance/Parent Arrays
Why do these auxiliary arrays each cost O(V)?
Peak Queue Size
In the worst case, how large can the frontier grow, and what graph shape causes it?
Trade-offs vs. DFS
When does BFS’s level-by-level shape beat DFS, and what does its frontier cost you in memory?
Common Pitfalls
Disconnected graphs, re-enqueuing duplicates, marking too late — where do these bite?
Implementation Walkthrough
Break the algorithm into parts before coding — what does each part own?
Setting Up the Graph & Auxiliary Arrays
What do you initialize for visited, distance, and parent before the loop starts?
Seeding & Draining the Queue
How do you push the source(s) and structure the main dequeue loop?
Per-Neighbor Work
Inside the loop, what do you check and update for each neighbor before enqueuing it?
Reconstructing a Path from Parents
After the search, how do you walk the parent array back from a target to the source?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Depth-First Search
What It Is & When to Use It
What problems — connectivity, ordering, cycle detection — does going deep first naturally solve?
The Key Idea: Go Deep, Then Backtrack
Why does committing fully to one branch before exploring siblings reveal structure a level-order sweep hides?
Recursive vs. Explicit-Stack
How does the call stack mirror an explicit stack, and when must you switch to the iterative form?
What the Call Stack Holds
At any moment mid-recursion, what does each frame on the stack represent about the active path?
Visited Tracking & States
Why do many DFS variants need three states (unvisited / in-progress / done) rather than a single visited flag?
Why “In-Progress” Matters
What does a vertex still on the active path tell you that a plain visited bit cannot?
How the Traversal Unfolds
Walk one branch to its dead end and back — what does “explore, recurse, backtrack” look like step by step?
Discovery & Finish Times
What do the two timestamps per vertex tell you about the structure of the search tree?
The Parenthesis Structure
Why do discovery/finish intervals nest like balanced parentheses, and what does nesting reveal about ancestry?
Edge Classification
Distinguish tree, back, forward, and cross edges — what signal reveals each one?
Using Timestamps to Classify
How do the relative discovery and finish times of two endpoints tell you which edge type you’re on?
Cycle Detection
Which edge type signals a cycle, and how does the answer differ for directed vs. undirected graphs?
Directed vs. Undirected
How do edge types and the cycle rule change when edges have direction?
Connectivity & Components
How does looping DFS over every unvisited vertex enumerate connected components?
Time Complexity
Reason about why DFS is O(V + E), not by memorizing it.
Each Vertex Visited Once
What guarantees the visited check fires the recursive call exactly once per vertex, totaling O(V)?
Each Edge Examined Once (or Twice)
Why does scanning adjacency lists total O(E), and why is an undirected edge seen from both ends?
How Representation Changes the Bound
Why does a matrix push DFS to O(V^2) while a list keeps it at O(V + E)?
Space Complexity
Account for every structure DFS keeps in memory.
The Call-Stack / Explicit-Stack Depth
In the worst case, how deep can recursion go, and what graph shape forces O(V) stack frames?
Visited Arrays & Timestamp Bookkeeping
Why do the visited, discovery, and finish arrays each cost O(V)?
Common Pitfalls
Stack overflow on deep graphs, forgetting disconnected vertices, mishandling the parent edge — where do these strike?
Implementation Walkthrough
Break the algorithm into parts before coding — what does each part own?
Choosing Recursive vs. Iterative
Which form will you write, and what changes in how you push/pop or recurse?
Marking and the Visited Check
Where exactly do you set a vertex visited, and where do you test it to avoid reprocessing?
Per-Edge Work & Recursion
Inside the neighbor loop, what do you do for an unvisited neighbor vs. an already-visited one?
Capturing Timestamps or Ordering
If you need discovery/finish times, where do you stamp them relative to the recursive calls?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Topological Sort
What It Is & When to Use It
What does a linear order respecting all “must come before” edges buy you — scheduling, build order, dependency resolution?
DAG Precondition
Why must the graph be acyclic, and what does a cycle mean for the ordering?
The Key Idea: Respect Every Dependency
Why does “every edge points forward in the final list” capture exactly what a valid order means?
Representation & Indegree Setup
What do you precompute (adjacency list, indegree counts) before either algorithm can run?
Kahn’s Algorithm (Indegree Peeling)
How does repeatedly removing zero-indegree nodes build a valid order?
The Zero-Indegree Queue
What data structure holds the “ready” nodes, and when does a node become ready?
Decrementing Indegrees as You Remove
When you output a node, which neighbors’ indegrees drop, and what makes one newly eligible?
DFS Finish-Time Ordering
Why does pushing each vertex on finish and reversing produce a valid topological order?
Why Reverse Finish Order Works
Why is a vertex always finished after every vertex it depends on, making the reversal valid?
Cycle Detection as a Byproduct
How does Kahn’s leftover count, or DFS’s back edge, surface a cycle when ordering fails?
Non-Uniqueness of Orderings
When does a DAG admit many valid orders, and what controls which one you get?
Lexicographically Smallest Order
How does swapping the queue for a priority queue in Kahn’s pick a deterministic order?
Real Uses
Where does this show up — task schedulers, spreadsheet recalc, course prerequisites, package managers?
Time Complexity
Reason about why both approaches are O(V + E).
Kahn’s: Each Node Enqueued Once, Each Edge Relaxed Once
Why does every vertex enter the queue exactly once and every edge trigger exactly one indegree decrement?
DFS Variant: Standard Traversal Cost
Why does the finish-time approach inherit DFS’s O(V + E) with only a stack push added per vertex?
Priority-Queue Variant Cost
What log V factor does the lexicographically-smallest version add, and where does it come from?
Space Complexity
Account for every structure these algorithms keep.
Indegree Array & the Queue
Why is the indegree array O(V), and how large can the ready-queue get?
Recursion Stack & Output List
Why do the DFS stack and the result list each cost O(V)?
Pitfalls & Edge Cases
Disconnected DAGs, self-loops, forgetting to seed all zero-indegree nodes — where do these trip you?
Implementation Walkthrough
Break the algorithm into parts before coding — what does each part own?
Computing Indegrees (Kahn) or Visited States (DFS)
What do you precompute in a first pass before the main loop begins?
The Main Removal / Recursion Loop
How is the core loop structured, and what does it emit on each step?
Detecting the Cycle Case
Where in the code do you notice that no valid order exists, and how do you report it?
Producing the Final Order
How do you assemble and (for the DFS variant) reverse the output sequence?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Strongly Connected Components
What They Are & When to Use Them
What does an SCC capture, and which problems — deadlock detection, 2-SAT, condensing cyclic dependencies — need them?
Mutual Reachability
What does it mean for two vertices to reach each other, and why does that make an equivalence class?
The Key Idea: Cycles Collapse Together
Why do all vertices on a common cycle belong to one SCC, and how does that shape the partition?
Representation & the Transpose Graph
What is the reversed graph, and how do you build it from the original adjacency list?
Kosaraju Two-Pass
Why does a DFS on the graph, then a DFS on its transpose in finish-time order, isolate each component?
Finish-Time Stack
How does the first pass’s finish order tell the second pass which component to peel first?
Why the Transpose Traps Each Component
Why does a second DFS on reversed edges, started from the latest-finishing vertex, stay inside one SCC?
Tarjan Lowlink
How does a single DFS with discovery and low-link values pop components off a stack?
Discovery & Low-Link Values
What do disc[v] and low[v] measure, and when does low[v] == disc[v] mark a component root?
On-Stack Tracking
Why must you check whether a neighbor is still on the stack before updating low-link?
Popping a Component
When a root is found, how do you pop exactly its members off the stack and no others?
Condensation DAG
How does collapsing each SCC to a single node yield an acyclic meta-graph, and why is that useful?
Kosaraju vs. Tarjan Trade-offs
Two passes and a transpose vs. one pass with bookkeeping — when do you reach for each?
Real Uses
Where do SCCs appear — 2-SAT solving, web link analysis, cycle collapsing in dependency graphs?
Time Complexity
Reason about why both algorithms stay linear despite multiple traversals.
Kosaraju’s Two DFS Passes Plus Transpose Build
Why do two full O(V + E) traversals plus building the reversed graph still total O(V + E)?
Tarjan’s Single Pass
Why does one DFS with constant extra work per vertex and edge keep Tarjan at O(V + E)?
Space Complexity
Account for every structure these algorithms keep.
Kosaraju: Storing the Transpose + Finish Stack
Why does holding a second copy of the edges and a finish-order stack cost O(V + E)?
Tarjan: disc / low / on-stack Arrays + the Stack
Why do the per-vertex arrays and the explicit vertex stack each cost O(V)?
Pitfalls & Edge Cases
Single-vertex components, self-loops, recursion depth, mixing up the transpose direction — where do these bite?
Implementation Walkthrough
Break the algorithm into parts before coding — what does each part own?
Building Forward and (for Kosaraju) Reverse Adjacency
What two structures do you populate, and how do you reverse each edge?
The First DFS: Ordering by Finish (or Assigning disc/low)
What does the first traversal compute and store for the second phase or the root test?
The Second Phase: Collecting Components
How do you walk vertices in the right order and group each traversal’s reach into one SCC?
Labeling Vertices with Component Ids
How do you record which SCC each vertex landed in for downstream use?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Articulation Points & Bridges
What They Are & Why They Matter
What does a cut vertex or cut edge represent, and which problems — network reliability, single points of failure — care?
The Key Idea: Can a Subtree Escape Without Its Parent?
Why does the whole question reduce to whether a child’s subtree has any back-edge bypassing the parent?
The DFS Tree & Back Edges
How does running DFS turn the graph into a tree of tree-edges plus back-edges, and why does that structure matter?
Why Undirected DFS Has No Cross Edges
Why can every non-tree edge in an undirected DFS only point back to an ancestor, never sideways?
Discovery & Low Values
What do disc[v] and low[v] measure, and how is low[v] updated from children and back-edges?
Computing low[v] Step by Step
From a vertex’s own discovery time, its children’s low values, and its back-edges, how do you fold these into low[v]?
Articulation-Point Condition
For a non-root vertex, when does a child’s low-value prove that removing the vertex disconnects the graph?
Bridge Condition
What strict inequality on a child’s low-value marks the connecting tree edge as a bridge?
Why the Inequality Is Strict for Bridges but Not Cut Vertices
Why does low[child] > disc[v] mean a bridge while low[child] >= disc[v] means a cut vertex?
Root Special Case
Why does the DFS root need its own rule based on how many DFS children it has?
Biconnected Components
How do articulation points partition the graph into biconnected pieces, and how does an edge stack collect them?
Single-Pass Computation
How do you find all articulation points and bridges in one DFS instead of testing each vertex by removal?
Why Removal-Testing Is Wasteful
What is the cost of deleting each vertex and rerunning connectivity, and how does low-link avoid it?
Directed vs. Undirected Scope
Why are these defined for undirected graphs, and what’s the directed analog?
Time Complexity
Reason about why the DFS-based approach is O(V + E).
One DFS, Constant Work per Edge
Why does computing disc/low during a single traversal add only constant work per edge, keeping it linear?
Contrast with Naive Removal Testing
Why does the remove-and-recheck approach blow up to O(V * (V + E)), and why is that so much worse?
Space Complexity
Account for every structure the algorithm keeps.
disc / low / visited Arrays
Why do these per-vertex arrays each cost O(V)?
Recursion Stack & Optional Edge Stack
Why does the DFS recursion cost O(V), and what does the biconnected-component edge stack add?
Pitfalls & Edge Cases
Parallel edges, the parent-edge exception, disconnected graphs, the root rule — where do these trip you?
Implementation Walkthrough
Break the algorithm into parts before coding — what does each part own?
Initializing Timers and Arrays
What counters and per-vertex arrays do you set up before the DFS?
The Recursive DFS with low Updates
Inside the recursion, in what order do you stamp disc, recurse into children, and fold results into low?
Distinguishing the Parent Edge from a Real Back-Edge
How do you avoid mistaking the edge back to your DFS parent for a genuine back-edge?
Emitting Cut Vertices, Bridges, or Components
Where in the recursion do you apply each condition and record the result?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Euler Paths & Circuits
What They Are & When to Use Them
What does traversing every edge exactly once solve — route planning, DNA fragment assembly, drawing without lifting the pen?
Path vs. Circuit
What distinguishes an Euler path (open) from an Euler circuit (closed), and when does each exist?
The Key Idea: Every Entry Needs an Exit
Why does “each visit to a vertex uses two edges” lead directly to the even-degree requirement?
Degree Conditions (Undirected)
What parity rules on vertex degrees decide existence — zero or two odd-degree vertices?
Why Exactly Zero or Two Odd Vertices
Why can’t a graph with one, three, or four odd-degree vertices have an Euler path?
Connectivity Requirement
Why must all edges live in one connected component, and how do you ignore isolated vertices?
Directed Graph Variant
How do in-degree vs. out-degree balances replace the parity test for digraphs?
Hierholzer’s Algorithm
How does following edges until stuck, then splicing in detours, stitch cycles into one full traversal?
Edge-Tracking & the Stack
What data structures mark edges used and hold the partial route, and how do you avoid reusing an edge?
Splicing Sub-Cycles Together
When you return to a vertex with unused edges, how does the stack let you weave that detour into the route?
Picking a Valid Start Vertex
Given the degree conditions, which vertex must you begin from for a path vs. a circuit?
Euler vs. Hamilton Contrast
Why is edge-traversal solvable in linear time while vertex-traversal is intractable?
Real Uses
Where does this appear — genome assembly with de Bruijn graphs, street-sweeping routes, circuit board drilling?
Time Complexity
Reason about why Hierholzer’s runs in O(E).
Each Edge Traversed Once
Why does marking an edge used and never revisiting it bound the work at O(E)?
The Cost of the Degree/Connectivity Pre-Check
Why does verifying the existence conditions up front add only O(V + E)?
Space Complexity
Account for every structure the algorithm keeps.
Used-Edge Marks & the Route Stack
Why does tracking which edges are spent, plus the current path stack, cost O(E)?
Pointers into Each Adjacency List
Why does keeping a “next unused edge” cursor per vertex cost O(V) and avoid rescanning?
Pitfalls & Edge Cases
Disconnected edge sets, multigraphs, self-loops, choosing the wrong start, re-walking an edge — where do these bite?
Implementation Walkthrough
Break the algorithm into parts before coding — what does each part own?
Verifying Existence Conditions First
What degree and connectivity checks gate the algorithm, and what do you return if they fail?
Tracking Unused Edges Efficiently
How do you mark an edge consumed and advance a per-vertex cursor so you never re-scan spent edges?
The Stack-Driven Traversal Loop
How does the main loop push when it can advance and pop-to-output when it gets stuck?
Emitting the Route in Correct Order
Why might the path come out reversed, and how do you produce the final ordering?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Hamiltonian Paths & Cycles
What They Are & When to Use Them
What does visiting every vertex exactly once model — TSP, sequencing, puzzle solving?
Visit-Every-Vertex-Once Constraint
What exactly distinguishes a Hamiltonian path from an Euler path, vertices vs. edges?
The Key Idea: Why No Local Rule Decides It
Why does the absence of a simple degree-style condition force you into search rather than a quick test?
Why It’s Hard (NP-Completeness)
Why is deciding existence believed to have no polynomial algorithm, and what does that mean practically?
Backtracking Search
How does a depth-first search over candidate orderings extend, test, and undo a partial path?
The Recursion State
What does each level of the backtracking recursion carry — current vertex, visited set, path so far?
Pruning the Search
What checks — degree limits, dead-end detection, connectivity — cut branches early?
Bitmask DP (Held-Karp)
How does a state over (visited set, current vertex) beat naive permutation enumeration?
Encoding the Visited Set
How does an integer bitmask represent which vertices are used, and how do you transition states?
Filling the DP Table
In what order over subsets do you compute states so each depends only on already-solved ones?
Sufficient Conditions (Ore / Dirac)
Which degree thresholds guarantee a Hamiltonian cycle exists without searching for one?
Path vs. Cycle Variants
How does requiring a return to the start change the search and the existence conditions?
Trade-offs & When to Pick Each
Tiny n with Held-Karp, moderate n with pruned backtracking, heuristics for large n — how do you choose?
Time Complexity
Reason about why every exact method is exponential.
Brute Force Over Permutations
Why does trying every ordering cost O(n! \cdot n), and how fast does that explode?
Backtracking’s Pruned but Still Exponential Cost
Why does pruning help in practice yet leave the worst case exponential?
Held-Karp’s O(2^n \cdot n^2)
Why does the bitmask DP’s 2^n subsets times n^2 transitions beat n! while staying exponential?
Space Complexity
Account for every structure each method keeps.
Backtracking: Path + Visited + Recursion Stack
Why is backtracking’s memory only O(n) despite its exponential time?
Held-Karp’s O(2^n \cdot n) Table
Why does storing a value for every (subset, endpoint) pair dominate the DP’s memory?
Real Uses & Pitfalls
Where it appears (routing, scheduling) and what trips you — exponential blowup, forgetting the cycle-close edge, weak pruning.
Implementation Walkthrough
Break the algorithm into parts before coding — what does each part own?
Representing the Partial Path & Visited Set
What structures track which vertices are used and the order chosen so far?
The Extend / Recurse / Undo Cycle
How does each step try a next vertex, recurse, and then roll back its choice on failure?
Pruning Hooks
Where in the recursion do you insert early-exit checks to cut dead branches?
The DP Alternative: Iterating Subsets and Endpoints
If you build Held-Karp instead, how do you loop over masks and current vertices to fill the table?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Bipartite Graphs & Matching
What They Are & When to Use Them
What does splitting vertices into two sides model — job assignment, scheduling, resource allocation?
Two-Coloring Test
How does a BFS/DFS that colors neighbors oppositely decide if a graph is bipartite?
Propagating Colors Along Edges
As you traverse, why must each neighbor take the opposite color, and what does a conflict mean?
Odd Cycle Characterization
Why does any odd-length cycle make two-coloring impossible?
What a Matching Is
What makes a set of edges a matching, and what distinguishes maximal from maximum?
The Key Idea: Grow the Matching via Alternating Paths
Why does flipping the edges along an alternating path that starts and ends free increase the matching by exactly one?
Augmenting Paths
What makes an alternating path “augmenting,” and what does flipping its edges do to the matching size?
Why No Augmenting Path Means Maximum
Why does the absence of any augmenting path certify that the current matching is already maximum?
Hungarian-Style Augmentation (Kuhn’s)
How does repeatedly searching for an augmenting path from each free vertex grow the matching to maximum?
The Try-Each-Left-Vertex Loop
How does attempting to match every left vertex in turn, reusing a DFS, build up the maximum?
Maximum Matching & Kőnig’s Theorem
How does maximum matching size relate to minimum vertex cover in a bipartite graph?
Hopcroft–Karp Speedup
How does finding many shortest augmenting paths per phase improve over one-at-a-time augmentation?
BFS Layering + DFS Augmenting
How does a BFS to build layers, followed by DFS to find disjoint shortest augmenting paths, define one phase?
Weighted Matching
How does the problem change when edges carry costs, and what’s the goal then?
Real Uses
Where does bipartite matching appear — assigning workers to tasks, students to schools, ad slots to bidders?
Time Complexity
Reason about the cost of each method.
Two-Coloring: A Single Traversal
Why is the bipartite check just O(V + E), the cost of one BFS/DFS?
Kuhn’s O(V \cdot E)
Why does running one augmenting-path search per left vertex, each scanning up to E edges, give this bound?
Hopcroft–Karp’s O(E \sqrt{V})
Why does batching shortest augmenting paths cut the number of phases to about \sqrt{V}?
Space Complexity
Account for every structure these algorithms keep.
Color / Match Arrays
Why do the coloring array and the two-sided match arrays each cost O(V)?
Visited Markers & Layer/Queue Storage
Why does the per-search visited set, plus Hopcroft–Karp’s layers and queue, cost O(V)?
Pitfalls & Edge Cases
Disconnected components, forgetting to color all parts, free vs. matched vertices, directed vs. undirected — where do these bite?
Implementation Walkthrough
Break the algorithm into parts before coding — what does each part own?
Building the Bipartition
How do you split vertices into the two sides, or run the coloring that produces them?
The Match Arrays
What do you store for each left and right vertex to record current pairings, and what marks “unmatched”?
The Augmenting-Path Search
Inside the per-vertex search, how do you try a neighbor, recurse to re-match its partner, and commit on success?
Counting and Reporting the Matching
How do you tally matched pairs and, if needed, output the actual assignment?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
DAG Shortest Paths
What It Is & When to Use It
Why does a DAG let you find shortest paths faster than Dijkstra or Bellman-Ford?
Topological Order Prerequisite
Why must vertices be processed in topological order, and how do you get that order first?
The Key Idea: Settle Vertices in Dependency Order
Why does processing vertices so all incoming edges are relaxed before you “leave” a vertex finalize its distance?
Edge Relaxation Recap
What does relaxing an edge mean, and when does it improve a stored distance?
Single-Pass Relaxation
How does one sweep over the topological order settle every vertex’s final distance?
Why One Pass Suffices
Why is a vertex’s distance final by the time you process it in topological order?
Relaxing Outgoing Edges
When you reach a vertex, which edges do you relax, and why are its own incoming relaxations already done?
Negative Weights Without Cycles
Why are negative edges safe here but dangerous for Dijkstra?
Longest Path & Critical Path
How does negating weights (or flipping the relaxation) turn this into a longest-path solver?
Critical Path Method
How does longest path in a task DAG give the minimum project completion time?
Path Reconstruction
How does a predecessor array let you recover the actual shortest path, not just its length?
Trade-offs vs. Dijkstra & Bellman-Ford
When does the DAG restriction pay off, and what do you lose if the graph has cycles?
Time Complexity
Reason about why this is O(V + E) and why it beats the alternatives.
Topological Sort + One Relaxation Sweep
Why does the O(V + E) topological sort plus a single linear relaxation pass total O(V + E)?
Why It Beats Dijkstra and Bellman-Ford
Why is there no log V heap factor and no V repeated passes here, unlike those two algorithms?
Space Complexity
Account for every structure the algorithm keeps.
Distance & Predecessor Arrays
Why do the dist and pred arrays each cost O(V)?
The Topological Order & Adjacency Storage
Why does holding the order plus the adjacency list cost O(V + E)?
Pitfalls & Edge Cases
Unreachable vertices left at infinity, a hidden cycle breaking the order, wrong relaxation direction — where do these trip you?
Implementation Walkthrough
Break the algorithm into parts before coding — what does each part own?
Producing the Topological Order
Which topological-sort method do you call first, and what does it hand back?
Initializing Distances
How do you set the source to zero and everything else to infinity before relaxing?
The Relaxation Sweep
As you walk the order, how do you relax each vertex’s outgoing edges and update predecessors?
Recovering Paths & Handling Unreachable Vertices
How do you follow predecessors to rebuild a path, and how do you report vertices still at infinity?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Graph Fundamentals: Problem Set
Every problem is an implementation task: fill in the stub in problemset/ and make
its test in tests/problemset/ pass. Inputs arrive as plain Java values — int[][]
edge lists, adjacency-style arrays, or char[][]/int[][] grids — so each problem is
self-contained and verified the same way. The Foundational group drills the core
traversals (BFS, DFS, components, cycles, ordering, coloring); the Applied Problems
group weaves LeetCode classics and contest-style tasks from easy to hard, all built on
those same primitives. A shared Graph helper lives in the module; use it by its simple
name wherever an adjacency structure is convenient.
Foundational
Problem 1: Vertex Degrees
Description
You are given an undirected graph with vertexCount vertices labelled 0..vertexCount-1
and a list of edges, where each edge is a pair [u, v]. Return an array deg where
deg[i] is the degree of vertex i — the number of edges incident to it. The graph has
no self-loops and no duplicate edges.
Examples
Example 1:
Input: vertexCount = 3, edges = [[0,1],[1,2]]
Output: [1, 2, 1]
Vertex 1 touches both edges, so its degree is 2; vertices 0 and 2 each touch one edge.
Example 2:
Input: vertexCount = 2, edges = []
Output: [0, 0]
With no edges every vertex has degree 0.
Example 3:
Input: vertexCount = 4, edges = [[0,1],[0,2],[0,3]]
Output: [3, 1, 1, 1]
Vertex 0 is the hub of a star and touches all three edges.
Constraints
0 <= vertexCount <= 10^50 <= edges.length <= 2 * 10^50 <= u, v < vertexCountandu != v- No duplicate edges.
Problem 2: Connected Components
Description
Given an undirected graph as a vertexCount and an int[][] edge list, return the number
of connected components — maximal sets of vertices reachable from one another. Isolated
vertices count as their own component.
Examples
Example 1:
Input: vertexCount = 5, edges = [[0,1],[1,2],[3,4]]
Output: 2
{0,1,2} form one component and {3,4} the other.
Example 2:
Input: vertexCount = 4, edges = []
Output: 4
With no edges each of the four vertices is its own component.
Example 3:
Input: vertexCount = 6, edges = [[0,1],[1,2],[2,0],[3,4]]
Output: 3
The triangle {0,1,2}, the pair {3,4}, and the lone vertex 5.
Constraints
1 <= vertexCount <= 10^50 <= edges.length <= 2 * 10^50 <= u, v < vertexCount
Problem 3: BFS Within K Levels
Description
Given an unweighted undirected graph (as vertexCount and an int[][] edge list), a
source vertex, and an integer k, return — sorted ascending — every vertex whose
shortest-path distance from source is at most k BFS levels. The source itself is at
distance 0 and is always included.
Examples
Example 1:
Input: vertexCount = 5, edges = [[0,1],[1,2],[2,3],[3,4]], source = 0, k = 2
Output: [0, 1, 2]
Distances from 0 are [0,1,2,3,4]; only those <= 2 qualify.
Example 2:
Input: vertexCount = 4, edges = [[0,1],[0,2],[0,3]], source = 0, k = 1
Output: [0, 1, 2, 3]
Every neighbor sits one level away from the hub.
Example 3:
Input: vertexCount = 3, edges = [], source = 1, k = 5
Output: [1]
With no edges only the source is reachable.
Constraints
1 <= vertexCount <= 10^50 <= source < vertexCount0 <= k <= vertexCount
Problem 4: Detect a Cycle (Undirected)
Description
Given an undirected graph as vertexCount and an int[][] edge list, return true if the
graph contains a cycle and false otherwise. The graph has no self-loops or duplicate
edges, so any cycle has length at least 3.
Examples
Example 1:
Input: vertexCount = 3, edges = [[0,1],[1,2],[2,0]]
Output: true
The three edges close a triangle.
Example 2:
Input: vertexCount = 4, edges = [[0,1],[1,2],[2,3]]
Output: false
A simple path has no cycle.
Example 3:
Input: vertexCount = 5, edges = [[0,1],[2,3],[3,4],[4,2]]
Output: true
The component {2,3,4} forms a cycle even though {0,1} does not.
Constraints
1 <= vertexCount <= 10^50 <= edges.length <= 2 * 10^5- No self-loops or duplicate edges.
Problem 5: Topological Order
Description
Given a directed graph as vertexCount and an int[][] edge list (each [u, v] means
u -> v), return a valid topological ordering of all vertices as an int[]. If the graph
contains a cycle no ordering exists, so return an empty array. Any valid order is accepted.
Examples
Example 1:
Input: vertexCount = 3, edges = [[0,1],[1,2]]
Output: [0, 1, 2]
Every edge points forward in the returned order.
Example 2:
Input: vertexCount = 2, edges = [[0,1],[1,0]]
Output: []
The two edges form a cycle, so no ordering exists.
Example 3:
Input: vertexCount = 4, edges = [[0,1],[0,2],[3,0]]
Output: [3, 0, 1, 2]
3 must precede 0, which must precede both 1 and 2.
Constraints
1 <= vertexCount <= 10^50 <= edges.length <= 2 * 10^5- A returned order, if non-empty, must list every vertex exactly once.
Problem 6: Bipartite Check With Classes
Description
Given an undirected graph as vertexCount and an int[][] edge list, decide whether it is
2-colorable. Return an int[] color of length vertexCount where each entry is 0 or
1 and no edge joins two same-colored vertices. If the graph is not bipartite, return an
empty array. For disconnected graphs, color each component independently; isolated vertices
may take either color (use 0).
Examples
Example 1:
Input: vertexCount = 4, edges = [[0,1],[1,2],[2,3],[3,0]]
Output: [0, 1, 0, 1]
The even cycle is 2-colorable.
Example 2:
Input: vertexCount = 3, edges = [[0,1],[1,2],[2,0]]
Output: []
An odd cycle cannot be 2-colored.
Example 3:
Input: vertexCount = 3, edges = [[0,1]]
Output: [0, 1, 0]
Vertex 2 is isolated and defaults to color 0.
Constraints
1 <= vertexCount <= 10^50 <= edges.length <= 2 * 10^5- A non-empty result must be a proper 2-coloring.
Applied Problems
Problem 7: Flood Fill
LeetCode: 733. Flood Fill
Description
You are given an int[][] image of pixel colors, a starting pixel (sr, sc), and a
color. Perform a flood fill: starting from (sr, sc), recolor that pixel and every pixel
4-directionally connected to it that shares its original color, then return the modified
image. If the start pixel already has the target color, the image is unchanged.
Examples
Example 1:
Input: image = [[1,1,1],[1,1,0],[1,0,1]], sr = 1, sc = 1, color = 2
Output: [[2,2,2],[2,2,0],[2,0,1]]
All 1s connected to the center become 2; the bottom-right 1 is cut off by water.
Example 2:
Input: image = [[0,0,0],[0,0,0]], sr = 0, sc = 0, color = 5
Output: [[5,5,5],[5,5,5]]
The whole image shares one color and is repainted.
Example 3:
Input: image = [[0,0,0],[0,1,0]], sr = 1, sc = 1, color = 1
Output: [[0,0,0],[0,1,0]]
The start already equals the new color, so nothing changes.
Constraints
1 <= image.length, image[0].length <= 500 <= image[i][j], color < 2^160 <= sr < image.length,0 <= sc < image[0].length
Problem 8: Number of Islands
LeetCode: 200. Number of Islands
Description
Given a char[][] grid of '1' (land) and '0' (water), count the number of islands. An
island is a maximal group of land cells connected horizontally or vertically. Cells outside
the grid are treated as water.
Examples
Example 1:
Input: grid = [['1','1','0'],['1','1','0'],['0','0','0']]
Output: 1
The four land cells form a single island.
Example 2:
Input: grid = [['1','0','1'],['0','0','0'],['1','0','0']]
Output: 3
Three land cells, none adjacent, give three islands.
Example 3:
Input: grid = [['0','0'],['0','0']]
Output: 0
No land means no islands.
Constraints
1 <= grid.length, grid[0].length <= 300- Each cell is
'0'or'1'.
Problem 9: Max Area of Island
LeetCode: 695. Max Area of Island
Description
Given an int[][] grid of 0s and 1s, return the area of the largest island. An island
is a maximal group of 1s connected 4-directionally; its area is the number of cells. If
there is no land, return 0.
Examples
Example 1:
Input: grid = [[1,1,0,0],[1,0,0,1],[0,0,1,1]]
Output: 3
The bottom-right island has three cells; the top-left island has three as well — the max is 3.
Example 2:
Input: grid = [[0,0,0],[0,0,0]]
Output: 0
No land at all.
Example 3:
Input: grid = [[1,1,1],[1,1,1]]
Output: 6
The whole grid is one island of six cells.
Constraints
1 <= grid.length, grid[0].length <= 50- Each cell is
0or1.
Problem 10: Largest Treasure Region
Description
A treasure map is an int[][] grid of 1 (treasure) and 0 (sea). A region is a maximal
group of treasure cells connected 4-directionally, and its size is the number of cells.
Return the size of the largest region, or 0 if there is no treasure.
Examples
Example 1:
Input: grid = [[1,0,1],[1,0,0],[0,0,1]]
Output: 2
The left column pair is the largest region.
Example 2:
Input: grid = [[0,0],[0,0]]
Output: 0
No treasure on the map.
Example 3:
Input: grid = [[1,1,1],[0,0,1],[1,1,1]]
Output: 7
All treasure cells but the isolated bottom-left pair connect into one region of seven.
Constraints
1 <= grid.length, grid[0].length <= 500- Each cell is
0or1.
Problem 11: Number of Provinces
LeetCode: 547. Number of Provinces
Description
There are n cities. You are given an n x n int[][] isConnected where
isConnected[i][j] = 1 means cities i and j are directly connected (the matrix is
symmetric with 1s on the diagonal). A province is a group of directly or indirectly
connected cities. Return the number of provinces.
Examples
Example 1:
Input: isConnected = [[1,1,0],[1,1,0],[0,0,1]]
Output: 2
Cities 0 and 1 form one province; city 2 stands alone.
Example 2:
Input: isConnected = [[1,0,0],[0,1,0],[0,0,1]]
Output: 3
No connections beyond self, so three provinces.
Example 3:
Input: isConnected = [[1,1,0],[1,1,1],[0,1,1]]
Output: 1
The connections chain all three cities into one province.
Constraints
1 <= n <= 200isConnected[i][j]is0or1,isConnected[i][i] = 1, and the matrix is symmetric.
Problem 12: Find Center of Star Graph
LeetCode: 1791. Find Center of Star Graph
Description
A star graph on n vertices (n >= 3) has one center connected to every other vertex and
no other edges. Given the int[][] edges list (exactly n - 1 edges), return the center
vertex.
Examples
Example 1:
Input: edges = [[1,2],[2,3],[4,2]]
Output: 2
Vertex 2 appears in every edge.
Example 2:
Input: edges = [[1,2],[5,1],[1,3],[1,4]]
Output: 1
Vertex 1 is the shared endpoint.
Example 3:
Input: edges = [[3,1],[3,2]]
Output: 3
The center is the vertex common to the first two edges.
Constraints
3 <= n <= 10^5andedges.length == n - 1- The input is guaranteed to form a valid star graph.
Problem 13: Find if Path Exists in Graph
LeetCode: 1971. Find if Path Exists in Graph
Description
Given an undirected graph with vertexCount vertices, an int[][] edge list, a source,
and a destination, return true if there is a path from source to destination and
false otherwise. A vertex always has a path to itself.
Examples
Example 1:
Input: vertexCount = 3, edges = [[0,1],[1,2],[2,0]], source = 0, destination = 2
Output: true
The triangle connects 0 and 2.
Example 2:
Input: vertexCount = 6, edges = [[0,1],[0,2],[3,5],[5,4],[4,3]], source = 0, destination = 5
Output: false
{0,1,2} and {3,4,5} are separate components.
Example 3:
Input: vertexCount = 1, edges = [], source = 0, destination = 0
Output: true
A vertex reaches itself trivially.
Constraints
1 <= vertexCount <= 2 * 10^50 <= edges.length <= 2 * 10^50 <= source, destination < vertexCount
Problem 14: Is Graph Bipartite?
LeetCode: 785. Is Graph Bipartite?
Description
Given an undirected graph as an adjacency list int[][] graph, where graph[u] lists the
neighbors of vertex u, return true if the graph is bipartite — its vertices can be split
into two sets with every edge crossing between them — and false otherwise. The graph may
be disconnected.
Examples
Example 1:
Input: graph = [[1,3],[0,2],[1,3],[0,2]]
Output: true
Sets {0,2} and {1,3} split the 4-cycle.
Example 2:
Input: graph = [[1,2,3],[0,2],[0,1,3],[0,2]]
Output: false
The triangle {0,1,2} is an odd cycle.
Example 3:
Input: graph = [[],[3],[],[1]]
Output: true
A lone edge {1,3} plus isolated vertices is bipartite.
Constraints
1 <= graph.length <= 100graph[u]contains distinct neighbors and does not containu.- If
vis ingraph[u], thenuis ingraph[v].
Problem 15: Two-Team Rivalry Split
Description
A tournament has playerCount players labelled 0..playerCount-1 and a list of undirected
[u, v] rivalry edges. Decide whether the players can be split into exactly two teams so
that every rivalry crosses between the teams. Return true or false. The rivalry graph may
be disconnected.
Examples
Example 1:
Input: playerCount = 4, edges = [[0,1],[1,2],[2,3],[3,0]]
Output: true
Teams {0,2} and {1,3} separate every rivalry.
Example 2:
Input: playerCount = 3, edges = [[0,1],[1,2],[2,0]]
Output: false
Three mutual rivals cannot be two-team split.
Example 3:
Input: playerCount = 5, edges = [[0,1],[2,3]]
Output: true
Disconnected rivalries are each separable.
Constraints
1 <= playerCount <= 10^50 <= edges.length <= 10^50 <= u, v < playerCount
Problem 16: Course Schedule
LeetCode: 207. Course Schedule
Description
There are numCourses courses labelled 0..numCourses-1. You are given prerequisites
where [a, b] means you must take course b before course a. Return true if you can
finish all courses — i.e. the prerequisite graph is acyclic — and false otherwise.
Examples
Example 1:
Input: numCourses = 2, prerequisites = [[1,0]]
Output: true
Take 0, then 1.
Example 2:
Input: numCourses = 2, prerequisites = [[1,0],[0,1]]
Output: false
Each course requires the other — a cycle.
Example 3:
Input: numCourses = 3, prerequisites = [[1,0],[2,1]]
Output: true
The chain 0 -> 1 -> 2 is acyclic.
Constraints
1 <= numCourses <= 10^50 <= prerequisites.length <= 5 * 10^50 <= a, b < numCourses
Problem 17: Build Deadlock
Description
A continuous-integration system has taskCount build tasks labelled 0..taskCount-1 and a
list of [a, b] dependencies meaning task a must finish before task b. Return true if
the dependency graph contains a circular dependency (a deadlock), else false.
Examples
Example 1:
Input: taskCount = 3, deps = [[0,1],[1,2],[2,0]]
Output: true
The three tasks wait on each other in a cycle.
Example 2:
Input: taskCount = 4, deps = [[0,1],[0,2],[1,3],[2,3]]
Output: false
A diamond of dependencies is still acyclic.
Example 3:
Input: taskCount = 2, deps = []
Output: false
No dependencies, no deadlock.
Constraints
1 <= taskCount <= 10^50 <= deps.length <= 2 * 10^50 <= a, b < taskCount
Problem 18: Course Schedule II
LeetCode: 210. Course Schedule II
Description
There are numCourses courses. Given prerequisites where [a, b] means b must be taken
before a, return any valid ordering of all courses as an int[]. If a cycle makes this
impossible, return an empty array.
Examples
Example 1:
Input: numCourses = 2, prerequisites = [[1,0]]
Output: [0, 1]
Course 0 unlocks course 1.
Example 2:
Input: numCourses = 4, prerequisites = [[1,0],[2,0],[3,1],[3,2]]
Output: [0, 1, 2, 3]
0 first, then 1 and 2, finally 3 (other valid orders accepted).
Example 3:
Input: numCourses = 2, prerequisites = [[0,1],[1,0]]
Output: []
A cyclic prerequisite leaves no ordering.
Constraints
1 <= numCourses <= 10^50 <= prerequisites.length <= 5 * 10^5- A non-empty result lists every course exactly once.
Problem 19: Pipeline Schedule
Description
Given stepCount pipeline steps and [a, b] dependency pairs (a must run before b),
return any valid execution order of all steps as an int[], or an empty array if a cycle
makes the pipeline unschedulable. When several steps are simultaneously available, prefer the
smallest step id, so a fully-chained input yields the natural ascending order.
Examples
Example 1:
Input: stepCount = 3, deps = [[0,1],[1,2]]
Output: [0, 1, 2]
A single chain yields the natural order.
Example 2:
Input: stepCount = 4, deps = [[0,2],[1,2],[2,3]]
Output: [0, 1, 2, 3]
0 and 1 are both free first; the smaller id 0 leads.
Example 3:
Input: stepCount = 2, deps = [[0,1],[1,0]]
Output: []
A cycle makes the pipeline unschedulable.
Constraints
1 <= stepCount <= 10^50 <= deps.length <= 2 * 10^5- Ties are broken by smallest available step id.
Problem 20: Rotting Oranges
LeetCode: 994. Rotting Oranges
Description
You are given an int[][] grid where 0 is empty, 1 is a fresh orange, and 2 is a
rotten orange. Each minute, any fresh orange 4-directionally adjacent to a rotten one becomes
rotten. Return the minimum number of minutes until no fresh orange remains, or -1 if some
fresh orange can never rot. Use multi-source BFS.
Examples
Example 1:
Input: grid = [[2,1,1],[1,1,0],[0,1,1]]
Output: 4
Rot spreads outward from the corner over four minutes.
Example 2:
Input: grid = [[2,1,1],[0,1,1],[1,0,1]]
Output: -1
The bottom-left orange is sealed off and never rots.
Example 3:
Input: grid = [[0,2]]
Output: 0
No fresh oranges, so zero minutes.
Constraints
1 <= grid.length, grid[0].length <= 100- Each cell is
0,1, or2.
Problem 21: 01 Matrix
LeetCode: 542. 01 Matrix
Description
Given an int[][] mat of 0s and 1s, return a matrix of the same size where each entry
is the distance to the nearest 0, measured in 4-directional steps. Cells holding 0 have
distance 0. Use multi-source BFS from all zeros at once.
Examples
Example 1:
Input: mat = [[0,0,0],[0,1,0],[0,0,0]]
Output: [[0,0,0],[0,1,0],[0,0,0]]
The single 1 is one step from a zero.
Example 2:
Input: mat = [[0,0,0],[0,1,0],[1,1,1]]
Output: [[0,0,0],[0,1,0],[1,2,1]]
The center bottom 1 is two steps from the nearest zero.
Example 3:
Input: mat = [[0,1],[1,1]]
Output: [[0,1],[1,2]]
The far corner is two steps away.
Constraints
1 <= mat.length, mat[0].length <= 10^4withmat.length * mat[0].length <= 10^4- Each cell is
0or1; at least one0is present.
Problem 22: Shortest Path in Binary Matrix
LeetCode: 1091. Shortest Path in Binary Matrix
Description
Given an n x n int[][] grid of 0s (clear) and 1s (blocked), return the length of
the shortest clear path from the top-left cell (0,0) to the bottom-right cell (n-1,n-1),
where moves may go in any of the 8 directions and path length counts visited cells. Return
-1 if no such path exists.
Examples
Example 1:
Input: grid = [[0,1],[1,0]]
Output: 2
Step diagonally from corner to corner.
Example 2:
Input: grid = [[0,0,0],[1,1,0],[1,1,0]]
Output: 4
A four-cell path threads down the open right column.
Example 3:
Input: grid = [[1,0,0],[1,1,0],[1,1,0]]
Output: -1
The start cell is blocked, so no path exists.
Constraints
1 <= n <= 100- Each cell is
0or1.
Problem 23: Maze Escape Hops
Description
A robot stands on the cell marked 'S' in a char[][] maze where '#' is a wall, '.'
is open floor, and 'E' is the single exit. Each move steps to a 4-adjacent non-wall cell.
Return the minimum number of moves from 'S' to 'E', or -1 if the exit is unreachable.
Examples
Example 1:
Input: maze = [['S','.','.'],['#','#','.'],['.','.','E']]
Output: 4
The only route hugs the right column then crosses the bottom row.
Example 2:
Input: maze = [['S','#','E']]
Output: -1
A wall separates start from exit.
Example 3:
Input: maze = [['S','.','E']]
Output: 2
Two open steps reach the exit.
Constraints
1 <= maze.length, maze[0].lengthandmaze.length * maze[0].length <= 10^6- Exactly one
'S'and one'E'; other cells are'.'or'#'.
Problem 24: Number of Closed Islands
LeetCode: 1254. Number of Closed Islands
Description
Given an int[][] grid where 0 is land and 1 is water, return the number of closed
islands. A closed island is a maximal group of land cells connected 4-directionally that does
not touch any border of the grid. Border-touching land does not count.
Examples
Example 1:
Input: grid = [[1,1,1,1],[1,0,0,1],[1,0,0,1],[1,1,1,1]]
Output: 1
The interior block of land is fully enclosed by water.
Example 2:
Input: grid = [[0,0,1,1],[0,1,0,1],[1,1,1,0]]
Output: 0
Every land cell reaches the border.
Example 3:
Input: grid = [[1,1,1,1,1],[1,0,0,0,1],[1,0,1,0,1],[1,0,0,0,1],[1,1,1,1,1]]
Output: 1
The ring of land around the central water cell is one closed island.
Constraints
1 <= grid.length, grid[0].length <= 100- Each cell is
0or1.
Problem 25: Surrounded Regions
LeetCode: 130. Surrounded Regions
Description
Given a char[][] board of 'X' and 'O', capture every region of 'O's that is fully
surrounded by 'X's by flipping those 'O's to 'X'. An 'O' region is safe if any of its
cells is connected 4-directionally to the border. Modify the board in place and return it.
Examples
Example 1:
Input: board = [['X','X','X'],['X','O','X'],['X','X','X']]
Output: [['X','X','X'],['X','X','X'],['X','X','X']]
The lone interior 'O' is captured.
Example 2:
Input: board = [['X','O','X'],['X','O','X']]
Output: [['X','O','X'],['X','O','X']]
The 'O's touch the top and bottom borders, so they survive.
Example 3:
Input: board = [['X','X','X','X'],['X','O','O','X'],['X','X','O','X'],['X','O','X','X']]
Output: [['X','X','X','X'],['X','X','X','X'],['X','X','X','X'],['X','O','X','X']]
The inner region is captured; the border-touching 'O' remains.
Constraints
1 <= board.length, board[0].length <= 200- Each cell is
'X'or'O'.
Problem 26: Pacific Atlantic Water Flow
LeetCode: 417. Pacific Atlantic Water Flow
Description
Given an int[][] heights grid, water can flow from a cell to a 4-adjacent cell of equal
or lower height. The Pacific Ocean borders the top and left edges; the Atlantic borders the
bottom and right edges. Return, as an int[][] list of [row, col] pairs sorted ascending
by row then column, every cell from which water can reach both oceans.
Examples
Example 1:
Input: heights = [[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5],[5,1,1,2,4]]
Output: [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]
These ridge cells drain to both coasts.
Example 2:
Input: heights = [[1]]
Output: [[0,0]]
The single cell touches every border.
Example 3:
Input: heights = [[2,1],[1,2]]
Output: [[0,0],[0,1],[1,0],[1,1]]
Every cell borders both oceans directly.
Constraints
1 <= heights.length, heights[0].length <= 2000 <= heights[i][j] <= 10^5
Problem 27: Clone Graph
LeetCode: 133. Clone Graph
Description
An undirected connected graph is given by vertexCount and an int[][] adjacency list
adj, where adj[u] lists the neighbors of vertex u. Produce a deep copy and return it as
a fresh adjacency list (a brand-new int[][], not the input array), with each neighbor list
sorted ascending. The clone must be structurally identical to the input.
Examples
Example 1:
Input: vertexCount = 4, adj = [[1,3],[0,2],[1,3],[0,2]]
Output: [[1,3],[0,2],[1,3],[0,2]]
A 4-cycle is copied edge for edge.
Example 2:
Input: vertexCount = 1, adj = [[]]
Output: [[]]
A single isolated vertex copies to itself.
Example 3:
Input: vertexCount = 2, adj = [[1],[0]]
Output: [[1],[0]]
A single edge is duplicated.
Constraints
1 <= vertexCount <= 100adj[u]contains distinct neighbors, none equal tou.- The returned array must not share references with the input.
Problem 28: Keys and Rooms
LeetCode: 841. Keys and Rooms
Description
There are n rooms labelled 0..n-1, all locked except room 0. You are given an int[][]
rooms, where rooms[i] lists the keys found in room i (each key opens the room with that
number). Starting in room 0, return true if you can visit every room and false
otherwise.
Examples
Example 1:
Input: rooms = [[1],[2],[3],[]]
Output: true
Each room hands you the key to the next.
Example 2:
Input: rooms = [[1,3],[3,0,1],[2],[0]]
Output: false
Room 2 is never unlocked.
Example 3:
Input: rooms = [[1,2],[],[]]
Output: true
Room 0 alone holds keys to both other rooms.
Constraints
2 <= n <= 10000 <= rooms[i].length <= 1000- Keys are valid room labels in
0..n-1.
Problem 29: Find Eventual Safe States
LeetCode: 802. Find Eventual Safe States
Description
Given a directed graph as an int[][] adjacency list graph (where graph[u] lists the
out-neighbors of u), a node is safe if every path starting from it eventually reaches a
terminal node (one with no outgoing edges) — that is, it cannot reach any cycle. Return all
safe nodes in ascending order.
Examples
Example 1:
Input: graph = [[1,2],[2,3],[5],[0],[5],[],[]]
Output: [2, 4, 5, 6]
Nodes 0, 1, 3 can reach the cycle 0 -> 1 -> 3 -> 0.
Example 2:
Input: graph = [[],[0,2,3,4],[3],[4],[]]
Output: [0, 1, 2, 3, 4]
The graph is acyclic, so every node is safe.
Example 3:
Input: graph = [[1],[2],[0]]
Output: []
Every node sits on the single 3-cycle.
Constraints
1 <= graph.length <= 10^4- The total number of edges is at most
4 * 10^4.
Problem 30: Minimum Height Trees
LeetCode: 310. Minimum Height Trees
Description
A tree on n vertices labelled 0..n-1 is given by an int[][] edge list of n - 1
undirected edges. Rooting the tree at a vertex produces a height; return all root labels that
minimize that height (the centroids), in ascending order. There are always either one or two
such roots. Peel leaves layer by layer until one or two vertices remain.
Examples
Example 1:
Input: n = 4, edges = [[1,0],[1,2],[1,3]]
Output: [1]
Rooting at the star center gives height 1.
Example 2:
Input: n = 6, edges = [[3,0],[3,1],[3,2],[3,4],[5,4]]
Output: [3, 4]
Two adjacent centroids minimize the height.
Example 3:
Input: n = 1, edges = []
Output: [0]
A single vertex is its own centroid.
Constraints
1 <= n <= 2 * 10^4edges.length == n - 1and the input forms a valid tree.
Dijkstra’s Shortest Paths
What It Solves & When
Single-source shortest paths on non-negative weights — what exact problem is this, and what graph property must hold before you reach for it?
The Shortest-Path Tree
The output is more than distances — it’s a tree rooted at the source. What does each tree edge mean, and why is there exactly one path to each vertex in it?
Greedy Relaxation Idea
Why can the closest unsettled vertex be finalized immediately? What invariant makes the greedy choice safe?
The Settled-Set Invariant
Once a vertex is “settled,” its distance never changes. Why does non-negativity guarantee no future path can do better?
The Relaxation Step
What does relaxing an edge (u,v) actually compute and compare, and what condition triggers an update to dist[v]?
The Frontier Grows Outward
Picture settled vertices as a wavefront expanding from the source. Why does Dijkstra always finalize vertices in non-decreasing distance order?
The Priority Queue
Which operations (push, pop-min, decrease-key) drive the loop, and why is a min-heap the natural fit for “pick the closest unsettled vertex”?
Lazy Deletion vs Decrease-Key
Two ways to handle a vertex whose distance improves — push a duplicate and skip stale pops, or decrease-key in place. What does each cost in code complexity and heap size?
Detecting Stale Entries
With lazy deletion the heap holds outdated (dist, v) pairs. What check lets you discard one on pop?
Building the Distance Array
How is dist[] initialized, when is a vertex marked settled, and how does the source bootstrap the whole process?
Predecessors & Path Reconstruction
How does a prev[] array let you walk back from target to source? What do you store on each successful relaxation, and which direction do you build the path in?
Why It Fails on Negative Edges
Sketch a tiny graph where a settled vertex later gains a shorter path. Why does finalizing-on-pop break the moment an edge is negative?
Variants
Early-exit when the target pops; multi-source by seeding several starts at distance 0; Dial’s algorithm with bucket queues for small integer weights — when is each worth it?
vs Bellman-Ford
Speed vs generality: which handles negatives, which is asymptotically faster, and what makes you accept the slower one?
Time Complexity
Every vertex is popped once and every edge relaxed once. How do those two counts map onto heap operations?
Binary Heap vs Fibonacci Heap
With a binary heap, what does each pop-min and each decrease-key cost, and how does that give the standard bound in terms of V and E? How does a Fibonacci heap change the decrease-key cost, and why is the improved bound rarely realized in practice?
Why the Bound Holds Intuitively
Tie the total to “one pop per vertex, one relaxation per edge, each touching the heap.” Why does the \\( \log V \\) factor attach to those operations?
Space Complexity
What must you store for dist[], prev[], the settled markers, and the adjacency representation, each in terms of V and E?
Heap Size Under Lazy Deletion
With duplicate pushes, how large can the priority queue grow relative to E, and how does that compare to a decrease-key implementation?
Implementation Walkthrough
Plan the code before writing it — what are the moving parts?
Graph & Structures Setup
What representation holds the weighted edges, and which arrays (dist[], prev[], visited) and what priority-queue element type do you initialize, and to what values?
The Main Loop
Sketch the pop-min → skip-if-stale → relax-neighbors cycle. What is the loop’s termination condition?
The Key Step: Relaxing Neighbors
For the popped vertex, what do you do per outgoing edge — the comparison, the dist[] update, the prev[] update, and the push?
Reconstruction
How do you turn prev[] into an ordered source-to-target path, and how do you signal “unreachable”?
Real Uses
Routing, network latency, game-map pathfinding — where do non-negative weights occur naturally?
Pitfalls
Forgetting to skip stale entries; assuming negative edges are fine; re-processing settled vertices; mixing up insertion vs decrease-key — what bug does each cause?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Bellman-Ford Shortest Paths
What It Solves & When
Single-source shortest paths that tolerate negative edges — when do you need this instead of Dijkstra?
Core Idea: Relax Every Edge, Repeatedly
Why does relaxing all edges V-1 times guarantee correct distances? What does each pass “settle”?
One Pass = One Edge of Path Length
After pass k, which shortest paths are guaranteed correct? Think in terms of hop count.
Why V-1 Passes Suffice
A simple shortest path has at most V-1 edges. How does that fact cap the number of passes you need?
The Dynamic-Programming View
Frame dist as “cheapest path using at most k edges.” What’s the recurrence relating the k-edge answer to the (k-1)-edge answer?
The Relaxation Loop
What’s the exact structure — repeated passes over the full edge list — and how does dist[] evolve pass to pass?
Order of Edge Processing
Why can a lucky edge order finalize distances faster, yet the worst case still needs all V-1 passes?
Detecting Negative Cycles
What happens on a V-th pass if any edge still relaxes? Why does that prove a negative cycle is reachable from the source?
Reporting the Cycle
How do you recover the actual vertices on the negative cycle, not just a yes/no? What does following predecessors give you?
Early Termination
If a full pass makes no updates, what can you conclude and stop?
Why It Doesn’t Need a Priority Queue
Dijkstra needs ordering; Bellman-Ford brute-forces edges. What does it trade away to gain negative-edge support?
Variants
SPFA (queue-based relaxation); use as a subroutine inside Johnson’s; DAG shortest paths via topological order in one pass — how do these relate?
vs Dijkstra
Generality vs speed. Which to pick when all weights are non-negative, and why?
Time Complexity
Each pass scans all E edges, and there are up to V-1 passes. How does that product give the bound, and how does early termination affect the best case?
Where the V·E Comes From
Walk the two nested loops — passes over vertices, edges within each pass. Why is each factor exactly what it is?
Space Complexity
What do you store beyond the edge list — dist[], prev[]? How does using an edge list rather than adjacency lists affect the space picture?
Implementation Walkthrough
Plan the code before writing it.
Graph & Structures Setup
Why is a flat edge list (u, v, w) convenient here? How do you initialize dist[] and prev[]?
The Relaxation Passes
Sketch the outer V-1 loop wrapping an inner sweep over every edge. What does a single relaxation update?
The Negative-Cycle Check
What extra pass do you run after the main loop, and what does any successful relaxation in it signal?
Reconstruction
How do you rebuild a path from prev[], and how do you report “unreachable” vs “affected by a negative cycle”?
Real Uses
Currency arbitrage detection, distance-vector routing protocols — why do negative weights show up there?
Pitfalls
Stopping at V passes instead of V-1; mishandling unreachable vertices; integer overflow on INF + weight — how do you guard each?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Floyd-Warshall All-Pairs Shortest Paths
What It Solves & When
Shortest paths between every pair of vertices in one shot — when is all-pairs the right framing instead of running a single-source algorithm V times?
Core Idea: DP Over Intermediate Vertices
What does “allow vertex k as an intermediate” mean, and how does adding one more allowed waypoint extend the solution?
The Recurrence
dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]) — what does each term represent?
Why It Builds Up Correctly
By the time you consider intermediate k, paths through 1..k-1 are already optimal. Why does that make the update safe?
The Triple Loop & Why k Is Outermost
Why must the intermediate vertex k be the outer loop? What breaks if i or j is outermost?
In-Place Update Without a Third Dimension
The textbook DP has a k superscript, but the code uses one 2-D matrix. Why is overwriting in place still correct?
The Distance Matrix
How do you initialize the matrix from the adjacency representation — self-distance, direct edges, missing edges?
Path Reconstruction with a Next Matrix
How does a next[i][j] (or predecessor) matrix let you rebuild the actual route between any pair? When do you update it?
Handling Negative Edges & Cycles
Negative edges are fine — but how do you detect a negative cycle? What does a negative diagonal entry dist[i][i] < 0 mean?
Variants
Transitive closure (reachability) via boolean AND/OR; min-max / widest-path by swapping the operators — what stays the same?
vs Johnson’s & Repeated Dijkstra
Dense vs sparse graphs: when does the simple matrix approach beat V runs of a single-source algorithm?
Time Complexity
Three nested loops over all vertices. Why does that give a cubic bound independent of edge count?
Why Dense Graphs Favor It
On a dense graph where E approaches V^2, how does the cubic time compare to running Dijkstra from every vertex? What makes the small constant factor matter?
Space Complexity
The distance matrix alone — what’s its size in terms of V? What extra cost does the next/predecessor matrix add?
In-Place Savings
Why does overwriting one matrix avoid an extra V-factor of space that a naive layered DP would need?
Implementation Walkthrough
Plan the code before writing it.
Matrix Setup
How do you fill the initial V x V matrix — diagonal zeros, edge weights, INF elsewhere — and initialize next[][]?
The Triple Loop
Sketch k outer, then i, then j. What single comparison-and-update sits in the innermost body?
The Key Step: Considering Vertex k
For each (i,j), what does routing through k test, and when do you commit the shorter route and update next?
Reconstruction & Cycle Check
How do you walk next[][] to list a path, and how do you scan the diagonal for negative cycles?
Real Uses
Routing tables, reachability in small graphs, kernel of more complex DP — where does dense all-pairs come up?
Pitfalls
Wrong loop order; overflow when summing two INF sentinels; mishandling i==j — what symptoms appear?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Johnson’s Algorithm
What It Solves & When
All-pairs shortest paths on a sparse graph that may have negative edges — why is this the sweet spot Johnson’s targets?
The Core Trick: Reweight, Then Dijkstra
We want to run fast Dijkstra V times, but negatives block it. What’s the high-level plan to make every edge non-negative first?
The Potential Function
Each vertex gets a potential h(v). The reweighted edge is w'(u,v) = w(u,v) + h(u) - h(v) — what role does the potential play?
Why Reweighting Keeps Edges Non-Negative
Given how h is computed, why is every w'(u,v) >= 0? What inequality on shortest-path distances guarantees it?
Why the Shift Is Path-Independent
Summing w' along any path telescopes to w(path) + h(start) - h(end). Why does that mean the cheapest path between two vertices is unchanged?
Adding a Virtual Source
A new vertex connects to all others with weight-0 edges. What is it for, and what does running Bellman-Ford from it produce?
The Bellman-Ford Phase
What does this phase compute (the h values), and what does it catch before any Dijkstra runs?
Detecting Negative Cycles Early
If Bellman-Ford finds a negative cycle here, what do you do, and why is it pointless to continue?
The Dijkstra Phase
Run Dijkstra from every vertex on the reweighted graph. Why is each run now valid?
Recovering True Distances
Dijkstra returns reweighted distances d'(u,v). How do you undo the shift with h(u) and h(v) to get the real distance?
vs Floyd-Warshall
Sparse vs dense: when does Johnson’s beat the simple cubic matrix, and when is it overkill?
Time Complexity
Add up the phases: one Bellman-Ford plus V Dijkstra runs. How do these combine?
Which Phase Dominates
On a sparse graph, is the bottleneck the single O(VE) Bellman-Ford or the V heap-based Dijkstra runs? How does graph density shift the answer?
Space Complexity
What do you store — the augmented graph, the h[] potentials, the per-source distance arrays, the reweighted edge weights? Size each in terms of V and E.
The Output Matrix
All-pairs results are V distance arrays. Why is the V^2 output unavoidable regardless of how fast you compute it?
Implementation Walkthrough
Plan the code before writing it.
Building the Augmented Graph
How do you add the virtual source and its zero-weight edges without disturbing the original vertices?
Computing Potentials with Bellman-Ford
Run from the virtual source — what does the resulting distance array become, and how do you bail on a negative cycle?
Reweighting Every Edge
Apply w'(u,v) = w(u,v) + h(u) - h(v) across the edge set. What invariant should you sanity-check afterward?
Per-Source Dijkstra & Un-Shifting
Loop Dijkstra over every source on the reweighted graph, then convert each reweighted distance back. What’s the un-shift formula?
Real Uses
All-pairs distances in large sparse networks with some negative weights — where does this combination occur?
Pitfalls
Forgetting to remove the virtual source before Dijkstra; applying the un-shift wrong; mixing up h(u) and h(v); reusing original weights by mistake — what breaks?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Prim’s Minimum Spanning Tree
What It Solves & When
Build a minimum spanning tree by growing one connected tree — what problem is an MST, and when does Prim’s shape fit best?
Growing a Single Tree
The vertex set splits into “in tree” and “not yet.” How does the tree expand exactly one vertex per step?
The Cut Property
State the cut property. Why does it justify always grabbing the cheapest edge crossing the in/out boundary?
Why the Greedy Edge Is Always Safe
Take any cut separating tree from non-tree. Why must the minimum crossing edge belong to some MST?
Priority Queue of Crossing Edges
Each non-tree vertex holds a key = cheapest known edge to the tree. When is that key decreased?
Lazy vs Eager Implementation
Lazy pushes stale (weight, vertex) entries and skips them on pop; eager uses decrease-key keyed by vertex. What’s the trade-off in memory and code complexity?
Detecting Stale Pops
In the lazy version, how do you recognize and discard an entry for a vertex already in the tree?
Tracking the Tree
What arrays do you maintain — key[], inMST[], parent[] — and what does each contribute?
Reconstructing the MST Edges
How does parent[] give you the actual tree edges and total weight, not just a number?
Variants
Adjacency-matrix Prim’s (no heap) for dense graphs; Fibonacci-heap Prim’s — when does each win?
vs Kruskal’s
Prim’s grows one tree with a heap; Kruskal’s sorts all edges with union-find. Dense vs sparse — which do you reach for?
Time Complexity
Every vertex is extracted once and every edge can trigger a key update. How do those map to heap operations?
Binary Heap vs Matrix vs Fibonacci Heap
Compare the three: binary-heap Prim’s in terms of E and V, the simple O(V^2) adjacency-matrix version, and the Fibonacci-heap bound. Why does the matrix version win on dense graphs where E nears V^2?
Space Complexity
What do key[], inMST[], parent[], the heap, and the graph representation cost in terms of V and E?
Heap Size Under Lazy Deletion
How large can the priority queue grow when you push duplicates instead of decreasing keys?
Implementation Walkthrough
Plan the code before writing it.
Graph & Structures Setup
What adjacency representation, and how do you initialize key[] (to INF), inMST[], parent[], and seed the start vertex?
The Main Loop
Sketch the extract-min → mark-in-tree → scan-neighbors cycle. When does the loop end?
The Key Step: Updating Crossing Edges
For each neighbor of the newly added vertex, what comparison decides whether to update its key and parent and push it?
Reconstruction
How do you read parent[] into a list of MST edges and accumulate the total weight?
Real Uses
Network/cable layout, clustering, approximation kernels — where does a single growing tree fit naturally?
Pitfalls
Forgetting to skip already-in-tree pops; not decreasing keys; assuming it works on directed graphs; starting with key[] not set to infinity — what goes wrong?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Kruskal’s Minimum Spanning Tree
What It Solves & When
Build a minimum spanning tree by adding cheapest safe edges globally — when does this edge-centric view beat growing a tree?
Core Idea: Sort Edges, Add If Safe
Process edges in non-decreasing weight. Why is the cheapest edge that doesn’t form a cycle always safe to add?
Why Sorted Order Makes Greedy Work
Tie this to the cut property: when you pick the next-cheapest non-cycling edge, which cut is it the minimum crosser of?
Union-Find as the Engine
How does a disjoint-set structure answer “are these two endpoints already connected?” in near-constant time?
Find with Path Compression
What does find return, and how does compression flatten the tree on the way up to the root?
Union by Rank / Size
Why attach the smaller tree under the larger? What worst-case tree height does this prevent?
The Cut & Cycle Properties
Which property justifies including a light crossing edge, and which justifies rejecting an edge inside a component?
Detecting Cycles Without Searching
Why does “both endpoints share a root” mean adding the edge would create a cycle?
Stopping Early
How many edges does a spanning tree on V vertices have, and how does that let you halt before exhausting the sorted list?
Variants
Pre-sorted or radix-sorted edges; filter-Kruskal; building a spanning forest on disconnected graphs — what changes?
vs Prim’s
Kruskal’s loves sparse graphs and pre-sorted edges; Prim’s loves dense ones. What drives the choice?
Time Complexity
Two phases: sorting the edges, then a near-linear sweep with union-find. Which dominates?
Why the Sort Dominates
State the sort cost in terms of E, and explain why the union-find work over all edges is effectively linear (the inverse-Ackermann factor). Why is \\( \log E \\) interchangeable with \\( \log V \\) here?
Space Complexity
What do the edge array and the union-find parent[]/rank[] arrays cost in terms of V and E?
Union-Find Footprint
Why is the disjoint-set overhead only linear in V, regardless of edge count?
Implementation Walkthrough
Plan the code before writing it.
Edge List & Union-Find Setup
How do you build the sortable edge list, and initialize parent[] and rank[] so each vertex starts in its own set?
Sorting & The Main Sweep
After sorting by weight, sketch the loop: for each edge, decide include-or-skip. What is the accept condition?
The Key Step: Union on Accept
When endpoints have different roots, what two things happen — record the edge, and merge the sets how?
Building the Result
How do you accumulate MST edges and total weight, and detect that the graph was disconnected?
Real Uses
Clustering (cut the k-1 heaviest MST edges), circuit/road design, image segmentation — where does the global edge view help?
Pitfalls
Skipping path compression/union-by-rank and getting slow finds; not handling disconnected graphs; unstable tie handling — what bites you?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Ford-Fulkerson Maximum Flow
What It Solves & When
Push as much “stuff” from source to sink as the edge capacities allow — what real problems reduce to maximum flow?
Flow Networks & Capacity Constraints
Define capacity, flow on an edge, conservation at intermediate nodes, and the value of a flow. What makes a flow feasible?
Skew Symmetry & Conservation
Why is flow on (u,v) the negative of flow on (v,u), and why must inflow equal outflow at every non-terminal node?
Core Idea: Augmenting Paths
Find a source-to-sink path with spare capacity, push flow along it, repeat. How much can each path carry?
The Bottleneck
Why does each augmenting path increase total flow by exactly its minimum residual capacity?
The Residual Graph
What is residual capacity, and how is it built from current flow and original capacity?
Why Back Edges Matter
Back edges let later paths “undo” earlier flow. Why is this cancellation essential to reaching the true maximum, not just a local optimum?
Updating Residuals on Augment
When you push b units along a path, what happens to the forward residual and the reverse residual of each edge?
Finding Paths: DFS and the Generic Method
Ford-Fulkerson leaves path choice open. What does a plain DFS search give you, and what does it risk?
Max-Flow Min-Cut Theorem
State it. Why does “no augmenting path remains” mean the flow is maximum?
The Three Equivalent Conditions
Max flow, no augmenting path, and flow value equal to some cut capacity — why are these three statements equivalent?
Termination & Irrational Capacities
Why can poor path choices run forever (or extremely long) with irrational/large capacities? What does integral capacity guarantee?
Variants
Different path-selection rules turn it into Edmonds-Karp (BFS) or capacity-scaling. How does the choice change behavior?
vs Edmonds-Karp
Generic Ford-Fulkerson’s runtime depends on the flow value; Edmonds-Karp fixes the path rule. What’s the trade-off?
Time Complexity
Each augmentation adds at least one unit of flow with integral capacities. How does that connect runtime to the flow value |f*|?
Why It Depends on the Flow Value
Each augmenting-path search costs O(E). Multiply by the number of augmentations — why can that number be as large as |f*|, and why is that a weakness?
Space Complexity
What do you store — the residual capacities (forward and reverse), the adjacency structure, and the per-search visited/parent arrays? Size each in terms of V and E.
Representing Residuals
Why is pairing each edge with its reverse (and indexing one from the other) a compact way to hold the residual graph?
Implementation Walkthrough
Plan the code before writing it.
Graph & Residual Setup
How do you store each directed edge alongside its reverse edge so updates touch both? What are the initial residual capacities?
The Augmenting Loop
Sketch: search for an s-t path with positive residual, find its bottleneck, augment, repeat until none exists.
The Key Step: Augmenting Along a Path
Given a found path, how do you compute the bottleneck and then update forward and reverse residuals edge by edge?
Reading Off the Max Flow
Where does the final flow value come from — the bottlenecks summed, or the saturated edges out of the source?
Real Uses
Bipartite matching, project selection, network reliability, image segmentation — how do these become flow problems?
Pitfalls
Forgetting to add reverse edges; not updating both directions on augment; choosing pathological paths; non-integer capacities causing non-termination — what fails?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Edmonds-Karp Maximum Flow
What It Solves & When
The same max-flow problem as Ford-Fulkerson, but with a concrete, predictable path rule — why does pinning the path choice matter?
Core Idea: BFS for the Shortest Augmenting Path
Pick the augmenting path with the fewest edges each iteration. Why shortest-by-hops rather than highest-capacity?
Why “Shortest” Means Fewest Edges, Not Cheapest
There are no edge weights here — only capacities. What does BFS distance measure in the residual graph?
The Residual Graph, Same as Ford-Fulkerson
BFS runs over residual capacities. What does Edmonds-Karp inherit unchanged — residuals, back edges, augmenting?
How One Iteration Works
BFS from source to sink, record the parent path, find the bottleneck, augment forward and reverse. Walk through one round.
Recording the Path via Parent Pointers
How does BFS leave behind a parent[] trail you can walk backward to extract the augmenting path and its bottleneck?
Why Path Lengths Never Decrease
The shortest source-sink distance in the residual graph is monotonically non-decreasing across iterations. Why does augmenting never create a shorter path?
Bounding the Number of Augmentations
Each edge becomes a saturated bottleneck only O(V) times. Why does that, combined with E edges, cap total augmentations at O(VE)?
Critical Edges
What makes an edge “critical” on an augmentation, and why must its BFS distance strictly increase before it can be critical again?
Variants & Relatives
Dinic’s algorithm builds on the same shortest-path idea with level graphs and blocking flows — how does it go further?
vs Ford-Fulkerson
Generic Ford-Fulkerson’s runtime scales with the flow value; Edmonds-Karp’s does not. What exactly did fixing BFS buy you?
Time Complexity
Combine the two independent counts: how many augmentations, and how much does each BFS cost?
Why It’s Flow-Value Independent
The O(VE) augmentation bound comes from the critical-edge argument, not from |f*|. With each BFS costing O(E), how do you reach the overall bound? Why is independence from the flow value the whole point?
Space Complexity
What does Edmonds-Karp store beyond the residual graph — the BFS queue, visited[], parent[]? Size each in terms of V and E.
Residual Graph Footprint
Why is the dominant space cost the residual capacities over forward and reverse edges, i.e. O(V + E)?
Implementation Walkthrough
Plan the code before writing it.
Graph & Residual Setup
Same paired forward/reverse edge structure as Ford-Fulkerson — how do you initialize residual capacities?
The BFS Augmenting-Path Search
Sketch a BFS from source that fills parent[] and stops when it reaches the sink. What residual condition lets an edge be traversed?
The Key Step: Augment Along the BFS Path
Walk parent[] back from sink to source to find the bottleneck, then update forward and reverse residuals. How do you fold this into the loop?
Termination & Result
When does the outer loop stop, and how do you total the flow that was pushed?
Real Uses
Anywhere max flow is needed with capacities that could be huge, where flow-value-dependent runtime is unacceptable. Examples?
Pitfalls
Using DFS by mistake and losing the guarantee; rebuilding the graph each BFS; forgetting reverse-edge updates; re-running BFS without resetting visited/parent — what regresses?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Minimum Cut
What It Solves & When
Find the cheapest set of edges whose removal disconnects source from sink (or splits the graph) — what does “cheapest to break” model in practice?
Defining an s-t Cut
Which vertex partitions count as a valid s-t cut, and how do you measure a cut’s capacity?
Only Forward Edges Count
Why does cut capacity sum only edges crossing from the source side to the sink side, not back edges?
Max-Flow Min-Cut Duality
Why does the maximum flow value equal the minimum cut capacity? What’s the intuition linking them?
Why Any Flow Bounds Any Cut
Every flow value is at most every cut capacity. Why does that “weak duality” make a matching flow/cut pair a certificate of optimality for both?
Recovering the Cut from a Max Flow
After computing max flow, run a reachability search in the residual graph. How does the set of reachable vertices give you the partition?
Which Edges Are Cut
Given the partition, how do you list the actual cut edges, and why are they all saturated by the max flow?
Global Min-Cut vs s-t Min-Cut
The s-t version fixes two terminals; the global version minimizes over all splits. How do the problems and algorithms differ?
Stoer-Wagner for Global Cuts
When there’s no fixed s and t, what does Stoer-Wagner do instead of max flow — and what is a “maximum adjacency” ordering?
Variants
Weighted vs unweighted; node cuts via node-splitting; multi-terminal cuts — how do you reduce these to the basic form?
Time Complexity
The s-t min cut costs exactly one max-flow computation. What does that make its runtime, given your chosen max-flow algorithm?
s-t via Max Flow vs Global via Stoer-Wagner
Why does the global min cut not need to fix terminals, and what does Stoer-Wagner’s repeated-merging cost come to in terms of V and E?
Space Complexity
What do you store — the flow network/residual graph, plus the reachability search’s visited set for cut recovery? Size each in terms of V and E.
Implementation Walkthrough
Plan the code before writing it.
Reusing the Max-Flow Machinery
How does the min-cut routine build directly on a completed max-flow run rather than starting fresh?
The Key Step: Residual Reachability
From the source, BFS/DFS over edges with positive residual capacity. What does the reached set become?
Extracting the Cut Edges
Scan original edges for those going from a reached vertex to an unreached one. Why are exactly these the min cut, and how do you sum their capacity?
Real Uses
Image segmentation (foreground/background), network reliability, community detection, bipartite vertex cover — sketch one reduction.
Pitfalls
Counting back edges in capacity; confusing global vs s-t; assuming the min cut is unique; reading reachability from the original graph instead of the residual — what mistakes follow?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Graph Algorithms: Problem Set
Every problem here is an implementation task: fill in the stub in problemset/
and make its test in tests/problemset/ pass. The set is organized into two
groups. Foundational problems build the primitives you reuse everywhere —
walking edges, crossing cuts, counting components, validating spanning trees,
and reading bottlenecks. Applied Problems then weave real LeetCode and
contest-style challenges from easy to hard across the three big themes of
weighted graphs: shortest paths, minimum spanning trees, and maximum
flow / minimum cut. Applied problems take their weighted graphs as int[][]
edge lists {u, v, w} (or {u, v, c} for capacities); the Foundational tier
reuses the shared WeightedGraph helper directly.
Foundational
Problem 1: Path Weight Validator
Description
Given a WeightedGraph and an ordered list of vertices describing a walk,
compute the total weight of traversing that exact sequence. If any consecutive
pair in the list is not connected by an edge, the walk is invalid: return an
empty result instead of a weight. An empty path or a single-vertex path has
total weight 0. When several edges connect the same pair, use the lightest
one.
Examples
Example 1:
Input: edges = [(0,1,4), (1,2,3)], path = [0, 1, 2]
Output: 7.0
The walk 0 → 1 → 2 uses edges of weight 4 and 3, totaling 7.
Example 2:
Input: edges = [(0,1,4)], path = [0, 2]
Output: empty
There is no edge from 0 to 2, so the walk is invalid.
Example 3:
Input: edges = [(0,1,4)], path = [0]
Output: 0.0
A single-vertex path traverses no edges.
Constraints
0 <= path.size()- Vertices in
pathare valid indices ofgraph. - Edge weights may be any
double.
Problem 2: Reachable Set Under Budget
Description
Given a WeightedGraph, a source vertex, and a non-negative distance budget
B, return the set of every vertex whose shortest-path distance from the source
is at most B. The source itself is always included (distance 0). Edge
weights are non-negative, so a Dijkstra-style relaxation finds the distances.
Examples
Example 1:
Input: edges = [(0,1,2), (1,2,2), (2,3,2)], source = 0, B = 4
Output: {0, 1, 2}
Distances from 0 are 0, 2, 4, 6; only the first three are within budget 4.
Example 2:
Input: edges = [(0,1,5)], source = 0, B = 0
Output: {0}
Only the source is within a budget of 0.
Example 3:
Input: edges = [(0,1,1), (0,2,1)], source = 0, B = 10
Output: {0, 1, 2}
Everything reachable fits inside a generous budget.
Constraints
0 <= B- Edge weights are non-negative.
sourceis a valid vertex.
Problem 3: Lightest Edge Across a Cut
Description
Given a WeightedGraph and a subset S of its vertices, find the
minimum-weight edge with exactly one endpoint in S — the cheapest edge
crossing the cut (S, V \ S). Return that edge, or report that none exists when
the cut is empty (no edge has exactly one endpoint inside S). This is the
greedy step at the heart of Prim’s algorithm.
Examples
Example 1:
Input: edges = [(0,1,5), (1,2,2), (0,2,9)], S = {0, 1}
Output: (1, 2, 2)
Edges crossing the cut are (1,2,2) and (0,2,9); the lighter is (1,2,2).
Example 2:
Input: edges = [(0,1,5)], S = {0, 1}
Output: none
Both endpoints of the only edge are inside S, so no edge crosses.
Example 3:
Input: edges = [(0,1,5), (2,3,1)], S = {0}
Output: (0, 1, 5)
Only (0,1) crosses; (2,3) lies entirely outside S.
Constraints
Sis a subset of the graph’s vertices.- The graph may be treated as undirected.
Problem 4: Count Connected Components
Description
Treating the WeightedGraph as undirected (ignore edge directions and
weights), count the number of connected components. Isolated vertices each count
as their own component.
Examples
Example 1:
Input: vertexCount = 5, edges = [(0,1), (1,2), (3,4)]
Output: 2
{0,1,2} forms one component and {3,4} forms another.
Example 2:
Input: vertexCount = 3, edges = []
Output: 3
With no edges, every vertex is isolated.
Example 3:
Input: vertexCount = 4, edges = [(0,1), (1,2), (2,3)]
Output: 1
A single path links all four vertices.
Constraints
0 <= vertexCount- Self-loops and parallel edges may appear.
Problem 5: Spanning Tree Validator
Description
Given a vertex count and a list of edges claimed to form a spanning tree, verify
that the edges connect every vertex without forming a cycle, and return the
total weight of the tree. If the edge set is not a valid spanning tree (wrong
edge count, a cycle, or a disconnected graph), return an empty result. A valid
spanning tree on V vertices has exactly V - 1 edges, is acyclic, and is
connected.
Examples
Example 1:
Input: vertexCount = 3, edges = [(0,1,4), (1,2,3)]
Output: 7.0
Two edges connect all three vertices acyclically; total weight is 7.
Example 2:
Input: vertexCount = 3, edges = [(0,1,4), (1,2,3), (0,2,1)]
Output: empty
Three edges on three vertices must contain a cycle, so this is not a tree.
Example 3:
Input: vertexCount = 4, edges = [(0,1,1), (2,3,1)]
Output: empty
The edges leave the graph disconnected.
Constraints
0 <= vertexCount- Edges are undirected.
Problem 6: Bottleneck Of A Path
Description
Given a WeightedGraph whose weights represent capacities and a fixed path
(an ordered list of vertices), return the bottleneck of the path: the minimum
edge capacity along it. If any consecutive pair is not connected, the path is
invalid and you return an empty result. When several edges connect a pair, the
widest (maximum-capacity) one is available to the path. A single-vertex path has
no edges; treat its bottleneck as positive infinity.
Examples
Example 1:
Input: edges = [(0,1,5), (1,2,3), (2,3,8)], path = [0, 1, 2, 3]
Output: 3.0
Capacities along the path are 5, 3, 8; the bottleneck is 3.
Example 2:
Input: edges = [(0,1,5)], path = [0, 2]
Output: empty
No edge connects 0 and 2, so the path is invalid.
Example 3:
Input: edges = [(0,1,5), (0,1,9)], path = [0, 1]
Output: 9.0
The path may use the wider of the two parallel edges.
Constraints
- Capacities are positive.
- Vertices in
pathare valid indices.
Applied Problems
Problem 7: Network Delay Time
LeetCode: 743. Network Delay Time
Description
A network of n nodes labeled 1..n is described by directed, weighted travel
times times[i] = {u, v, w}, meaning a signal from u reaches v after w
units of time. A signal is sent from node k. Return the minimum time for the
signal to reach all n nodes, or -1 if some node is unreachable. Run
Dijkstra from k; the answer is the largest finite distance.
Examples
Example 1:
Input: times = [[2,1,1],[2,3,1],[3,4,1]], n = 4, k = 2
Output: 2
From node 2: node 1 at time 1, node 3 at time 1, node 4 at time 2. The last to hear is node 4 at time 2.
Example 2:
Input: times = [[1,2,1]], n = 2, k = 1
Output: 1
The single edge delivers the signal to node 2 at time 1.
Example 3:
Input: times = [[1,2,1]], n = 2, k = 2
Output: -1
Node 1 cannot be reached from node 2.
Constraints
1 <= k <= n <= 1001 <= times.length <= 60000 <= w <= 100
Problem 8: Path With Maximum Probability
LeetCode: 1514. Path with Maximum Probability
Description
You are given an undirected weighted graph of n nodes 0..n-1, with edges
edges[i] = {u, v} where the success probability of traversing that edge is
succProb[i]. Return the maximum probability of success to go from start to
end (the product of edge probabilities along the path), or 0 if no path
exists. Because probabilities multiply and lie in [0, 1], a Dijkstra-style
search that maximizes the running product (using a max-heap) finds the answer.
Examples
Example 1:
Input: n = 3, edges = [[0,1],[1,2],[0,2]], succProb = [0.5,0.5,0.2], start = 0, end = 2
Output: 0.25
The path 0 → 1 → 2 has probability 0.5 * 0.5 = 0.25, beating the direct edge’s 0.2.
Example 2:
Input: n = 3, edges = [[0,1],[1,2],[0,2]], succProb = [0.5,0.5,0.3], start = 0, end = 2
Output: 0.3
Now the direct edge (0.3) beats the two-hop path (0.25).
Example 3:
Input: n = 3, edges = [[0,1]], succProb = [0.5], start = 0, end = 2
Output: 0.0
No path connects 0 to 2.
Constraints
2 <= n <= 10^40 <= start, end < n0 <= succProb[i] <= 1
Problem 9: Path With Minimum Effort
LeetCode: 1631. Path With Minimum Effort
Description
You are given a rows x cols grid heights where heights[r][c] is the height
of cell (r, c). Travel from the top-left cell to the bottom-right cell, moving
up, down, left, or right. The effort of a route is the maximum absolute
height difference between two consecutive cells along it. Return the minimum
effort required. A Dijkstra-style bottleneck search — where a path’s cost is the
max edge weight rather than the sum — solves it.
Examples
Example 1:
Input: heights = [[1,2,2],[3,8,2],[5,3,5]]
Output: 2
The route 1→3→5→3→5 has a maximum adjacent difference of 2, better than routes through the 8.
Example 2:
Input: heights = [[1,2,3],[3,8,4],[5,3,5]]
Output: 1
The route 1→2→3→4→5 keeps every step within difference 1.
Example 3:
Input: heights = [[1,2,1,1,1],[1,2,1,2,1],[1,2,1,2,1],[1,2,1,2,1],[1,1,1,2,1]]
Output: 0
A path of all-equal heights exists, so the effort is 0.
Constraints
1 <= rows, cols <= 1001 <= heights[r][c] <= 10^6
Problem 10: Min Cost To Connect All Points
LeetCode: 1584. Min Cost to Connect All Points
Description
You are given points on a 2D plane, where points[i] = {xi, yi}. The cost of
connecting two points is the Manhattan distance between them,
|xi - xj| + |yi - yj|. Return the minimum total cost to connect all points so
that there is exactly one simple path between any two points. This is a minimum
spanning tree over the complete graph of Manhattan distances (Prim’s or
Kruskal’s).
Examples
Example 1:
Input: points = [[0,0],[2,2],[3,10],[5,2],[7,0]]
Output: 20
A minimum spanning tree of the Manhattan-distance graph has total weight 20.
Example 2:
Input: points = [[3,12],[-2,5],[-4,1]]
Output: 18
Connecting the three points along the cheapest two edges costs 18.
Example 3:
Input: points = [[0,0]]
Output: 0
A single point needs no connections.
Constraints
1 <= points.length <= 1000-10^6 <= xi, yi <= 10^6- All points are distinct.
Problem 11: Cheapest Flights Within K Stops
LeetCode: 787. Cheapest Flights Within K Stops
Description
There are n cities 0..n-1 connected by directed flights
flights[i] = {from, to, price}. Return the cheapest price from src to dst
using at most k intermediate stops, or -1 if there is no such route. A
hop-bounded Bellman-Ford (relax all edges k + 1 rounds over a snapshot of the
previous round’s distances) respects the stop limit cleanly.
Examples
Example 1:
Input: n = 4, flights = [[0,1,100],[1,2,100],[2,0,100],[1,3,600],[2,3,200]], src = 0, dst = 3, k = 1
Output: 700
0 → 1 → 3 costs 700 with one stop; the cheaper 0 → 1 → 2 → 3 (500) needs two stops and is disallowed.
Example 2:
Input: n = 3, flights = [[0,1,100],[1,2,100],[0,2,500]], src = 0, dst = 2, k = 1
Output: 200
0 → 1 → 2 (one stop) costs 200, beating the direct 500.
Example 3:
Input: n = 3, flights = [[0,1,100],[1,2,100],[0,2,500]], src = 0, dst = 2, k = 0
Output: 500
With no stops allowed, only the direct flight qualifies.
Constraints
1 <= n <= 1000 <= flights.length <= n * (n - 1)1 <= price <= 10^40 <= k < n
Problem 12: Find The City With The Smallest Number Of Neighbors
LeetCode: 1334. Find the City With the Smallest Number of Neighbors at a Threshold Distance
Description
There are n cities 0..n-1 connected by weighted bidirectional roads
edges[i] = {u, v, w}. For a given distanceThreshold, find the city that can
reach the fewest other cities within that threshold distance. If multiple cities
tie, return the one with the greatest label. All-pairs shortest paths
(Floyd-Warshall) computes the reachability counts directly.
Examples
Example 1:
Input: n = 4, edges = [[0,1,3],[1,2,1],[1,3,4],[2,3,1]], distanceThreshold = 4
Output: 3
City 3 reaches only cities 1 and 2 within distance 4 (tied for fewest with city 0), and 3 is the larger label.
Example 2:
Input: n = 5, edges = [[0,1,2],[0,4,8],[1,2,3],[1,4,2],[2,3,1],[3,4,1]], distanceThreshold = 2
Output: 0
City 0 reaches only city 1 within distance 2 — the fewest of all cities.
Example 3:
Input: n = 2, edges = [[0,1,10]], distanceThreshold = 5
Output: 1
Neither city reaches the other; both tie at 0, so the larger label 1 wins.
Constraints
2 <= n <= 1001 <= w, distanceThreshold <= 10^4
Problem 13: Swim In Rising Water
LeetCode: 778. Swim in Rising Water
Description
You are given an n x n grid where grid[r][c] is the elevation of cell
(r, c). At time t, every cell with elevation at most t is submerged and
swimmable. Starting at (0, 0), you may swim 4-directionally to adjacent cells
that are submerged. Return the least time t at which you can reach the
bottom-right cell (n-1, n-1). This is a minimax-path / bottleneck shortest path
on cell elevations.
Examples
Example 1:
Input: grid = [[0,2],[1,3]]
Output: 3
You cannot move until time 3, when cell (1,1) with elevation 3 becomes swimmable, completing the route 0 → 1 → 3.
Example 2:
Input: grid = [[0,1,2,3,4],[24,23,22,21,5],[12,13,14,15,16],[11,17,18,19,20],[10,9,8,7,6]]
Output: 16
The optimal route waits until time 16, the highest elevation it must cross.
Example 3:
Input: grid = [[0,1],[2,3]]
Output: 3
Reaching (1,1) requires its elevation 3 to be submerged.
Constraints
n == grid.length == grid[i].length1 <= n <= 50grid[i][j]is a permutation of0..n*n-1.
Problem 14: Minimum Cost To Reach Destination In Time
LeetCode: 1928. Minimum Cost to Reach Destination in Time
Description
There are n cities 0..n-1 connected by bidirectional roads
edges[i] = {u, v, time}, where time is the minutes to traverse road i.
Each city j has a passing fee passingFees[j] charged every time you are at
that city (including the start and end). Starting at city 0 with a time budget
of maxTime minutes, return the minimum total passing fee to reach city
n-1 within the budget, or -1 if impossible. State is (city, timeUsed);
relax with a Dijkstra-style search keyed on cost subject to the time bound.
Examples
Example 1:
Input: maxTime = 30, edges = [[0,1,10],[1,2,10],[2,5,10],[0,3,1],[3,4,10],[4,5,15]],
passingFees = [5,1,2,20,20,3]
Output: 11
The route 0 → 1 → 2 → 5 takes 30 minutes and pays fees 5 + 1 + 2 + 3 = 11.
Example 2:
Input: maxTime = 29, edges = [[0,1,10],[1,2,10],[2,5,10],[0,3,1],[3,4,10],[4,5,15]],
passingFees = [5,1,2,20,20,3]
Output: 48
With one fewer minute the cheap route is too slow, forcing a costlier one.
Example 3:
Input: maxTime = 25, edges = [[0,1,10],[1,2,10],[2,5,10],[0,3,1],[3,4,10],[4,5,15]],
passingFees = [5,1,2,20,20,3]
Output: -1
No route reaches city 5 within 25 minutes.
Constraints
1 <= n <= 10001 <= maxTime <= 10001 <= passingFees[j] <= 1000
Problem 15: The Maze II — Shortest Roll To Destination
LeetCode: 505. The Maze II
Description
A ball rolls through a maze given as a grid of 0 (empty) and 1 (wall). From a
start cell the ball can roll up, down, left, or right, and it does not stop
until it hits a wall. Given start and destination cells, return the shortest
distance (number of empty cells the ball travels, not counting the start) for the
ball to stop at the destination, or -1 if it cannot. Model each “roll until a
wall” as a weighted edge and run Dijkstra over stop positions.
Examples
Example 1:
Input: maze = [[0,0,1,0,0],[0,0,0,0,0],[0,0,0,1,0],[1,1,0,1,1],[0,0,0,0,0]],
start = [0,4], destination = [4,4]
Output: 12
The ball can stop at the destination after rolling a total of 12 cells.
Example 2:
Input: maze = [[0,0,1,0,0],[0,0,0,0,0],[0,0,0,1,0],[1,1,0,1,1],[0,0,0,0,0]],
start = [0,4], destination = [3,2]
Output: -1
The ball cannot stop at (3,2); it always rolls past.
Example 3:
Input: maze = [[0,0],[0,0]], start = [0,0], destination = [1,1]
Output: 2
Roll right then down (or down then right) to stop in the corner after 2 cells.
Constraints
1 <= rows, cols <= 100startanddestinationare empty cells.
Problem 16: Frostbite Supply Lines
Description
An outpost network has n depots 0..n-1 joined by undirected supply roads
roads[i] = {u, v, w}, where w is the road’s snow depth. A convoy crosses a
road only if its plow rating is at least that road’s snow depth. Return the
minimum single plow rating r such that the roads with w <= r connect all n
depots, or -1 if no rating connects them. This is the bottleneck / minimax
spanning tree problem: the answer is the largest edge weight in a minimum
spanning tree.
Examples
Example 1:
Input: n = 4, roads = [[0,1,3],[1,2,1],[2,3,4],[0,3,2]]
Output: 3
Using roads of depth ≤ 3 connects all depots; depth 2 alone does not.
Example 2:
Input: n = 3, roads = [[0,1,5],[1,2,5]]
Output: 5
Both roads share depth 5 and are both required to connect the depots.
Example 3:
Input: n = 3, roads = [[0,1,2]]
Output: -1
Depot 2 is isolated; no rating connects the network.
Constraints
1 <= n <= 10^50 <= w <= 10^9
Problem 17: Festival Stage Cabling
Description
A festival must power n stages 0..n-1; stage 0 holds the generator.
Candidate undirected cables cables[i] = {u, v, w} connect two stages at install
cost w. With a total budget, install cables greedily from cheapest to most
expensive (skipping any cable whose two stages are already in one component, and
any cable that would push the running total over budget). Return the maximum
number of stages — including stage 0 — drawn into a single powered component
connected to the generator.
Examples
Example 1:
Input: n = 4, cables = [[0,1,1],[1,2,2],[2,3,5]], budget = 3
Output: 3
Install (0,1,1) and (1,2,2) for total 3; the (2,3) cable would overspend, so stages {0,1,2} are powered.
Example 2:
Input: n = 3, cables = [[0,1,4]], budget = 2
Output: 1
No affordable cable touches the generator, so only stage 0 is powered.
Example 3:
Input: n = 4, cables = [[1,2,1],[2,3,1]], budget = 10
Output: 1
The affordable cables form a component {1,2,3} disconnected from the generator; only stage 0 stays powered.
Constraints
1 <= n <= 10^50 <= w <= 10^90 <= budget <= 10^14
Problem 18: Galactic Courier
Description
The Galactic Courier Guild has n relay stations 0..n-1 joined by
bidirectional warp lanes lanes[i] = {u, v, w} with fuel cost w. Some lanes
are boosted (boosted[i] == true): using a boosted lane halves its cost via
integer division (w / 2). A courier travelling from src to dst may apply
the boost to at most one lane over the whole trip. Return the minimum total
fuel from src to dst, or -1 if dst is unreachable. Run a layered
Dijkstra over states (station, boostUsed).
Examples
Example 1:
Input: n = 3, lanes = [[0,1,10],[1,2,10]], boosted = [false, true], src = 0, dst = 2
Output: 15
Boosting the second lane gives 10 + 10/2 = 15.
Example 2:
Input: n = 3, lanes = [[0,1,4],[1,2,4],[0,2,10]], boosted = [false,false,true], src = 0, dst = 2
Output: 5
Boosting the direct lane costs 10/2 = 5, beating the unboosted 8.
Example 3:
Input: n = 2, lanes = [], boosted = [], src = 0, dst = 1
Output: -1
No lanes connect the stations.
Constraints
1 <= n <= 10^50 <= w <= 10^9- Lanes may repeat; the graph may be disconnected.
Problem 19: Toll Road Reimbursement
Description
A company runs trucks across n cities 0..n-1 over directed toll roads
roads[i] = {u, v, w}. Promotions can make a toll negative (a rebate), but the
network has no negative-cost cycle. From headquarters at city 0, return an
array cost of length n where cost[v] is the minimum net toll from city 0
to city v, using Integer.MAX_VALUE for any unreachable city. Negative weights
rule out Dijkstra; use Bellman-Ford.
Examples
Example 1:
Input: n = 3, roads = [[0,1,4],[1,2,-2],[0,2,5]]
Output: [0, 4, 2]
City 2 is cheaper via 0 → 1 → 2 (4 + (-2) = 2) than the direct edge of 5.
Example 2:
Input: n = 3, roads = [[0,1,1]]
Output: [0, 1, 2147483647]
City 2 is unreachable, so it stays at Integer.MAX_VALUE.
Example 3:
Input: n = 2, roads = [[0,1,-3]]
Output: [0, -3]
A single rebate edge yields a negative net toll.
Constraints
1 <= n <= 2000-10^4 <= w <= 10^4- No negative-cost cycle exists.
Problem 20: Shortest Path With Exactly K Edges
Description
Given a WeightedGraph, a source, a sink, and an integer k, compute the
minimum-weight walk from source to sink that uses exactly k edges (vertices
and edges may repeat), or report that none exists. Dynamic programming over the
number of edges used — best[e][v] = min weight to reach v in exactly e
edges — solves it in O(k * E) time.
Examples
Example 1:
Input: edges = [(0,1,2), (1,2,3), (0,2,10)], source = 0, sink = 2, k = 2
Output: 5.0
The two-edge walk 0 → 1 → 2 costs 5; the direct edge uses only one edge.
Example 2:
Input: edges = [(0,1,2), (1,2,3), (0,2,4)], source = 0, sink = 2, k = 1
Output: 4.0
Exactly one edge forces the direct route of weight 4.
Example 3:
Input: edges = [(0,1,2)], source = 0, sink = 2, k = 3
Output: empty
No 3-edge walk reaches the sink.
Constraints
0 <= k- Edge weights may be any
double. - Walks may repeat vertices and edges.
Problem 21: Second-Best Minimum Spanning Tree
Description
Given an undirected WeightedGraph, compute the weight of the second-best
spanning tree: the minimum weight over all spanning trees that differ from a
minimum spanning tree in at least one edge. Build an MST, then for each non-tree
edge consider swapping it in and removing the heaviest tree edge on the cycle it
creates; the smallest resulting increase gives the second-best total. Assume the
graph is connected with at least two distinct spanning trees.
Examples
Example 1:
Input: edges = [(0,1,1), (1,2,2), (0,2,3)]
Output: 4.0
The MST {(0,1),(1,2)} weighs 3; the cheapest single swap (drop (1,2), add (0,2)) gives 1 + 3 = 4.
Example 2:
Input: edges = [(0,1,1), (1,2,1), (0,2,1)]
Output: 2.0
Every spanning tree weighs 2, so the second-best also weighs 2.
Example 3:
Input: edges = [(0,1,1), (1,2,2), (2,3,3), (0,3,4)]
Output: 7.0
The MST weighs 6; the cheapest swap raises it to 7.
Constraints
- The graph is connected and undirected.
- At least two distinct spanning trees exist.
Problem 22: Aqueduct Throughput (Maximum Flow)
Description
An aqueduct routes water from spring source to cistern sink over n
junctions 0..n-1. Directed pipes pipes[i] = {u, v, c} each carry up to c
litres per second from u to v; water is conserved at every junction except
the source and sink. Return the maximum sustained throughput from source to
sink. Build a residual network and run a max-flow algorithm (Edmonds-Karp:
repeatedly find an augmenting path by BFS and push its bottleneck until none
remain).
Examples
Example 1:
Input: n = 4, pipes = [[0,1,3],[0,2,2],[1,2,1],[1,3,2],[2,3,3]], source = 0, sink = 3
Output: 4
Two augmenting paths saturate the cut into the sink at total throughput 4.
Example 2:
Input: n = 3, pipes = [[0,1,7],[1,2,4]], source = 0, sink = 2
Output: 4
The narrower pipe (capacity 4) limits the series throughput.
Example 3:
Input: n = 2, pipes = [[0,1,5],[0,1,3]], source = 0, sink = 1
Output: 8
Two parallel pipes add their capacities for total throughput 8.
Constraints
2 <= n <= 5001 <= c <= 10^4- Total capacity fits in an
int.
Problem 23: Evacuation Choke Point (Minimum Cut)
Description
A coastal town has n intersections 0..n-1 and undirected roads
roads[i] = {u, v, c} with per-hour vehicle capacity c. During an evacuation,
vehicles flow from town center source to the on-ramp sink. Return the minimum
total capacity that, if removed, would cut source off from sink. By the
max-flow / min-cut theorem this equals the maximum evacuation throughput. Model
each undirected road as two opposing directed arcs of capacity c.
Examples
Example 1:
Input: n = 4, roads = [[0,1,3],[1,3,3],[0,2,2],[2,3,2]], source = 0, sink = 3
Output: 5
The two disjoint routes carry 3 and 2, so cutting capacity 5 separates source from sink.
Example 2:
Input: n = 3, roads = [[0,1,4],[1,2,4]], source = 0, sink = 2
Output: 4
Cutting either road (capacity 4) breaks the only path.
Example 3:
Input: n = 4, roads = [[0,1,5]], source = 0, sink = 3
Output: 0
The sink is already disconnected; no capacity need be removed.
Constraints
2 <= n <= 5001 <= c <= 10^4
Problem 24: Vertex-Capacitated Max Flow
Description
Given a directed flow network on n vertices 0..n-1 with edge capacities
edges[i] = {u, v, c} and a per-vertex capacity vertexCap[v] limiting the
total flow that may pass through vertex v (the source and sink are
uncapacitated), compute the maximum flow from source to sink. Use the
vertex-splitting trick: replace each vertex v with an in-node and an out-node
joined by an edge of capacity vertexCap[v], then run ordinary max flow.
Examples
Example 1:
Input: n = 3, edges = [[0,1,10],[1,2,10]], vertexCap = [99,4,99], source = 0, sink = 2
Output: 4
Vertex 1 throttles the series flow to 4 despite the wide edges.
Example 2:
Input: n = 4, edges = [[0,1,5],[0,2,5],[1,3,5],[2,3,5]], vertexCap = [99,3,3,99], source = 0, sink = 3
Output: 6
Two paths each capped at 3 by their middle vertex yield 6.
Example 3:
Input: n = 2, edges = [[0,1,7]], vertexCap = [99,99], source = 0, sink = 1
Output: 7
With uncapped endpoints the single edge passes its full capacity.
Constraints
2 <= n <= 2001 <= c <= 10^41 <= vertexCap[v] <= 10^4
Problem 25: Minimum-Cost Maximum Flow
Description
Given a directed network on n vertices 0..n-1 where each edge
edges[i] = {u, v, cap, cost} has a capacity and a per-unit-flow cost, compute
the minimum total cost among all flows that achieve the maximum flow value
from source to sink. Repeatedly send flow along the cheapest augmenting path
(shortest path by cost in the residual graph, e.g. via Bellman-Ford / SPFA),
saturating its bottleneck, until no augmenting path remains.
Examples
Example 1:
Input: n = 4, edges = [[0,1,3,1],[0,2,2,2],[1,3,2,1],[2,3,3,1],[1,2,1,1]],
source = 0, sink = 3
Output: 9
The maximum flow is 4; routing it along the cheapest paths costs 9 in total.
Example 2:
Input: n = 3, edges = [[0,1,5,2],[1,2,5,3]], source = 0, sink = 2
Output: 25
All 5 units travel the only route at unit cost 2 + 3 = 5, for 25 total.
Example 3:
Input: n = 3, edges = [[0,1,4,1],[0,2,4,5],[1,2,4,1]], source = 0, sink = 2
Output: 8
Four units along 0 → 1 → 2 (unit cost 2) beat the costly direct edge.
Constraints
2 <= n <= 2001 <= cap <= 10^40 <= cost <= 10^4
Problem 26: Reconnecting The Country After Road Closures
LeetCode: 1489. Find Critical and Pseudo-Critical Edges in Minimum Spanning Tree
Description
You have n cities 0..n-1 and a list of weighted undirected edges
edges[i] = {u, v, w} (edge i is identified by its index). An edge is
critical if removing it increases the weight of any minimum spanning tree (or
disconnects the graph); it is pseudo-critical if it appears in some MST but
not all. Return the two index lists {critical, pseudoCritical}. Compare MST
weights with each edge force-excluded and force-included against the baseline MST
weight.
Examples
Example 1:
Input: n = 5, edges = [[0,1,1],[1,2,1],[2,3,2],[0,3,2],[0,4,3],[3,4,3],[1,4,6]]
Output: critical = [0,1], pseudoCritical = [2,3,4,5]
Edges 0 and 1 are in every MST; edges 2–5 appear in some but not all.
Example 2:
Input: n = 4, edges = [[0,1,1],[1,2,1],[2,3,1],[0,3,1]]
Output: critical = [], pseudoCritical = [0,1,2,3]
All four equal-weight edges are interchangeable, so none is critical.
Example 3:
Input: n = 3, edges = [[0,1,1],[1,2,2]]
Output: critical = [0,1], pseudoCritical = []
Both edges are needed to span three vertices, so both are critical.
Constraints
2 <= n <= 1001 <= edges.length <= min(200, n * (n - 1) / 2)1 <= w <= 1000
Problem 27: Distribute Water With Optimal Supply
LeetCode: 1168. Optimize Water Distribution in a Village
Description
There are n houses 1..n. Supplying water to a house costs either wells[i]
(building a well at house i+1) or the cost of a pipe pipes[j] = {u, v, w}
connecting it to another supplied house. Return the minimum total cost to supply
water to every house. The classic trick adds a virtual node 0 connected to each
house i with an edge of weight wells[i-1], turning the problem into a single
minimum spanning tree.
Examples
Example 1:
Input: n = 3, wells = [1,2,2], pipes = [[1,2,1],[2,3,1]]
Output: 3
Build a well at house 1 (cost 1) and lay pipes 1–2 and 2–3 (cost 1 each).
Example 2:
Input: n = 2, wells = [1,1], pipes = [[1,2,1]]
Output: 2
Two wells (cost 1 each) beat one well plus the pipe.
Example 3:
Input: n = 1, wells = [5], pipes = []
Output: 5
The only option is to build the single well.
Constraints
1 <= n <= 10^4wells.length == n0 <= pipes.length <= 10^41 <= wells[i], w <= 10^5
Problem 28: Number Of Ways To Arrive At Destination
LeetCode: 1976. Number of Ways to Arrive at Destination
Description
A city has n intersections 0..n-1 joined by bidirectional roads
roads[i] = {u, v, time}. Starting at intersection 0, return the number of
different shortest-time paths that arrive at intersection n-1. Because the
count can be large, return it modulo 10^9 + 7. Run Dijkstra and, alongside the
shortest distances, accumulate path counts: when a strictly shorter route to a
node is found, reset its count; when an equally short route is found, add to it.
Examples
Example 1:
Input: n = 7, roads = [[0,6,7],[0,1,2],[1,2,3],[1,3,3],[6,3,3],[3,5,1],[6,5,1],[2,5,1],[0,4,5],[4,6,2]]
Output: 4
The shortest time from 0 to 6 is 7, reachable by 4 distinct paths.
Example 2:
Input: n = 2, roads = [[0,1,1]]
Output: 1
A single road gives exactly one shortest path.
Example 3:
Input: n = 3, roads = [[0,1,1],[1,2,1],[0,2,2]]
Output: 2
Both 0 → 2 directly and 0 → 1 → 2 take time 2, so there are 2 shortest paths.
Constraints
1 <= n <= 2001 <= roads.length <= n * (n - 1) / 21 <= time <= 10^9
Hash Functions
What It Is & Why It Matters
What job does a hash function do in a hash table — turning an arbitrary key into a slot index — and why does its quality single-handedly decide whether lookups stay fast?
Properties Of A Good Hash Function
Which traits do you want: determinism, fast computation, and spreading keys evenly across slots — and why does each one matter for performance?
Determinism & Speed
Why must the same key always hash to the same slot, and why does a slow hash defeat the whole point of \( O(1) \) access?
Uniformity (Avoiding Hot Slots)
Why do you want keys scattered so no single slot gets overloaded, and what happens to operation cost when the function clumps keys together?
Avalanche: Small Key Change, Big Hash Change
Why is it desirable that flipping one bit of the key scrambles the whole hash, and how does poor avalanche let near-identical keys collide?
From Hash Code To Index
Why do you separate “compute an integer hash code from the key” from “fold that integer into \( [0, m) \)”, and how does each stage have its own pitfalls?
Handling Negative Or Overflowed Codes
Why can a raw integer hash code be negative or wrap around, and how must you sanitize it before using it as an array index?
The Division Method
How does hash mod m map a key into the table, and why is the choice of \( m \) (favor a prime, avoid powers of two) so important here?
Why Powers Of Two Are Dangerous
Why does a power-of-two modulus effectively keep only the low bits of the hash, and what kind of key patterns does that expose?
The Multiplication Method
How does multiplying by a constant fraction and taking the fractional part spread keys, and why is it more forgiving about the value of \( m \)?
Hashing Non-Integer Keys
How would you turn a string, tuple, or object into an integer first — e.g. polynomial rolling for strings — and why must equal keys always produce equal codes?
The hashCode / equals Contract
Why must any two objects that are equals produce the same hash code, and what subtle bug appears if you override one without the other?
The Simple Uniform Hashing Assumption
What idealization (every key equally likely to land in any slot, independently) do average-case analyses lean on, and when does real-world structured data violate it?
Time Complexity
Why is a hash function’s own runtime — not just the table’s — part of every operation’s cost, and what determines it?
Cost Of Computing The Hash
Why is hashing a fixed-width integer \( O(1) \) but hashing a string \( O(L) \) in its length \( L \), and how does that feed into overall lookup cost?
How The Hash Shapes Table Operation Cost
Why does a good hash give expected \( O(1) \) per table operation while a poor one collapses toward \( O(n) \) by overloading a few slots?
Space Complexity
Why does a hash function itself use \( O(1) \) space (just a few constants or a seed), independent of how many keys you hash, and where does the actual storage cost live instead?
Trade-offs
Why might you accept a slower but stronger hash (better spread, avalanche, adversary-resistance) versus a fast weak one, and what governs that choice?
Pitfalls
Where do hash functions go wrong — using a power-of-two modulus that throws away high bits, ignoring part of the key, forgetting to handle negatives, or letting structured input create patterns?
Implementation Walkthrough
Before writing code, plan the pieces below — each prompt tells you what to work out, not the answer.
Computing An Integer Hash Code
How do you derive a well-mixed integer from the key type you care about, and how do you fold a multi-field or string key in?
Reducing To A Slot Index
How do you map the (possibly negative or oversized) code into \( [0, m) \) safely, and which modulus choice are you committing to?
A Polynomial String Hash
How does a rolling Horner-style loop combine each character with a multiplier to hash a string, and what base do you pick?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Chaining
What It Is & When To Use It
How does separate chaining resolve collisions by letting each slot hold a whole list of entries, and why is it the default for tables that must handle high load or frequent deletes?
The Bucket-Of-Lists Picture
Picture the table as an array where each cell points to a linked list — where does a colliding key go, and why can a single slot hold any number of entries?
Representation
What does each node in a chain store (key, value, next pointer), and how does the table array reference the head of each list?
Why The List, Not The Array, Owns Collisions
Why does pushing collisions into per-bucket lists let the table array stay fixed-width, and how does that decouple capacity from collision handling?
Coding insert
How do you hash to a bucket, then add the entry — and why must you first scan the chain to update an existing key rather than blindly duplicate it?
Insert At Head vs. Tail
Why is head-insertion \( O(1) \) but risks reordering, and when does the existing-key scan dominate the cost anyway?
Coding lookup
How do you hash to a bucket and walk its list comparing keys with equals, and what do you return when the chain runs out without a match?
Coding delete
Why is deletion clean with chaining — just unlink the node by fixing its predecessor’s pointer — compared to the tombstone gymnastics open addressing needs?
Load Factor \( \alpha \)
Define \( \alpha = n/m \) (entries over slots) and explain why it equals the average chain length and thus directly predicts search cost.
Why \( \alpha > 1 \) Is Allowed
Why can chaining keep working with more entries than slots, unlike open addressing — what is the cost of letting \( \alpha \) grow past 1?
Choosing The Bucket Structure
When might a balanced tree or dynamic array beat a linked list per bucket, and why does Java’s HashMap convert a bucket to a tree past a threshold?
Time Complexity
Why does every chaining operation cost “hash the key, then scan one bucket”, making expected cost a function of \( \alpha \) and worst case a function of the longest chain?
insert / lookup / delete — Average Case
Why are all three \( O(1 + \alpha) \) on average with a good hash, and which part is the constant and which part is the chain walk?
Worst Case — Everything In One Chain
What adversarial or degenerate input collapses every key into one bucket, and why does that make all operations \( O(n) \)?
Effect Of Resizing On Amortized Cost
Why does keeping \( \alpha \) bounded by periodic resizing hold the expected per-operation cost at \( O(1) \) amortized?
Space Complexity
Why does chaining use \( O(n + m) \) space — the table array of \( m \) slots plus one node per entry — and why does each node carry pointer overhead that open addressing avoids?
The Pointer-Overhead Tax
Why does every chained entry cost extra memory for its next reference, and how does that add up versus a flat probed table?
Trade-offs vs. Open Addressing
Why does chaining tolerate \( \alpha > 1 \) and trivial deletes, but pay in pointer overhead and worse cache locality than a contiguous probed table?
Pitfalls
Where do bugs hide — forgetting to check for an existing key on insert, comparing with == instead of equals, losing the predecessor pointer on delete, or letting \( \alpha \) grow unbounded?
Implementation Walkthrough
Before writing code, plan the pieces below — each prompt tells you what to work out, not the answer.
The Bucket Array And Node Type
How do you declare the array of list heads and the node holding key, value, and next?
Hash-To-Bucket Then Scan
How does each operation share the same first two steps — index into the array, then walk that one chain?
Insert With Update-Or-Add
How do you distinguish “key already present, overwrite value” from “new key, prepend node”?
Delete By Unlinking
How do you track the predecessor as you scan so you can splice a node out of the chain?
Triggering A Resize
Where do you check \( \alpha \) after an insert and hand off to a rehash routine?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Open Addressing
What It Is & When To Use It
How does open addressing store every entry directly in the table array (no external lists), and why is its cache-friendliness attractive when load stays moderate?
Storing Everything In The Table
Contrast with chaining: when a slot is taken, where does the colliding key go, and why does this design cap the table at \( \alpha \le 1 \)?
Probe Sequences: The Core Idea
Why does each key define a sequence of slots to try in order, and why must lookup follow the exact same sequence insert used or risk missing a present key?
The Three Slot States
Why do you need to distinguish empty, occupied, and deleted slots, and how does each state steer a probe differently?
Linear Probing
Write the probe formula that steps one slot at a time — what makes it simple and cache-friendly, and what clustering problem does it invite?
Quadratic Probing
Write the probe formula with a quadratic offset — how does it scatter probes farther apart, and why must table size and constants be chosen so it can visit enough slots?
Double Hashing
Show the probe formula using a second hash for the step size — why does giving each key its own stride spread probes best of the three schemes?
Why The Second Hash Must Be Nonzero And Coprime
Why does a step size of zero or one sharing a factor with \( m \) fail to cover the table, and how do you guarantee the stride is safe?
Primary & Secondary Clustering
Describe primary clustering (long contiguous runs from linear probing) versus secondary clustering (keys with the same start sharing a probe path), and which scheme causes each?
Coding Insert, Lookup & The Empty Marker
How does insert walk the probe sequence to the first usable slot, how does lookup stop only at a truly empty slot, and why does that empty marker decide correctness of the whole search?
Tombstones On Delete
Why can’t you just blank a slot on delete (it would sever probe chains and hide later keys), and how does a “deleted” tombstone let lookups keep probing while inserts may reuse the slot?
Why Tombstones Accumulate And Hurt
Why do tombstones count as “occupied” for search length even though they hold no key, and why does that force periodic rehashing?
Time Complexity
Why does open addressing’s cost hinge entirely on the expected number of probes, and why does that number depend so sharply on \( \alpha \)?
insert / lookup / delete — Expected Probes
Why does expected probe count rise roughly like \( \frac{1}{1-\alpha} \) (state it, don’t derive), so all three operations stay \( O(1) \) at moderate load but balloon near full?
Worst Case
Why can a single operation degrade to \( O(n) \) when clustering or a near-full table forces a probe of most of the array?
How Tombstones Inflate Effective Load
Why does a table littered with tombstones behave like a fuller table for timing purposes, lengthening every probe?
Space Complexity
Why is open addressing’s space just the single array of \( m \) slots — \( O(m) \), with no per-entry pointers — and why does that compactness improve cache behavior over chaining?
Why \( \alpha \le 1 \) Bounds Storage
Why can the number of entries never exceed the slot count, and how does that ceiling shape your resize policy?
Trade-offs vs. Chaining
Why does open addressing win on memory and cache locality but lose on tolerance for high load, deletes, and clustering?
Pitfalls
Where do bugs live — stopping a lookup at a tombstone instead of an empty, forgetting tombstones inflate effective load, a quadratic step that never reaches some slots, or a zero-stride double hash?
Implementation Walkthrough
Before writing code, plan the pieces below — each prompt tells you what to work out, not the answer.
The Slot Array And State Tracking
How do you represent occupied, empty, and deleted in parallel arrays or a small enum per slot?
The Probe Loop
How does one loop generate successive indices from your chosen scheme and stop at the right state for insert vs. lookup?
Insert Reusing Tombstones
How does insert remember the first tombstone it passed so it can place the key there if the key isn’t found later?
Delete By Tombstoning
How does delete find the key, then mark its slot deleted rather than empty?
Resize And Drop Tombstones
How does rehashing into a fresh array naturally discard all tombstones and reset effective load?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Amortized Resizing
What It Is & Why It Matters
Why must a hash table change its slot count as it fills and empties, and how does occasional expensive resizing keep every operation cheap on average?
Why Tables Must Grow
What goes wrong as the load factor \( \alpha \) drifts too high — longer chains or more probes — and why does that erode the \( O(1) \) promise?
Why Tables Sometimes Shrink
Why is a mostly-empty table wasteful of memory, and when is it worth halving capacity to reclaim space?
Table Doubling
State the rule: when \( \alpha \) crosses a threshold, double the slot count — and why does multiplying capacity (not adding a fixed amount) matter for the amortized argument?
Why Growing By A Constant Fails
Why does adding a fixed number of slots each time lead to \( O(n) \) amortized insertion, and how does geometric growth fix it?
The Cost Of One Resize
Why does a single resize cost \( O(n) \) — you must allocate a new array and rehash every existing entry into it — and why can’t old indices simply be copied over?
Why Rehashing Is Unavoidable
Since the slot index depends on the table size, why does every key need a freshly computed index in the larger table rather than a position-preserving copy?
Time Complexity
Why must you reason about resizing over a whole sequence of operations, since any single insert that triggers a resize looks \( O(n) \) in isolation?
A Single Insert — Worst Case
Why does the one insert that crosses the threshold pay the full \( O(n) \) rehash, and why is that spike unavoidable?
Amortized \( O(1) \) Insert (Intuition Only)
Why does an \( O(n) \) resize that happens only after about \( n \) cheap inserts spread out to a tiny constant per insert — and why does that make the average, not the spike, the right cost to quote?
The Pre-Paying / Credit Picture
Imagine each insert sets aside a little extra “credit” that later funds the next doubling — why does this bookkeeping idea explain the constant average without a formal proof?
Amortized-Analysis Lenses
How would the aggregate, accounting, and potential methods each frame this same result, and why do all three land on the same constant average?
Space Complexity
Why does the table use \( O(m) \) space that stays within a constant factor of \( n \) under doubling/halving, and why does a resize briefly need both old and new arrays at once — a transient \( O(n) \) spike?
Wasted Space Between Resizes
Why does geometric growth mean the table is at most a constant factor larger than the live entries, bounding wasted space?
Avoiding Thrashing
Why should the shrink threshold sit well below the grow threshold (not symmetric), and what repeated grow-shrink-grow disaster does that hysteresis gap prevent?
Trade-offs
Why pick a larger growth factor (fewer resizes, more wasted space) versus a smaller one (tighter memory, more frequent rehashes), and what governs the choice?
Pitfalls
Where do mistakes happen — growing by a constant instead of a factor, symmetric grow/shrink thresholds that thrash, forgetting tombstones count toward load in open addressing, or rehashing into stale indices?
Implementation Walkthrough
Before writing code, plan the pieces below — each prompt tells you what to work out, not the answer.
The Load-Factor Check
Where in insert (and delete) do you compute \( \alpha \) and decide whether to resize?
Allocating The New Table
How do you choose the new capacity and create the fresh backing array?
Rehashing Every Entry
How do you walk the old table and re-insert each live entry into the new one using the new size?
The Hysteresis Thresholds
What two different thresholds do you pick for growing vs. shrinking, and why the gap?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Universal Hashing
What It Is & Why It Matters
What does picking a hash function at random from a carefully designed family guarantee that any single fixed function cannot, and why does that guarantee survive adversarial input?
The Adversary Problem
Why can an attacker who knows your one fixed hash function feed inputs that all collide into a single slot, turning expected \( O(1) \) into \( O(n) \)?
Where This Bites In Practice
Why is this a real security concern (hash-flooding denial-of-service against web servers and language runtimes), not just a theoretical worry?
The Core Idea: Randomize The Function, Not The Data
Why does choosing \( h \) randomly at table-creation time mean no fixed input can be “bad” in advance, since the adversary can’t predict which \( h \) you’ll use?
Randomness At Setup, Not Per Operation
Why is the random choice made once when the table is built (and held fixed after), rather than re-rolled on every lookup, and why is that enough?
Definition Of A Universal Family
State the defining guarantee: for any two distinct keys, the probability they collide over a random choice of \( h \) is at most \( 1/m \) — and why is that exactly “as good as truly random”?
Why \( 1/m \) Is The Right Target
Why is \( 1/m \) the collision probability you’d get from idealized uniform hashing, so matching it means you lose nothing to adversaries?
Constructing A Universal Family
Sketch the \( ((a k + b) \bmod p) \bmod m \) construction — what are the prime \( p \), the multiplier \( a \), and the offset \( b \), and how are they chosen so the family is provably universal?
The Role Of The Prime \( p \)
Why must \( p \) exceed the key universe and be prime, and what does the inner mod-\( p \) step accomplish before the outer mod-\( m \)?
Expected Collisions From The Bound
How does the \( 1/m \) per-pair collision bound translate into an expected chain length of about \( \alpha \), restoring the average-case guarantee for chaining even on worst-case keys?
Stronger Independence & \( k \)-Wise Variants
What does \( k \)-independence (any \( k \) keys behave independently) buy beyond plain universality, and which applications need it?
Time Complexity
Why does universal hashing keep the same expected per-operation cost as ordinary hashing while making that expectation hold for every input — at the price of a little more arithmetic per hash?
Expected Operation Cost Over Random h
Why is each operation expected \( O(1) \) where the expectation is now taken over the random choice of \( h \), so it holds regardless of which keys arrive?
Cost Of Evaluating The Hash
Why does the \( ((ak+b) \bmod p) \bmod m \) evaluation stay \( O(1) \) per call, just with a couple more multiplies and mods than a naive hash?
Space Complexity
Why does a universal hash need only \( O(1) \) extra storage — the constants \( a \), \( b \), \( p \) (and seed) — independent of the number of keys, so it adds nothing asymptotic to the table?
Trade-offs
Why is the extra arithmetic and the need for randomness at setup worth it for adversarial or untrusted input, but possibly overkill for trusted, fixed data?
Pitfalls
Where do people slip — reusing the same seed across every table, picking a non-prime \( p \) or one too small, or assuming universality removes worst-case collisions entirely rather than just making them unlikely?
Implementation Walkthrough
Before writing code, plan the pieces below — each prompt tells you what to work out, not the answer.
Choosing The Parameters
How do you pick the prime \( p \), and how do you draw random \( a \) and \( b \) in their valid ranges at construction time?
Evaluating The Function
How does the hash method apply \( ((a k + b) \bmod p) \bmod m \) while guarding against overflow on the multiply?
Re-Seeding On Resize
Why might you draw fresh \( a, b \) when the table resizes, and what does that protect against?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Bloom Filters
What It Is & When To Use It
What question does a Bloom filter answer — probabilistic set membership in tiny space — and when is trading certainty for memory the right call?
Probabilistic Membership
Why does a Bloom filter only ever say “definitely not present” or “possibly present,” and why does it store no keys at all, only bits?
Representation: Bit Array + \( k \) Hashes
How is the whole structure just a bit array of \( m \) bits plus \( k \) independent hash functions, and why does that make it so compact compared to a real set?
Why \( k \) Independent Hashes
Why does each key get mapped to \( k \) different bit positions instead of just one, and what does that buy versus a single hash?
Coding insert
How does adding a key set the \( k \) bits at its hashed positions, and why are bits only ever turned on, never off?
Coding query
How does a membership test check whether all \( k \) bits are set — why does one clear bit prove absence, while all-set bits only suggest presence?
False Positives, Never False Negatives
Explain why “possibly present” can lie (other inserted keys’ bits happened to cover all \( k \) positions) but “absent” can never be wrong — and why that asymmetry defines the structure?
Why The False-Positive Rate Climbs As You Fill It
Why does each insert that sets more bits raise the chance that a never-inserted key finds all its bits already on, and why does an over-full filter approach uselessness?
Why Deletion Doesn’t Work
Why can’t you clear bits to remove a key (you’d corrupt other keys that share those bits), and how do counting Bloom filters work around it?
Tuning \( m \), \( k \), And \( n \)
How do the bit-array size \( m \), number of hashes \( k \), and expected element count \( n \) trade off, and why is there an optimal \( k \) that minimizes false positives for a given size budget?
Too Few vs. Too Many Hashes
Why does using too few hashes under-use the available bits while too many fills the array too fast — both raising the false-positive rate?
Time Complexity
Why is a Bloom filter’s speed governed entirely by \( k \), the number of hashes, and not by how many elements it already holds?
insert / query — Cost In Terms Of \( k \)
Why are both insert and query \( O(k) \) — independent of \( n \) — and why does that make a Bloom filter’s timing flat as it fills?
Why There’s No Resizing Story
Why can’t a standard Bloom filter grow and rehash like a hash table, and what must you do instead if you under-sized it?
Space Complexity
Why does a Bloom filter use only \( O(m) \) bits — often a handful of bits per element — and why is that dramatically smaller than storing the keys themselves?
Bits Per Element, Not Bytes Per Key
Why is the space budget naturally expressed as bits-per-element, and how does that beat any structure that must retain the actual keys?
Real Uses & Trade-offs
Where do Bloom filters shine — a cheap in-memory pre-check before an expensive disk or network lookup (databases, caches, web crawlers, CDNs) — and what certainty do you give up for the space saving?
Pitfalls
Where do people go wrong — attempting deletion on a plain filter, undersizing \( m \) for the real element count, reusing correlated hashes, or treating “possibly present” as a definite yes?
Implementation Walkthrough
Before writing code, plan the pieces below — each prompt tells you what to work out, not the answer.
The Bit Array
How do you store \( m \) bits compactly (e.g. a long array with bit indexing) and set/test an individual bit?
Deriving \( k \) Positions From A Key
How can you generate \( k \) distinct bit positions per key, perhaps by combining two base hashes instead of maintaining \( k \) separate functions?
Insert: Set All \( k \) Bits
How does insert loop over the \( k \) positions turning each bit on?
Query: Test All \( k \) Bits
How does query short-circuit to “absent” the moment it finds one clear bit?
Sizing From A Target False-Positive Rate
Given an expected \( n \) and a desired error rate, how would you back out reasonable \( m \) and \( k \) at construction?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Hash Tables: Problem Set
Master the hash-map toolkit — frequency counting, deduplication, grouping by canonical key, O(1) lookup, and prefix-sum maps. Foundational problems build the moves you should write cold; Applied Problems weave interview-style (LeetCode) and competitive-programming-style (Codeforces / Advent of Code / Codyssi) challenges, roughly easy to hard.
Foundational
Problem 1: Two Sum
LeetCode: 1. Two Sum
Description
Given an int[] array nums and an int target, return the indices [i, j] (i < j) of the two distinct elements that sum to target. Exactly one solution exists. Use a hash map from value to index so each element is visited once: for each x, check whether target - x was already seen.
Examples
Example 1:
Input: nums = [2, 7, 11, 15], target = 9
Output: [0, 1]
nums[0] + nums[1] = 2 + 7 = 9.
Example 2:
Input: nums = [3, 2, 4], target = 6
Output: [1, 2]
Example 3:
Input: nums = [3, 3], target = 6
Output: [0, 1]
Constraints
2 <= n <= 10^4- Exactly one valid answer exists.
- Target O(n) time.
Problem 2: Character Frequency Count
Description
Given a lowercase string s, return a Map<Character, Integer> mapping each distinct character to the number of times it appears in s. This is the canonical frequency-count move that underpins anagram and counting problems. Characters that do not appear are absent from the map.
Examples
Example 1:
Input: s = "banana"
Output: {b=1, a=3, n=2}
Example 2:
Input: s = "aabb"
Output: {a=2, b=2}
Example 3:
Input: s = ""
Output: {}
Constraints
0 <= s.length() <= 10^5sconsists of lowercase English letters.
Problem 3: Deduplicate Preserving Order
Description
Given an int[] array, return a new array with duplicates removed, keeping the first occurrence order of each distinct value. Use a hash set to track which values have already been emitted.
Examples
Example 1:
Input: [3, 1, 3, 4, 1, 5]
Output: [3, 1, 4, 5]
Example 2:
Input: [7, 7, 7]
Output: [7]
Example 3:
Input: []
Output: []
Constraints
0 <= n <= 10^5- Preserve first-seen order.
Problem 4: Contains Duplicate
LeetCode: 217. Contains Duplicate
Description
Given an int[] array nums, return true if any value appears at least twice, and false if every element is distinct. Add values to a hash set during a single scan and report a collision the moment one is found.
Examples
Example 1:
Input: [1, 2, 3, 1]
Output: true
1 repeats.
Example 2:
Input: [1, 2, 3, 4]
Output: false
Example 3:
Input: [1, 1, 1, 3, 3, 4, 3, 2, 4, 2]
Output: true
Constraints
1 <= n <= 10^5- Target O(n) time.
Problem 5: Most Frequent Element
Description
Given a non-empty int[] array, return the value that occurs most often. Break ties by returning the smallest such value. Tally counts in a hash map, then scan the entries for the max count (smallest value on ties).
Examples
Example 1:
Input: [1, 3, 3, 2, 2, 2]
Output: 2
2 appears 3 times.
Example 2:
Input: [4, 4, 5, 5]
Output: 4
Tie between 4 and 5; return the smaller.
Example 3:
Input: [9]
Output: 9
Constraints
1 <= n <= 10^5- Return smallest value on a tie.
Problem 6: Group By Parity Key
Description
Given an int[] array, partition the values into groups keyed by a simple canonical function — here, remainder modulo 3 (0, 1, or 2, using the non-negative remainder). Return a Map<Integer, List<Integer>> from each key to the list of values that map to it, in their original order. This drills the grouping-by-key pattern that generalizes to anagrams and signatures.
Examples
Example 1:
Input: [1, 2, 3, 4, 5, 6]
Output: {0=[3, 6], 1=[1, 4], 2=[2, 5]}
Example 2:
Input: [3, 6, 9]
Output: {0=[3, 6, 9]}
Example 3:
Input: []
Output: {}
Constraints
0 <= n <= 10^5- Use the non-negative remainder modulo 3.
Applied Problems
Problem 7: Valid Anagram
LeetCode: 242. Valid Anagram
Description
Given two strings s and t, return true if t is an anagram of s — the same characters with the same multiplicities. Count characters of s in a hash map, decrement for each character of t, and confirm every count lands at zero (or compare two frequency maps).
Examples
Example 1:
Input: s = "anagram", t = "nagaram"
Output: true
Example 2:
Input: s = "rat", t = "car"
Output: false
Example 3:
Input: s = "ab", t = "a"
Output: false
Different lengths cannot be anagrams.
Constraints
1 <= s.length(), t.length() <= 5 * 10^4sandtconsist of lowercase English letters.
Problem 8: First Unique Character
LeetCode: 387. First Unique Character in a String
Description
Given a string s, return the index of the first character that appears exactly once. If no such character exists, return -1. Build a frequency map in one pass, then scan left to right for the first character with count 1.
Examples
Example 1:
Input: s = "leetcode"
Output: 0
'l' is the first non-repeating character.
Example 2:
Input: s = "loveleetcode"
Output: 2
'v' is the first non-repeating character.
Example 3:
Input: s = "aabb"
Output: -1
Constraints
1 <= s.length() <= 10^5sconsists of lowercase English letters.
Problem 9: Intersection of Two Arrays
LeetCode: 349. Intersection of Two Arrays
Description
Given two int[] arrays a and b, return their distinct shared values in ascending order. Put one array’s values into a hash set, test membership while scanning the other, and collect each match once.
Examples
Example 1:
Input: a = [1, 2, 2, 1], b = [2, 2]
Output: [2]
Example 2:
Input: a = [4, 9, 5], b = [9, 4, 9, 8, 4]
Output: [4, 9]
Example 3:
Input: a = [1, 2, 3], b = [4, 5, 6]
Output: []
Constraints
0 <= a.length, b.length <= 10^4- Result must be sorted ascending and contain no duplicates.
Problem 10: Ransom Note
LeetCode: 383. Ransom Note
Description
Given two strings ransomNote and magazine, return true if ransomNote can be constructed using the letters of magazine, where each letter of magazine may be used at most once. Count available letters in a hash map, then spend them as you scan the note.
Examples
Example 1:
Input: ransomNote = "a", magazine = "b"
Output: false
Example 2:
Input: ransomNote = "aa", magazine = "ab"
Output: false
Only one 'a' is available.
Example 3:
Input: ransomNote = "aa", magazine = "aab"
Output: true
Constraints
1 <= ransomNote.length(), magazine.length() <= 10^5- Both consist of lowercase English letters.
Problem 11: Jewels and Stones
LeetCode: 771. Jewels and Stones
Description
Given a string jewels (each character is a distinct jewel type) and a string stones, return how many characters in stones are jewels. Load jewels into a hash set for O(1) membership, then count matching stones in one pass.
Examples
Example 1:
Input: jewels = "aA", stones = "aAAbbbb"
Output: 3
Example 2:
Input: jewels = "z", stones = "ZZ"
Output: 0
Matching is case-sensitive.
Example 3:
Input: jewels = "abc", stones = "aabbcc"
Output: 6
Constraints
1 <= jewels.length(), stones.length() <= 50- All characters are letters; characters in
jewelsare distinct.
Problem 12: Contains Duplicate II
LeetCode: 219. Contains Duplicate II
Description
Given an int[] array nums and an integer k, return true if there exist two indices i and j such that nums[i] == nums[j] and |i - j| <= k. Track the last index seen for each value in a hash map; when a value recurs, check the index gap.
Examples
Example 1:
Input: nums = [1, 2, 3, 1], k = 3
Output: true
The two 1s are 3 apart.
Example 2:
Input: nums = [1, 0, 1, 1], k = 1
Output: true
Example 3:
Input: nums = [1, 2, 3, 1, 2, 3], k = 2
Output: false
Constraints
1 <= n <= 10^50 <= k <= 10^5
Problem 13: Group Anagrams
LeetCode: 49. Group Anagrams
Description
Given an array of strings words, group together those that are anagrams of one another. Return the number of distinct anagram groups. Map each word to a canonical key (its sorted characters, or a 26-letter count signature) and count distinct keys.
Examples
Example 1:
Input: ["eat", "tea", "tan", "ate", "nat", "bat"]
Output: 3
Groups: {eat, tea, ate}, {tan, nat}, {bat}.
Example 2:
Input: [""]
Output: 1
Example 3:
Input: ["a", "b", "c"]
Output: 3
Constraints
1 <= words.length <= 10^4- Words consist of lowercase English letters.
Problem 14: Top K Frequent Elements
LeetCode: 347. Top K Frequent Elements
Description
Given an int[] array nums and an integer k, return the k most frequent values in descending order of frequency. Break frequency ties by smaller value first. Tally counts in a hash map, then select the top k by frequency.
Examples
Example 1:
Input: nums = [1, 1, 1, 2, 2, 3], k = 2
Output: [1, 2]
Example 2:
Input: nums = [1], k = 1
Output: [1]
Example 3:
Input: nums = [4, 4, 5, 5, 6], k = 2
Output: [4, 5]
4 and 5 both appear twice; ties broken by smaller value.
Constraints
1 <= n <= 10^51 <= k <=number of distinct values.
Problem 15: Subarray Sum Equals K
LeetCode: 560. Subarray Sum Equals K
Description
Given an int[] array nums and an integer k, return the number of contiguous subarrays whose elements sum to k. Maintain a running prefix sum and a hash map of how many times each prefix-sum value has occurred; for each position add the count of earlier prefixes equal to prefix - k.
Examples
Example 1:
Input: nums = [1, 1, 1], k = 2
Output: 2
Example 2:
Input: nums = [1, 2, 3], k = 3
Output: 2
[1,2] and [3].
Example 3:
Input: nums = [1, -1, 0], k = 0
Output: 3
Constraints
1 <= n <= 2 * 10^4-1000 <= nums[i] <= 1000- Values may be negative; target O(n) time.
Problem 16: Longest Substring Without Repeating Characters
LeetCode: 3. Longest Substring Without Repeating Characters
Description
Given a string s, return the length of the longest substring that contains no repeated character. Use a sliding window with a hash map from character to its last seen index; when a repeat falls inside the window, jump the left boundary past the previous occurrence.
Examples
Example 1:
Input: s = "abcabcbb"
Output: 3
The answer is "abc".
Example 2:
Input: s = "bbbbb"
Output: 1
Example 3:
Input: s = "pwwkew"
Output: 3
The answer is "wke".
Constraints
0 <= s.length() <= 5 * 10^4sconsists of printable ASCII characters.
Problem 17: Longest Consecutive Sequence
LeetCode: 128. Longest Consecutive Sequence
Description
Given an unsorted int[] array, return the length of the longest run of consecutive integers. Put all values in a hash set; start a count only at values that are the start of a run (x - 1 absent), then walk upward. This achieves O(n) without sorting.
Examples
Example 1:
Input: [100, 4, 200, 1, 3, 2]
Output: 4
The run 1, 2, 3, 4 has length 4.
Example 2:
Input: [0, 3, 7, 2, 5, 8, 4, 6, 0, 1]
Output: 9
Example 3:
Input: []
Output: 0
Constraints
0 <= n <= 10^5- Target O(n) time.
Problem 18: Lone Odd-Count Code
Description
The Codyssi quartermaster scans each crate’s part code at the dock and again in the vault. Across both combined logs every part code appears an even number of times except one, which appears an odd number of times. Given the combined int[] log, return that lone odd-count code. Tally counts in a hash map (or toggle a set’s membership) and return the code whose count is odd.
Examples
Example 1:
Input: [4, 1, 2, 1, 2]
Output: 4
Example 2:
Input: [7, 7, 9]
Output: 9
Example 3:
Input: [5]
Output: 5
Constraints
1 <= n <= 10^6,nis odd.- Codes lie in
1..1_000_000; exactly one code has an odd count.
Problem 19: Elf Gift Pairing
Description
Each Advent elf has a joy value; two elves can pair if their joy values differ by exactly d. Given an int[] array of joy values and d, return the number of distinct unordered value pairs (a, b) with a < b, b - a == d, and both a and b present in the array. Count value pairs, not index pairs. Put the distinct values in a hash set, then for each distinct value a check whether a + d is present.
Examples
Example 1:
Input: values = [1, 5, 3, 4, 2], d = 2
Output: 3
Pairs: (1,3), (2,4), (3,5).
Example 2:
Input: values = [8, 12, 16, 4, 0, 0], d = 4
Output: 4
Duplicates do not create extra value pairs.
Example 3:
Input: values = [1, 2, 3], d = 5
Output: 0
Constraints
1 <= n <= 10^5d >= 1; the array may contain duplicates.
Problem 20: Handle Team Signatures
Description
Group Codeforces handles by signature — the case-insensitive multiset of their letters, ignoring digits and any non-letter characters. Handles with identical signatures share a team. Given a String[] of handles, return the size of the largest team, or 0 if there are no handles. Build a canonical key per handle (e.g. a 26-letter count string) and count group sizes in a hash map.
Examples
Example 1:
Input: ["abc", "CAB", "bca1", "xyz"]
Output: 3
abc, CAB, bca1 all reduce to signature {a,b,c}.
Example 2:
Input: ["a1", "A2", "b"]
Output: 2
a1 and A2 both reduce to {a}.
Example 3:
Input: []
Output: 0
Constraints
0 <= n <= 10^4- Signatures ignore case, digits, and non-letter characters.
Problem 21: Balanced Ledger Window
Description
The Codyssi treasury logs daily net coin changes (positive or negative) in an int[]. Return the length of the longest contiguous range of days whose values sum to exactly zero, or 0 if no such range exists. Track running prefix sums in a hash map from sum value to its earliest index; a repeated prefix sum marks a zero-sum range between the two positions.
Examples
Example 1:
Input: [3, -3, 1, 1, -2]
Output: 2
The range [3, -3] sums to 0 (length 2).
Example 2:
Input: [1, 2, -3, 3, -3]
Output: 5
The whole array sums to 0.
Example 3:
Input: [1, 2, 3]
Output: 0
Constraints
1 <= n <= 10^5- Values may be negative; the log is non-null.
Problem 22: Stable Rune Lines
Description
Each scroll line is a stream of rune characters. A line is stable if, after counting each distinct rune’s occurrences, every nonzero count is unique — no two distinct runes share a count. Given a String[] of lines, return how many lines are stable. Build a frequency map per line, then check that its count values have no duplicates (a set of counts whose size equals the number of distinct runes).
Examples
Example 1:
Input: ["aabbb", "abc", "aabb"]
Output: 1
"aabbb" has counts {2,3} (unique) — stable. "abc" has counts {1,1,1} (collision) — unstable. "aabb" has counts {2,2} (collision) — unstable.
Example 2:
Input: ["a", "aabbb", "xxyyyzz"]
Output: 2
"a" has counts {1}; "aabbb" has {2,3} — both unique. "xxyyyzz" has {2,3,2} (collision) — unstable.
Example 3:
Input: []
Output: 0
Constraints
0 <= n <= 10^4- A single-rune line is always stable.
Problem 23: Submission Verdict Streak
Description
Replay a stream of submitted problem ids as an int[]. Return the length of the longest contiguous run of submissions in which no problem id repeats. Use a sliding window with a hash map from id to last seen index; advance the left boundary past any repeat to keep the window distinct.
Examples
Example 1:
Input: [1, 2, 3, 2, 5]
Output: 3
[1, 2, 3] and [3, 2, 5] are length-3 windows with distinct ids.
Example 2:
Input: [7, 7, 7]
Output: 1
Example 3:
Input: [1, 2, 3, 4]
Output: 4
Constraints
1 <= n <= 10^5- The stream is non-null.
Problem 24: LRU Cache
LeetCode: 146. LRU Cache
Description
Implement a fixed-capacity LRU (least-recently-used) cache supporting two operations in O(1) expected time: get(key) returns the value for key or -1 if absent (and marks it most-recently used), and put(key, value) inserts or updates the entry (marking it most-recently used), evicting the least-recently-used entry when capacity is exceeded. Combine a hash map with a doubly linked list (or a LinkedHashMap in access order).
Examples
Example 1:
Input: capacity = 2
put(1, 1); put(2, 2); get(1); put(3, 3); get(2); put(4, 4); get(1); get(3); get(4)
Output: get(1)=1, get(2)=-1, get(1)=-1, get(3)=3, get(4)=4
put(3,3) evicts key 2; put(4,4) evicts key 1.
Example 2:
Input: capacity = 1
put(1, 1); put(2, 2); get(1); get(2)
Output: get(1)=-1, get(2)=2
Example 3:
Input: capacity = 2
put(1, 10); put(1, 20); get(1)
Output: get(1)=20
Updating an existing key overwrites its value.
Constraints
1 <= capacity <= 3000getandputmust run in O(1) expected time.
The Dynamic Programming Paradigm
What DP Is & When to Reach for It
What kind of problems (optimize / count / decide feasibility) make you suspect DP rather than greedy or plain divide-and-conquer?
Recognizing the DP: The Telltale Signs
Which clues — “max/min/number of ways”, a small set of repeating choices at each step, an exponential brute force — flag a DP?
The “Last Decision” Lens
If you focus only on the final choice in an optimal solution, what smaller problem remains? Why is that the seed of a recurrence?
Optimal Substructure
How would you argue informally that an optimal whole is composed of optimal pieces? Sketch a “cut-and-paste” exchange argument in your head.
When Optimal Substructure Fails
Give yourself a problem where a locally optimal piece does NOT extend to a global optimum. What property is missing?
Overlapping Subproblems
What in the recursion tree tells you the same subproblem is recomputed, and how is that different from independent subproblems?
Counting Distinct Subproblems
How do you bound how many genuinely different subproblems exist? Why does that number cap the useful work?
Defining the State
How do you choose which variables index a subproblem so the rest of the work falls out cleanly? What makes a state “sufficient”?
Minimal vs Redundant State
What happens if your state carries too much information? Too little? How do you test whether a state is exactly right?
Writing the Recurrence
How do you turn “the last decision” into a transition between states? Walk every choice for one tiny worked example by hand.
Base Cases
Which smallest subproblems do you answer directly, and how do you ensure they are never recomputed or mis-indexed?
Top-Down Memoization
How do you take a plain recursive solution and bolt a cache onto it? What is the cache keyed by, and when do you read vs write it?
Bottom-Up Tabulation & Fill Order
How do you decide the order to fill the table so every dependency is ready before you need it? What is the dependency graph here?
Memo vs Tabulation Trade-offs
When is recursion-with-cache cleaner, and when does an explicit loop win on stack depth, constant factors, or space tricks?
Time Complexity
State the master identity: total time = (number of distinct states) × (work per transition). Why does memoization make this an upper bound, not the naive tree size?
Counting States and Transition Cost
For a generic DP, how do you read the state count off the state variables’ ranges, and how do you count the work inside one transition?
Why It Beats Naive Recursion
The unmemoized recursion is often exponential — where do the repeated branches come from, and how does caching collapse them to polynomial?
Space Complexity
What does the full table cost in the worst case, and how does that compare to the time bound? When are they equal and when not?
Table Space vs Recursion-Stack Space
Top-down adds a call stack; bottom-up does not. How deep can the stack get, and how does that add on top of table space?
Rolling Arrays & Dropping Dimensions
When a state depends only on the previous “layer”, how can you drop a whole dimension (2D→1D) and shrink space — and what do you give up?
Reconstructing the Solution
Once you have the optimal value, how do you recover the actual choices — storing back-pointers vs replaying the recurrence over the table?
Common Pitfalls
Where do DP attempts go wrong — insufficient state, missed base case, wrong fill order, off-by-one, overwriting cells still needed?
Implementation Walkthrough
Plan the code in parts before writing it — what does each part have to accomplish?
State, Table Setup & Base Cases
What shape is your table, what sentinel marks “unsolved/unreachable”, and which cells get hard-coded base values first?
The Transition Loop & Fill Order
Which loops iterate the state variables, in what order, so dependencies are always already filled when read?
Top-Down Memo vs Bottom-Up Form
How would the same recurrence look as a cached recursive function vs an iterative table fill? What changes, what stays?
Reconstruction Pass
After the values are computed, what second pass (or stored choices) lets you emit the actual optimal solution?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
0/1 Knapsack
0/1 vs Unbounded vs Fractional
How do the allowed item counts change the problem, and which variant is greedy (not DP) and which need DP?
Recognizing the DP
What about “pick a subset under a capacity to maximize value” signals knapsack-style DP rather than greedy?
Why Greedy by Value-Density Fails Here
For fractional knapsack greedy works — what breaks when items are indivisible? Construct a tiny counterexample in your head.
Optimal Substructure
Why is the best packing of the first \( i \) items under capacity \( w \) built from best packings of fewer items?
Overlapping Subproblems
In the take/leave recursion tree, which \( (i, w) \) pairs reappear? Why does the count of distinct pairs stay small?
State Definition
What does \( OPT(i, w) \) mean? Name the two axes and exactly what each index ranges over.
The Take-or-Leave Recurrence
Write the choice for item \( i \): skip it vs take it (paying weight, gaining value). What max do you take, and what guards “take”?
Base Cases
What are the values when there are no items left, or when capacity is zero?
Top-Down Memoization
How do you cache on the pair \( (i, w) \)? What does the recursion return at the leaves, and when do you hit the cache?
Bottom-Up Table & Fill Order
In what order do you fill the \( (i, w) \) grid so each cell’s dependencies (the row above) are already computed?
Reconstructing Which Items
Walking the filled table backward from \( OPT(n, W) \), how do you tell whether item \( i \) was taken?
1D Space Optimization
How do you collapse to a single row, and why must the capacity loop run backward for 0/1 (but forward for unbounded)?
Time Complexity
Apply cost = states × transition. How many \( (i, w) \) states are there, and what is the O(1) work per state?
Pseudo-Polynomial Explained
Why is \( O(nW) \) NOT polynomial in the input size? Tie it to how \( W \) is encoded in bits.
Beating Brute Force
The subset brute force is \( O(2^n) \) — how does indexing by \( (i, w) \) collapse that to \( O(nW) \)?
Space Complexity
The full table is \( O(nW) \). After the 1D trick it is \( O(W) \) — what dependency makes one row enough? And the recursion-stack cost top-down?
Variants & Related Problems
How do subset-sum, partition-equal-subset, and bounded knapsack reduce to or extend this template?
Pitfalls
Where do people slip — wrong loop direction in 1D, treating it as fractional, non-integer weights, capacity off-by-one?
Implementation Walkthrough
Plan the code in parts before writing it.
Table Setup & Base Row
What dimensions does the table have, and what does the zero-items (or zero-capacity) row/column hold?
The Capacity Loop & Take/Leave Fill
Inside the item loop, how does each capacity cell choose between the value above it and value + best-with-remaining-capacity?
Top-Down Memo vs Bottom-Up
How does the same take/leave choice read as a cached recursion vs an iterative fill? Which is easier to space-optimize?
Reconstruction Pass
Starting at the final cell, what comparison tells you an item was included, and how do you step to the previous state?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Longest Common Subsequence
Subsequence vs Substring
Pin down the difference. Why does dropping the contiguity requirement change which DP applies?
Recognizing the DP
What about “align two sequences and maximize shared order-preserving elements” signals a two-prefix DP?
Optimal Substructure
Why is the LCS of two full strings built from the LCS of their prefixes? Reason about the last character of each.
Overlapping Subproblems
In the recursion over prefixes, which \( (i, j) \) pairs get revisited? How many distinct pairs are there at most?
DP State on Two Prefixes
What does \( LCS(i, j) \) measure over the first \( i \) chars of one string and first \( j \) of the other?
The Match vs Mismatch Recurrence
Write the two cases: when the current characters are equal vs not equal. What choice does mismatch force?
Base Cases
What is the LCS when either prefix is empty?
Top-Down Memoization
How do you cache on \( (i, j) \)? What does each recursive call return, and where do the two recursive branches go?
Bottom-Up Table & Fill Order
In what direction do you fill the grid so each cell depends only on already-filled neighbors (up, left, diagonal)?
Backtracking the Subsequence
How do you walk the table from the bottom-right to recover an actual common subsequence (not just its length)?
Space-Reduced Variants
How do you get just the length in \( O(\min(m,n)) \) space, and what do you lose for reconstruction? (Hirschberg hints.)
Time Complexity
Apply cost = states × transition. How many \( (i, j) \) states fill the grid, and why is the work per cell O(1)?
Why It Beats Brute Force
Enumerating subsequences is exponential — how does the prefix grid collapse that to \( O(mn) \)?
Space Complexity
The full grid is \( O(mn) \). Why can the length-only version drop to two rows (or one), and what breaks reconstruction when you do?
Stack vs Table in Top-Down
How deep can the memoized recursion go, and how does that stack cost compare to the table itself?
Variants & Related Problems
How do edit distance, longest common substring, shortest common supersequence, and diff relate to this template?
Pitfalls
Where do people slip — confusing substring/subsequence, off-by-one on prefix indexing, losing reconstruction after space-saving?
Implementation Walkthrough
Plan the code in parts before writing it.
Grid Setup & Empty-Prefix Base Row/Column
What are the table dimensions, and what values fill the row and column for empty prefixes?
The Fill Loop: Match vs Mismatch
How does each inner-loop cell branch on character equality into a diagonal+1 vs a max of up/left?
Top-Down Memo vs Bottom-Up
How does the same match/mismatch logic look as a cached recursion vs an iterative double loop?
Reconstruction Pass
From the bottom-right cell, what move (diagonal vs up vs left) do you take at each step, and when do you emit a character?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Longest Increasing Subsequence
What the Problem Asks
What exactly counts as “increasing”, and is the subsequence required to be contiguous? Why does that matter?
Recognizing the DP
What about “longest chain of elements respecting order and a comparison” signals an ending-index DP?
Optimal Substructure
Why does the best subsequence ending at \( i \) build on the best subsequences ending at earlier valid indices?
Overlapping Subproblems
When you recurse on “best ending at \( j \)” for many \( i \), which subproblems repeat? Why does caching by index help?
Defining the Subproblem
Let \( L(i) \) be the LIS length ending at index \( i \). Why anchor it at the end rather than “first \( i \) elements”?
Why a Suffix-Free State Works
Why is “ending at \( i \)” enough information, with no need to remember the whole chosen prefix?
The \( O(n^2) \) Recurrence
Write \( L(i) \) in terms of all earlier indices \( j \) with a smaller value. What do you take the max over?
Base Cases
What is \( L(i) \) for the very first element, or any element with no valid predecessor?
Tabulation & Reading the Answer
After filling \( L \), where is the final answer — is it \( L(n-1) \), or the max over all \( L(i) \)?
Patience Sorting Intuition
How does the card-pile / “tails” metaphor model the problem and hint at a faster method?
The \( O(n \log n) \) Refinement
Sketch the tails array: what does each entry mean, and where does binary search replace the inner loop?
Recovering the Subsequence
What predecessor links (or pile indices) let you rebuild an actual LIS, in either the \( O(n^2) \) or \( O(n\log n) \) version?
Time Complexity
Apply cost = states × transition. For the basic version, how many states (one per index) and how much work per state?
Where the Log Comes From
In the refined version the per-state inner scan becomes a binary search — why does that turn \( O(n^2) \) into \( O(n \log n) \)?
Beating Exponential Brute Force
Checking all subsequences is \( O(2^n) \) — how does indexing by “ending position” collapse it to polynomial?
Space Complexity
Both versions use \( O(n) \) for the DP/tails plus predecessor links. What exactly lives in each array, and is there any rolling-array saving?
Variants & Related Problems
How do longest non-decreasing, longest bitonic, box-stacking, and Russian-doll envelopes extend this?
Pitfalls
Where do people slip — strict vs non-strict comparison, what the tails array does/doesn’t store, broken reconstruction in the fast version?
Implementation Walkthrough
Plan the code in parts before writing it.
State Array & Predecessor Setup
What does each entry of the length array start at, and what does the predecessor array record?
The Inner Scan (or Binary Search)
In the \( O(n^2) \) form, what does the inner loop compare? In the \( O(n\log n) \) form, what value are you searching for in tails?
Reading the Answer & Reconstruction
How do you find where the optimum ends, then follow predecessor links backward to emit the subsequence?
Why the Fast Version’s tails Isn’t the Answer Itself
The tails array length gives the LIS length but its contents are not necessarily a valid LIS — what extra bookkeeping fixes reconstruction?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Edit Distance
What the Problem Asks
What does it mean to transform one string into another, and what are we minimizing?
The Three Operations
Name insert, delete, and replace. What cost does the standard (Levenshtein) version assign each?
Recognizing the DP
What about “cheapest way to align/transform two sequences” signals the same two-prefix grid as LCS?
Optimal Substructure
Why is the cheapest transform of two full strings built from cheapest transforms of their prefixes? Reason about the last operation.
Overlapping Subproblems
In the recursion over prefix pairs, which \( (i, j) \) states recur? How many distinct ones are there?
DP State on Two Prefixes
What does \( D(i, j) \) mean over the first \( i \) chars of the source and first \( j \) of the target?
Base Cases
What is \( D(i, 0) \) and \( D(0, j) \) — transforming to/from an empty string? Why are these pure insert/delete counts?
The Recurrence
Write \( D(i,j) \) as a min over the three edits, plus the free “match” case when characters are equal. Walk each branch.
Which Neighbor Is Which Edit
Map each of up / left / diagonal in the grid to insert / delete / replace. Why is that mapping easy to flip by accident?
Top-Down vs Bottom-Up
How do you cache on \( (i,j) \) top-down, and in what order do you fill the grid bottom-up?
Reconstructing the Edit Script
How do you trace the table back into an actual alignment or sequence of operations?
Space Optimization
How do you reduce to two rows (or one) for the cost, and what does that cost you for reconstruction?
Time Complexity
Apply cost = states × transition. How many \( (i, j) \) states, and why is each transition O(1) (a min of three)?
Why It Beats Naive Recursion
Unmemoized, the three-way branching is exponential — how does the prefix grid collapse it to \( O(mn) \)?
Space Complexity
The full grid is \( O(mn) \). Why can the cost-only version drop to \( O(\min(m,n)) \), and what does that prevent? Add the top-down stack cost.
Variants & Weighted Costs
How does the recurrence change under unequal operation costs, or when transpositions are allowed (Damerau)?
Pitfalls
Where do people slip — base-case initialization, mixing up which index means insert vs delete, equal-char off-by-one?
Implementation Walkthrough
Plan the code in parts before writing it.
Grid Setup & Base Row/Column
What are the dimensions, and why do the first row and column count up from zero?
The Min-of-Three Fill Loop
Inside the loops, how does each cell take the cheaper of replace/insert/delete, with the diagonal handled specially on a match?
Top-Down Memo vs Bottom-Up
How does the same min-of-three logic read as a cached recursion vs an iterative double loop?
Reconstruction Pass
From the bottom-right cell, how does the move you take (diagonal/up/left) tell you which edit was applied?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Matrix Chain Multiplication
Why Parenthesization Matters
Same product, wildly different cost. What does associativity let you choose, and what does the wrong choice cost?
Cost of a Single Multiply
Recall the scalar-multiply count for a \( p \times q \) times \( q \times r \) product. Why is that the unit of cost?
Recognizing the DP (Interval DP)
What about “optimally split a sequence at some point, recurse on both sides” signals interval DP?
Optimal Substructure
Why is the best bracketing of a chain built from best bracketings of its left and right subchains around the outermost split?
Overlapping Subproblems
Which subchains \( (i, j) \) get solved repeatedly across different outer splits? How many distinct intervals are there?
Interval DP State
What does \( OPT(i, j) \) mean over the subchain of matrices from \( i \) to \( j \)?
The Split-Point Recurrence
Write the min over every split \( k \) between \( i \) and \( j \): left cost + right cost + cost to combine the two halves.
Reading the Combine Term
The combine cost uses three dimensions from the dimension array — which three, and why those?
Base Cases
What is \( OPT(i, i) \) — the cost of a single matrix with nothing to multiply?
Order of Filling
Why fill by increasing interval length rather than row by row? What dependency makes length-order the safe sweep?
Reconstructing the Parenthesization
How does a stored split table \( s(i,j) \) let you rebuild the optimal bracketing recursively?
Time Complexity
Apply cost = states × transition. How many intervals \( (i, j) \) are there, and why does each cost \( O(n) \) to scan its splits?
Where \( O(n^3) \) Comes From
Combine the \( O(n^2) \) intervals with the \( O(n) \) split scan — and contrast with the exponential count of all parenthesizations (Catalan).
Space Complexity
Why is the cost table \( O(n^2) \), and what extra \( O(n^2) \) does the split table add for reconstruction? Any rolling-array saving here, and why is it awkward?
Variants & Related Problems
How do optimal BST, polygon triangulation, burst balloons, and boolean parenthesization share this interval-DP shape?
Pitfalls
Where do people slip — dimension array off-by-one, wrong fill order (not by length), forgetting the combine term, confusing counts vs values?
Implementation Walkthrough
Plan the code in parts before writing it.
Dimension Array & Table Setup
How is the chain encoded as a dimension array of length \( n+1 \), and what do the diagonal (single-matrix) cells start at?
The Length-Then-Split Loops
How do you loop over increasing interval length, then over start index, then over split point \( k \)?
Recording the Best Split
Where do you store the argmin split so reconstruction can find it later?
Reconstruction Pass
How does a recursive walk over the split table emit the bracketed product?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Coin Change
The Two Questions
Distinguish “fewest coins to make an amount” from “how many distinct ways to make it”. Why do they need different recurrences?
Why Greedy Fails
Give the flavor of a denomination set where always picking the largest coin first gives a wrong or non-optimal answer.
Recognizing the DP
What about “reach a target using unlimited copies of given pieces” signals an unbounded-knapsack-style DP?
Optimal Substructure
Why is the answer for amount \( a \) built from answers for smaller amounts \( a - c \)? Reason about the last coin placed.
Overlapping Subproblems
Which amounts get recomputed across different coin choices? Why does the distinct-amount count bound the work?
State Definition
What does the DP entry mean — indexed by amount alone, or by (coin index, amount)? When do you actually need the coin axis?
The Min-Coins Recurrence
Write the min over each coin \( c \): one coin plus the best for amount \( a - c \). What’s the base case at amount 0, and how do you mark “impossible”?
The Counting-Ways Recurrence
Write the sum over coins for the number of ways. Why does this version need to avoid double-counting permutations?
Order of Loops Matters
For counting ways, why does coin-outer / amount-inner count combinations, while amount-outer / coin-inner counts permutations?
A Concrete Mental Trace
Pick coins {1,2} and amount 3. Trace both loop orders by hand — which one double-counts 1+2 vs 2+1?
Reconstructing the Coin Set
What auxiliary “last coin used” table lets you recover which coins achieve the optimum?
Time Complexity
Apply cost = states × transition. How many amount-states, and why is the per-amount work proportional to the number of coins?
Why It Beats Brute Force
Enumerating coin multisets is exponential — how does indexing by amount collapse it to \( O(n \cdot \text{amount}) \)?
Space Complexity
The 2D (coin × amount) table is \( O(n \cdot \text{amount}) \); the 1D form is \( O(\text{amount}) \). What dependency lets you drop the coin dimension, and which loop order keeps it correct?
Variants & Related Problems
How do bounded coins, combination sum, and “minimum perfect squares” map onto this template?
Pitfalls
Where do people slip — loop order swap, wrong “impossible” sentinel, integer overflow in counts, base case at amount 0?
Implementation Walkthrough
Plan the code in parts before writing it.
Array Setup & The Amount-0 Base Case
What size is the DP array, what sentinel marks unreachable amounts (min version), and what is the value at amount 0 for each question?
The Fill Loop & Loop Order Choice
Which loop is outer and which inner, and how does that choice encode combinations vs permutations for the counting question?
Top-Down Memo vs Bottom-Up
How does the same “try each coin” choice read as a cached recursion on amount vs an iterative sweep?
Reconstruction Pass
Using a “last coin” record, how do you walk from the target down to zero emitting coins?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Rod Cutting
The Problem Setup
Given a price-per-length table, what are you maximizing when cutting a rod of length \( n \) into pieces?
Recognizing the DP
What about “cut a whole into pieces to maximize total value, pieces reusable” signals unbounded-knapsack-style DP?
Optimal Substructure
Why is the best revenue for length \( n \) built from best revenue of a shorter remainder? Reason about the first piece cut off.
Overlapping Subproblems
Which remaining-length subproblems recur across different first cuts? Why does the distinct-length count bound the work?
State Definition
What does \( r(n) \) mean? Why is one axis (remaining length) enough here, unlike a 2D knapsack table?
First-Cut Recurrence
Write \( r(n) \) as a max over the length \( i \) of the first piece: price of that piece plus best for the rest. Walk the choices.
Base Cases
What is \( r(0) \), and why does that anchor the whole recursion?
Naive Recursion Blowup
Why does the unmemoized recursion explode, and roughly how fast? What is the branching factor at length \( n \)?
Memoized (Top-Down) Version
How do you cache on remaining length so each subproblem is solved once? What does a cache hit save?
Bottom-Up Tabulation
In what order do you fill \( r(0..n) \), and why does an increasing-length sweep guarantee dependencies are ready?
Recovering the Cuts
What auxiliary “first cut length” table lets you print the actual sequence of piece lengths?
Time Complexity
Apply cost = states × transition. There are \( n+1 \) length-states; why is the per-state work \( O(n) \), giving \( O(n^2) \)?
From Exponential to Quadratic
The naive tree is exponential — show yourself how memoizing on length alone collapses it to \( O(n^2) \).
Space Complexity
Why is the revenue table \( O(n) \), plus \( O(n) \) for the cut-choice table? What is the top-down recursion-stack depth, and does it change the order of growth?
Variants & Related Problems
How do “minimize cuts”, cutting with a per-cut cost, and the integer-knapsack reduction relate to this?
Pitfalls
Where do people slip — off-by-one on price indexing, forgetting the “no cut” option, confusing length with count?
Implementation Walkthrough
Plan the code in parts before writing it.
Revenue Array & Length-0 Base
What size is the revenue array, and what does \( r(0) \) hold to seed the sweep?
The First-Cut Max Loop
For each length, how does the inner loop try every first-piece length and keep the best total?
Top-Down Memo vs Bottom-Up
How does the same first-cut max read as a cached recursion on length vs an outer-to-inner table fill?
Reconstruction Pass
Using the stored first-cut length per total length, how do you peel off pieces until the rod is consumed?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Dynamic Programming on Trees
What Makes Tree DP Different
How does the subproblem structure of a tree (subtrees) differ from the grids and intervals of array DP?
Recognizing the DP
What about “compute something for every subtree, parent depends on children” signals tree DP?
Rooting the Tree
Why fix an arbitrary root, and how does that orient every subproblem toward “the subtree hanging below a node”?
Optimal Substructure on Subtrees
Why is a node’s answer built from its children’s subtree answers, and what makes those child answers independent of each other?
Overlapping Subproblems (or Not)
Each subtree is solved once in a plain tree DP — so where is the “overlap”? Contrast with rerooting, where reuse genuinely matters.
State Per Subtree
What does \( dp[v] \) summarize about the subtree rooted at \( v \)? How do you pick what to store so the parent can combine it?
Combining Children
How do you fold each child’s answer into the parent’s accumulator? Why must all children be finished first?
Include/Exclude States
Many tree DPs track two values per node (e.g. max independent set, house robber on a tree). Sketch when one number per node isn’t enough.
How the Two States Interact at the Parent
If \( v \) is “included”, what does that force about each child’s allowed state? If “excluded”?
Post-Order Traversal & Fill Order
Why does a single post-order DFS guarantee children are computed before parents?
Reconstructing the Choice
How do you walk back down from the root, carrying the parent’s decision, to recover which nodes/edges were actually chosen?
The Rerooting Technique
When you need the answer with every node as root, how do you reuse work instead of re-running DFS \( n \) times?
Down-pass and Up-pass
What does each of the two passes compute, and how do they combine into a per-node answer?
Time Complexity
Apply cost = states × transition. Why does summing the work over all nodes (each child visited once) give \( O(n) \) for a single-root DP?
Why Rerooting Stays Linear
Naively re-rooting is \( O(n^2) \) — how does precomputing partial sums / “answer excluding one child” keep the rerooted version \( O(n) \)?
Space Complexity
What is the per-node DP storage, and what dominates the rest: the recursion stack. How deep can it get on a degenerate (path-like) tree, and how do you avoid overflow?
Variants & Related Problems
How do subtree size, tree diameter, max weighted independent set, and tree-coloring DPs fit this template?
Pitfalls
Where do people slip — recursion depth / stack overflow on deep trees, revisiting the parent in DFS, wrong base case at leaves?
Implementation Walkthrough
Plan the code in parts before writing it.
Adjacency, Rooting & DFS Skeleton
How is the tree stored, how do you pass the parent to avoid going back up, and where does the post-order work happen?
Per-Node State & Leaf Base Case
What does a leaf’s \( dp \) value hold, and how is that the base case the recursion bottoms out on?
Combining Children Into the Parent
After recursing into each child, how do you merge their returned values (including the include/exclude pair) into the node’s answer?
Reconstruction or Rerooting Pass
For reconstruction, how do you re-descend carrying the chosen decision? For rerooting, what second DFS spreads the parent’s contribution down?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Dynamic Programming: Problem Set
Dynamic programming turns exponential recursion into polynomial tables by
remembering subproblem answers. This set starts with the classic 1-D recurrences
(stairs, robber, subarrays) and builds up through the core patterns: knapsack and
coin systems, grid paths, subsequence problems (LIS, LCS, palindromes), edit
distance, partitioning, and interval DP. Every problem is an implementation task:
fill in the stub in problemset/ and make its test in tests/problemset/ pass.
Foundational
Problem 1: Climbing Stairs
LeetCode: 70. Climbing Stairs
Description
You are climbing a staircase with n steps. Each time you may climb either 1 or 2
steps. Count the number of distinct ways you can reach the top. This is the canonical
introduction to DP: the number of ways to reach step i is the sum of the ways to
reach steps i-1 and i-2, the Fibonacci recurrence in disguise.
Examples
Example 1:
Input: n = 2
Output: 2
There are two ways: 1+1 and 2.
Example 2:
Input: n = 3
Output: 3
The ways are 1+1+1, 1+2, and 2+1.
Example 3:
Input: n = 5
Output: 8
Constraints
1 <= n <= 45
Problem 2: Maximum Subarray Sum
LeetCode: 53. Maximum Subarray
Description
Given an integer array nums, find the contiguous non-empty subarray with the largest
sum and return that sum. Framed as DP (Kadane’s algorithm), the best sum ending at index
i is either nums[i] alone or nums[i] extended onto the best sum ending at i-1.
Examples
Example 1:
Input: nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
Output: 6
The subarray [4, -1, 2, 1] has the largest sum, 6.
Example 2:
Input: nums = [1]
Output: 1
Example 3:
Input: nums = [-5, -1, -8]
Output: -1
All elements are negative, so the best single element -1 is the answer.
Constraints
1 <= nums.length <= 10^5-10^4 <= nums[i] <= 10^4
Problem 3: House Robber
LeetCode: 198. House Robber
Description
You are a robber planning to loot houses along a street. Each house holds a non-negative
amount of money, given by the array nums. Adjacent houses have connected security systems,
so robbing two adjacent houses on the same night triggers an alarm. Return the maximum amount
you can rob without alerting the police.
Examples
Example 1:
Input: nums = [1, 2, 3, 1]
Output: 4
Rob house 0 (money = 1) and house 2 (money = 3): total 4.
Example 2:
Input: nums = [2, 7, 9, 3, 1]
Output: 12
Rob houses 0, 2, and 4: 2 + 9 + 1 = 12.
Example 3:
Input: nums = [5]
Output: 5
Constraints
1 <= nums.length <= 1000 <= nums[i] <= 400
Problem 4: Unique Grid Paths
LeetCode: 62. Unique Paths
Description
A robot sits in the top-left corner of an m x n grid. It can only move either right or
down at any point. Count the number of distinct paths the robot can take to reach the
bottom-right corner. The number of paths to a cell is the sum of paths to the cell above
and the cell to the left.
Examples
Example 1:
Input: m = 3, n = 7
Output: 28
Example 2:
Input: m = 3, n = 2
Output: 3
The three paths are: Right→Down→Down, Down→Right→Down, Down→Down→Right.
Example 3:
Input: m = 1, n = 1
Output: 1
Constraints
1 <= m, n <= 100- The answer fits in a 32-bit signed integer.
Problem 5: Subset Sum Feasibility
Description
Given an array of positive integers nums and a target t, decide whether some subset of
nums sums to exactly t. This is the boolean core of the knapsack family: reachable[s]
is true if some subset sums to s, and adding a number x makes reachable[s] true whenever
reachable[s - x] was already true.
Examples
Example 1:
Input: nums = [3, 34, 4, 12, 5, 2], t = 9
Output: true
The subset [4, 5] sums to 9.
Example 2:
Input: nums = [3, 34, 4, 12, 5, 2], t = 30
Output: false
No subset sums to exactly 30.
Example 3:
Input: nums = [1, 2, 3], t = 0
Output: true
The empty subset sums to 0.
Constraints
0 <= nums.length <= 2001 <= nums[i] <= 10000 <= t <= 100000
Problem 6: Minimum Falling Path Sum
LeetCode: 931. Minimum Falling Path Sum
Description
Given an n x n integer matrix, find the minimum sum of any falling path. A falling path
starts at any cell in the first row and chooses, at each step, the cell directly below or
diagonally left-below or right-below the current cell, until it reaches the last row. Return
the minimum total of such a path.
Examples
Example 1:
Input: matrix = [[2, 1, 3], [6, 5, 4], [7, 8, 9]]
Output: 13
The minimal path is 1 -> 5 -> 7 = 13.
Example 2:
Input: matrix = [[-19, 57], [-40, -5]]
Output: -59
The minimal path is -19 -> -40 = -59.
Example 3:
Input: matrix = [[7]]
Output: 7
Constraints
1 <= n <= 100-100 <= matrix[i][j] <= 100
Applied Problems
Problem 7: Min Cost Climbing Stairs
LeetCode: 746. Min Cost Climbing Stairs
Description
You are given an array cost where cost[i] is the cost of stepping on the i-th stair.
Once you pay the cost you may climb one or two stairs. You can start from either stair 0
or stair 1. Return the minimum cost to reach the top of the floor (just past the last
stair).
Examples
Example 1:
Input: cost = [10, 15, 20]
Output: 15
Start at index 1, pay 15, and climb two stairs to the top.
Example 2:
Input: cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
Output: 6
Stepping on indices 0, 2, 4, 6, 7, 9 costs 1 each: total 6.
Example 3:
Input: cost = [0, 0]
Output: 0
Constraints
2 <= cost.length <= 10000 <= cost[i] <= 999
Problem 8: Coin Change (Minimum Coins)
LeetCode: 322. Coin Change
Description
Given an array of coin denominations coins and a target amount, compute the fewest number
of coins needed to make up that amount. Each denomination may be used an unlimited number of
times. If the amount cannot be formed by any combination of coins, return -1.
Examples
Example 1:
Input: coins = [1, 2, 5], amount = 11
Output: 3
11 = 5 + 5 + 1, using three coins.
Example 2:
Input: coins = [2], amount = 3
Output: -1
No combination of 2-coins makes 3.
Example 3:
Input: coins = [1, 5, 10], amount = 0
Output: 0
Constraints
1 <= coins.length <= 121 <= coins[i] <= 2^31 - 10 <= amount <= 10^4
Problem 9: Coin Change (Count Combinations)
LeetCode: 518. Coin Change II
Description
Given an array of coin denominations coins and a target amount, count the number of
distinct combinations (order does not matter) of coins that sum to amount. Each denomination
may be used an unlimited number of times. This is the unbounded counting version of coin
change: iterating coins in the outer loop avoids counting permutations as distinct.
Examples
Example 1:
Input: amount = 5, coins = [1, 2, 5]
Output: 4
The combinations are 5, 2+2+1, 2+1+1+1, and 1+1+1+1+1.
Example 2:
Input: amount = 3, coins = [2]
Output: 0
Example 3:
Input: amount = 10, coins = [10]
Output: 1
Constraints
1 <= coins.length <= 3001 <= coins[i] <= 50000 <= amount <= 5000- The answer fits in a 32-bit signed integer.
Problem 10: Perfect Squares
LeetCode: 279. Perfect Squares
Description
Given an integer n, return the least number of perfect-square numbers (1, 4, 9, 16, ...)
that sum to n. This is coin change where the “coins” are the perfect squares not exceeding
n, with unlimited reuse.
Examples
Example 1:
Input: n = 12
Output: 3
12 = 4 + 4 + 4.
Example 2:
Input: n = 13
Output: 2
13 = 4 + 9.
Example 3:
Input: n = 1
Output: 1
Constraints
1 <= n <= 10^4
Problem 11: Maximum Product Subarray
LeetCode: 152. Maximum Product Subarray
Description
Given an integer array nums, find the contiguous non-empty subarray with the largest product
and return that product. Because a negative factor can flip the largest product to the smallest,
you must track both the running maximum and minimum product ending at each index.
Examples
Example 1:
Input: nums = [2, 3, -2, 4]
Output: 6
The subarray [2, 3] has product 6.
Example 2:
Input: nums = [-2, 0, -1]
Output: 0
The result cannot be 2, because [-2, -1] is not contiguous.
Example 3:
Input: nums = [-2, 3, -4]
Output: 24
The whole array multiplies to 24.
Constraints
1 <= nums.length <= 2 * 10^4-10 <= nums[i] <= 10- The product of any subarray fits in a 32-bit signed integer.
Problem 12: House Robber II (Circular Street)
LeetCode: 213. House Robber II
Description
Houses are arranged in a circle, so the first and last houses are adjacent. As before, you cannot rob two adjacent houses. Return the maximum amount you can rob. The trick is to run the linear House Robber twice — once excluding the first house, once excluding the last — and take the better result.
Examples
Example 1:
Input: nums = [2, 3, 2]
Output: 3
You cannot rob houses 0 and 2 together (they are adjacent in the circle), so the best is 3.
Example 2:
Input: nums = [1, 2, 3, 1]
Output: 4
Rob houses 0 and 2: 1 + 3 = 4.
Example 3:
Input: nums = [5]
Output: 5
Constraints
1 <= nums.length <= 1000 <= nums[i] <= 1000
Problem 13: Decode Ways
LeetCode: 91. Decode Ways
Description
A message of digits is encoded using the mapping 'A' -> "1", 'B' -> "2", ..., 'Z' -> "26".
Given a string s of digits, count the number of ways to decode it. A single digit 1–9
decodes on its own; a two-digit group 10–26 decodes as one letter. Leading zeros and
standalone zeros that cannot pair are invalid and contribute 0 ways.
Examples
Example 1:
Input: s = "12"
Output: 2
It decodes as "AB" (1 2) or "L" (12).
Example 2:
Input: s = "226"
Output: 3
It decodes as "BZ", "VF", or "BBF".
Example 3:
Input: s = "06"
Output: 0
A leading zero cannot be decoded.
Constraints
1 <= s.length <= 100scontains only digits and may contain leading zeros.
Problem 14: Longest Increasing Subsequence
LeetCode: 300. Longest Increasing Subsequence
Description
Given an integer array nums, return the length of the longest strictly increasing
subsequence. A subsequence is formed by deleting zero or more elements without changing the
order of the rest. The O(n^2) DP sets dp[i] to the longest increasing subsequence ending
at index i.
Examples
Example 1:
Input: nums = [10, 9, 2, 5, 3, 7, 101, 18]
Output: 4
One longest increasing subsequence is [2, 3, 7, 18].
Example 2:
Input: nums = [0, 1, 0, 3, 2, 3]
Output: 4
Example 3:
Input: nums = [7, 7, 7, 7]
Output: 1
“Strictly” increasing means equal elements do not extend the run.
Constraints
1 <= nums.length <= 2500-10^4 <= nums[i] <= 10^4
Problem 15: Longest Common Subsequence
LeetCode: 1143. Longest Common Subsequence
Description
Given two strings text1 and text2, return the length of their longest common subsequence,
or 0 if there is none. A common subsequence appears in both strings in the same relative
order but not necessarily contiguously. The DP compares characters: on a match, extend the
diagonal; otherwise take the better of dropping a character from either string.
Examples
Example 1:
Input: text1 = "abcde", text2 = "ace"
Output: 3
The longest common subsequence is "ace".
Example 2:
Input: text1 = "abc", text2 = "abc"
Output: 3
Example 3:
Input: text1 = "abc", text2 = "def"
Output: 0
Constraints
1 <= text1.length, text2.length <= 1000- The strings consist of lowercase English letters.
Problem 16: Longest Palindromic Subsequence
LeetCode: 516. Longest Palindromic Subsequence
Description
Given a string s, return the length of its longest palindromic subsequence. This is interval
DP: dp[i][j] is the longest palindromic subsequence within s[i..j]. When the endpoints match
they add 2 to the inner interval; otherwise take the better of shrinking from either end.
Examples
Example 1:
Input: s = "bbbab"
Output: 4
One longest palindromic subsequence is "bbbb".
Example 2:
Input: s = "cbbd"
Output: 2
One longest palindromic subsequence is "bb".
Example 3:
Input: s = "a"
Output: 1
Constraints
1 <= s.length <= 1000sconsists of lowercase English letters.
Problem 17: Edit Distance
LeetCode: 72. Edit Distance
Description
Given two strings word1 and word2, return the minimum number of operations required to
convert word1 into word2. The permitted operations are inserting a character, deleting a
character, and replacing a character (Levenshtein distance). The DP fills a table where
dp[i][j] is the edit distance between the first i and first j characters.
Examples
Example 1:
Input: word1 = "horse", word2 = "ros"
Output: 3
horse -> rorse -> rose -> ros (replace, delete, delete).
Example 2:
Input: word1 = "intention", word2 = "execution"
Output: 5
Example 3:
Input: word1 = "", word2 = "abc"
Output: 3
Constraints
0 <= word1.length, word2.length <= 500- The strings consist of lowercase English letters.
Problem 18: Partition Equal Subset Sum
LeetCode: 416. Partition Equal Subset Sum
Description
Given an array of positive integers nums, decide whether it can be partitioned into two
subsets with equal sums. This reduces to subset-sum: a valid split exists only if the total
sum is even and some subset sums to exactly half of it.
Examples
Example 1:
Input: nums = [1, 5, 11, 5]
Output: true
The array splits into [1, 5, 5] and [11], each summing to 11.
Example 2:
Input: nums = [1, 2, 3, 5]
Output: false
The total 11 is odd, so no equal split is possible.
Example 3:
Input: nums = [2, 2]
Output: true
Constraints
1 <= nums.length <= 2001 <= nums[i] <= 100
Problem 19: Unique Paths II (With Obstacles)
LeetCode: 63. Unique Paths II
Description
A robot moves from the top-left to the bottom-right of an m x n grid, going only right or
down. Some cells contain obstacles, marked 1, which cannot be entered; open cells are marked
0. Count the number of distinct obstacle-free paths. If the start or end cell is blocked, the
answer is 0.
Examples
Example 1:
Input: grid = [[0, 0, 0], [0, 1, 0], [0, 0, 0]]
Output: 2
The obstacle in the middle leaves exactly two paths around it.
Example 2:
Input: grid = [[0, 1], [0, 0]]
Output: 1
Example 3:
Input: grid = [[1]]
Output: 0
The start cell is blocked.
Constraints
1 <= m, n <= 100grid[i][j]is0or1.- The answer fits in a 32-bit signed integer.
Problem 20: Minimum Path Sum
LeetCode: 64. Minimum Path Sum
Description
Given an m x n grid of non-negative integers, find a path from the top-left to the
bottom-right that minimizes the sum of all numbers along the way. You may only move right or
down. Return that minimum sum, counting both endpoints.
Examples
Example 1:
Input: grid = [[1, 3, 1], [1, 5, 1], [4, 2, 1]]
Output: 7
The path 1 -> 3 -> 1 -> 1 -> 1 sums to 7.
Example 2:
Input: grid = [[1, 2, 3], [4, 5, 6]]
Output: 12
Example 3:
Input: grid = [[5]]
Output: 5
Constraints
1 <= m, n <= 2000 <= grid[i][j] <= 200
Problem 21: Word Break
LeetCode: 139. Word Break
Description
Given a string s and a dictionary of strings wordDict, return true if s can be segmented
into a space-separated sequence of one or more dictionary words. The same dictionary word may be
reused. The DP marks dp[i] true when some dictionary word ends exactly at position i and the
prefix before it is also breakable.
Examples
Example 1:
Input: s = "leetcode", wordDict = ["leet", "code"]
Output: true
"leetcode" splits into "leet code".
Example 2:
Input: s = "applepenapple", wordDict = ["apple", "pen"]
Output: true
"applepenapple" splits into "apple pen apple"; reuse is allowed.
Example 3:
Input: s = "catsandog", wordDict = ["cats", "dog", "sand", "and", "cat"]
Output: false
Constraints
1 <= s.length <= 3001 <= wordDict.length <= 10001 <= wordDict[i].length <= 20- All strings consist of lowercase English letters.
Problem 22: Best Time to Buy and Sell Stock with Cooldown
LeetCode: 309. Best Time to Buy and Sell Stock with Cooldown
Description
Given an array prices where prices[i] is the stock price on day i, find the maximum profit
you can achieve. You may complete as many transactions as you like, but you may not hold more than
one share at a time, and after you sell you must wait one day (a cooldown) before buying again.
A small state machine — holding, sold, resting — captures the transitions.
Examples
Example 1:
Input: prices = [1, 2, 3, 0, 2]
Output: 3
The transactions are buy, sell, cooldown, buy, sell: profit 3.
Example 2:
Input: prices = [1]
Output: 0
Example 3:
Input: prices = [2, 1, 4]
Output: 3
Buy at 1, sell at 4.
Constraints
1 <= prices.length <= 50000 <= prices[i] <= 1000
Problem 23: Maximal Square
LeetCode: 221. Maximal Square
Description
Given an m x n binary matrix of 0s and 1s, find the largest square containing only 1s and
return its area. The DP value dp[i][j] is the side length of the largest all-ones square whose
bottom-right corner is at (i, j), equal to one plus the minimum of its top, left, and top-left
neighbors when the cell is 1.
Examples
Example 1:
Input: matrix = [[1, 0, 1, 0, 0], [1, 0, 1, 1, 1], [1, 1, 1, 1, 1], [1, 0, 0, 1, 0]]
Output: 4
The largest all-ones square has side 2, so area 4.
Example 2:
Input: matrix = [[0, 1], [1, 0]]
Output: 1
Example 3:
Input: matrix = [[0]]
Output: 0
Constraints
1 <= m, n <= 300matrix[i][j]is0or1.
Problem 24: Target Sum
LeetCode: 494. Target Sum
Description
Given an array of non-negative integers nums and an integer target, you assign a + or -
sign to each number and concatenate them into an expression. Return the number of different sign
assignments that evaluate to target. Splitting the numbers into a positive group P and a
negative group N, the problem reduces to counting subsets P with a fixed sum, a subset-sum
counting DP.
Examples
Example 1:
Input: nums = [1, 1, 1, 1, 1], target = 3
Output: 5
There are five ways to reach 3, e.g. -1+1+1+1+1 and +1+1+1+1-1.
Example 2:
Input: nums = [1], target = 1
Output: 1
Example 3:
Input: nums = [1], target = 2
Output: 0
Constraints
1 <= nums.length <= 200 <= nums[i] <= 1000-1000 <= target <= 1000- The answer fits in a 32-bit signed integer.
Problem 25: Longest Palindromic Substring
LeetCode: 5. Longest Palindromic Substring
Description
Given a string s, return the longest contiguous substring of s that is a palindrome. Unlike
the subsequence version, the characters must be adjacent. The interval DP marks dp[i][j] true
when s[i..j] is a palindrome, which holds when the endpoints match and the inner substring is
also a palindrome.
Examples
Example 1:
Input: s = "babad"
Output: "bab"
"aba" is also a valid answer of the same length.
Example 2:
Input: s = "cbbd"
Output: "bb"
Example 3:
Input: s = "a"
Output: "a"
Constraints
1 <= s.length <= 1000sconsists of digits and English letters.
Problem 26: Number of Longest Increasing Subsequences
LeetCode: 673. Number of Longest Increasing Subsequence
Description
Given an integer array nums, return the number of longest strictly increasing subsequences.
Beyond the length DP, you track a count cnt[i] of how many longest increasing subsequences end
at index i, updating both the length and the count as you extend.
Examples
Example 1:
Input: nums = [1, 3, 5, 4, 7]
Output: 2
The two longest increasing subsequences are [1, 3, 4, 7] and [1, 3, 5, 7].
Example 2:
Input: nums = [2, 2, 2, 2, 2]
Output: 5
Each single element is a longest increasing subsequence of length 1.
Example 3:
Input: nums = [1, 2, 4, 3, 5, 4, 7, 2]
Output: 3
Constraints
1 <= nums.length <= 2000-10^6 <= nums[i] <= 10^6- The answer fits in a 32-bit signed integer.
Problem 27: 0/1 Knapsack
Description
You are given n items, where item i has weight weights[i] and value values[i], and a
knapsack with capacity capacity. Choose a subset of items whose total weight does not exceed
capacity so as to maximize the total value. Each item may be taken at most once. Return the
maximum value achievable.
Examples
Example 1:
Input: weights = [1, 3, 4, 5], values = [1, 4, 5, 7], capacity = 7
Output: 9
Take items with weights 3 and 4 for value 4 + 5 = 9.
Example 2:
Input: weights = [2, 3, 4], values = [4, 5, 6], capacity = 5
Output: 9
Take items with weights 2 and 3 for value 4 + 5 = 9.
Example 3:
Input: weights = [5], values = [10], capacity = 4
Output: 0
The only item is too heavy.
Constraints
0 <= n <= 2001 <= weights[i] <= 10000 <= values[i] <= 10^60 <= capacity <= 10^4
Problem 28: Minimum Workshop Split
Description
An elf workshop must divide a pile of parts into two bins. Given positive part weights parts,
split them into two groups so that the absolute difference between the two group sums is as small
as possible. Return that minimum difference. This is the optimization twin of partition: find the
subset sum closest to half the total.
Examples
Example 1:
Input: parts = [1, 6, 11, 5]
Output: 1
Split into [1, 5, 6] (sum 12) and [11] (sum 11): difference 1.
Example 2:
Input: parts = [3, 1, 4, 2, 2]
Output: 0
Split into [3, 2] and [1, 4]… or any even split summing to 6 each.
Example 3:
Input: parts = [10]
Output: 10
Constraints
0 <= parts.length <= 2001 <= parts[i] <= 100
Problem 29: Matrix Chain Multiplication
Description
You must multiply a chain of matrices A1 · A2 · ... · An, where matrix Ai has dimensions
dims[i-1] x dims[i]. Matrix multiplication is associative, so the parenthesization does not
change the result but does change the number of scalar multiplications. Given the array dims of
length n+1, return the minimum number of scalar multiplications needed. This is the textbook
interval DP: dp[i][j] is the best cost to multiply the sub-chain from i to j, split at every
possible point k.
Examples
Example 1:
Input: dims = [10, 20, 30]
Output: 6000
Two matrices 10x20 and 20x30 cost 10 * 20 * 30 = 6000.
Example 2:
Input: dims = [40, 20, 30, 10, 30]
Output: 26000
The optimal parenthesization is (A1(A2A3))A4.
Example 3:
Input: dims = [10, 20]
Output: 0
A single matrix needs no multiplications.
Constraints
2 <= dims.length <= 1001 <= dims[i] <= 500- The answer fits in a 64-bit signed integer.
Problem 30: Burst Balloons
LeetCode: 312. Burst Balloons
Description
You are given n balloons in a row, with balloon i painted with the number nums[i]. If you
burst balloon i, you earn nums[left] * nums[i] * nums[right] coins, where left and right
are the balloons immediately adjacent to i (treating out-of-range neighbors as a virtual balloon
worth 1). After bursting, the neighbors become adjacent. Return the maximum coins you can collect
by bursting all the balloons. The interval DP considers each balloon as the last one burst in an
interval, so its neighbors are the fixed interval boundaries.
Examples
Example 1:
Input: nums = [3, 1, 5, 8]
Output: 167
Bursting in order 1, 5, 3, 8 yields 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167.
Example 2:
Input: nums = [1, 5]
Output: 10
Burst 1 first for 1*1*5 = 5, then 5 for 1*5*1 = 5: total 10.
Example 3:
Input: nums = [7]
Output: 7
Constraints
1 <= n <= 3000 <= nums[i] <= 100- The answer fits in a 32-bit signed integer.
The Greedy Paradigm
What Greedy Is & When to Reach for It
What kind of optimization problem makes “grab the best-looking option now” a viable strategy?
The Greedy Template
What are the recurring steps — sort/prioritize, iterate, commit irrevocably — shared by every greedy algorithm?
Irrevocable Commitment
Why does a greedy algorithm never reconsider a choice once made, and what does that rule out?
The Greedy-Choice Property
Why must there exist an optimal solution that contains the first greedy choice for the method to work?
Safe Moves
What makes a choice “safe” — provably extendable to an optimum — rather than merely attractive?
Optimal Substructure
After committing one greedy choice, why must the leftover subproblem be solved the same greedy way?
Reducing to a Smaller Subproblem
How does each greedy step shrink the problem to a strictly smaller instance of itself?
Choosing the Right Greedy Criterion
Given several plausible “best” choices (smallest, largest, ratio, earliest), how do you tell which one is correct?
Testing a Criterion Against Counterexamples
How do you stress a candidate rule with small adversarial inputs before trusting it?
Greedy vs. Dynamic Programming
When does committing early throw away information that DP would have kept by exploring all options?
When Greedy Fails
Sketch an instance (e.g. coin change with odd denominations) where the locally best move blocks the global optimum.
Proof Strategies at a Glance
What do “exchange argument” and “greedy stays ahead” each try to show about an optimal solution?
Time Complexity
Across greedy algorithms, where does the running time actually go?
The Sort Step Usually Dominates
Why does an initial sort make the typical greedy \( O(n \log n) \), and when can a heap or counting sort change that?
Cost of the Greedy Pass
Why is the commit-as-you-go scan often a single \( O(n) \) sweep, and what pushes it higher (e.g. a per-step heap pop)?
Best vs. Worst Case
Why does greedy’s runtime usually not vary with input shape the way search or DP does?
Space Complexity
What does a greedy algorithm need to keep in memory beyond the input?
Auxiliary Structures
When does greedy need only \( O(1) \) running state, and when does it carry a heap or result list of size \( O(n) \)?
In-Place vs. Extra Output
How does choosing to mutate the input versus build a separate answer change the space bound?
Trade-offs vs. Alternatives
What do you gain in speed and simplicity, and what guarantee do you give up compared to exhaustive or DP search?
Real Uses
Where do greedy choices show up in scheduling, compression, routing, and resource allocation?
Pitfalls
Why is “it passed my examples” never enough to trust a greedy algorithm?
Implementation Walkthrough
How would you structure a generic greedy solution before writing a specific one?
Setup & Data Structures
What do you sort or load into a priority queue first, and what running state do you initialize?
The Main Greedy Loop
What is the single decision made each iteration, and how is feasibility checked before committing?
Termination
When does the loop stop, and how do you assemble the final answer from committed choices?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
The Exchange Argument
What an Exchange Argument Proves
What claim about a greedy solution are you actually trying to establish with this technique?
The Core Idea: Transform Without Losing
Why does nudging an optimal solution toward the greedy choice never make it worse?
Setting Up the Two Solutions
How do you name the greedy solution and an arbitrary optimal one so you can compare them step by step?
Why “Arbitrary Optimal” Matters
Why must the optimal you start from be unconstrained rather than a convenient special case?
Finding the First Difference
Where do you locate the earliest place the optimal solution diverges from greedy, and why there?
Ordering the Choices
Why does the argument depend on a consistent order (by finish time, ratio, weight) to define “first”?
The Swap Step
How does exchanging the optimal’s choice for the greedy’s choice at that point preserve feasibility?
Showing the Swap Doesn’t Hurt the Objective
Why is the cost/value after the swap no worse than before?
Preserving Feasibility After the Swap
Why must you re-check that the swapped-in element still satisfies every constraint?
Inductively Converting Optimal Into Greedy
How do repeated swaps turn any optimal solution into the greedy one without losing optimality?
Why the Process Terminates
Why does each swap reduce the number of differences, forcing the argument to end?
Exchange vs. Greedy-Stays-Ahead
When is it easier to argue a running invariant than to argue a swap, and vice versa?
Worked Intuition: Why Earliest-Finish Wins
As a prompt — how would a swap show that picking the earliest-finishing activity is safe?
Worked Intuition: Why Highest-Ratio Wins
As a prompt — how would a swap move weight from a low-ratio item to a high-ratio one without losing value?
Common Mistakes in Exchange Proofs
Why does forgetting to check feasibility after the swap, or assuming a unique optimum, break the argument?
Cost of an Argument vs. Cost of the Algorithm
Why is the exchange argument about correctness rather than runtime, and how does it still dictate what the code must compute?
Pitfalls
What goes wrong if the “first difference” you pick isn’t well-defined or the swap changes the rest of the solution?
Implementation Walkthrough
How would you turn an exchange-justified greedy into code that mirrors the proof’s ordering?
Encoding the Ordering as a Comparator
How does the sort key you choose correspond directly to the “first difference” in the proof?
The Commit Loop the Proof Justifies
Why does each loop iteration correspond to one safe choice the exchange argument blessed?
Verifying on Small Cases
How do you use tiny inputs to sanity-check that your comparator matches the proven safe move?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Activity Selection
The Problem: Most Non-Overlapping Intervals
Given intervals with start and finish times, what exactly are you maximizing and under what constraint?
Why the Obvious Greedies Fail
Why do “shortest activity first” or “earliest start first” pick suboptimal sets?
Building a Counterexample
How would you hand-craft three intervals that defeat the shortest-duration rule?
The Greedy Choice: Earliest Finish Time
Why does always taking the activity that frees the resource soonest leave the most room for the rest?
The Algorithm Walked Through
Sort by finish time, then scan — when do you take an activity and when do you skip it?
Tracking the Last Finish Time
What single variable lets you decide compatibility in \( O(1) \) per activity?
Why the Greedy Is Optimal (Intuition)
How would an exchange argument swap an optimal solution’s first pick for the earliest-finishing one?
Staying-Ahead View
Why is the greedy set never “behind” an optimal one in finish time after each pick?
Interval Scheduling vs. Interval Partitioning
What’s the difference between picking the most jobs and using the fewest rooms?
The Weighted Variant
Why does attaching values to activities break greedy and push you toward DP?
Time Complexity
Where does the work go from input to answer?
Sorting Dominates
Why is the sort by finish time the \( O(n \log n) \) bottleneck?
The Linear Scan
Why is the selection pass only \( O(n) \), and how does pre-sorted input drop the total to \( O(n) \)?
Space Complexity
What memory does the algorithm need beyond the intervals themselves?
Storing the Result
Why is the extra space \( O(n) \) for the chosen set in the worst case but only \( O(1) \) running state?
Real Uses
Where does this appear — meeting-room booking, CPU job scheduling, bandwidth allocation?
Pitfalls
Why must you sort by finish time, not start time, and how do you handle ties or touching endpoints?
Implementation Walkthrough
How do the pieces of the code map onto the greedy rule?
Setup & Sorting
What comparator do you sort by, and how do you represent each interval?
The Selection Loop
How does the loop compare each activity’s start against the last finish and decide to take or skip?
Collecting & Returning Results
How do you record selected activities and end with the maximum compatible set?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Huffman Coding
The Compression Problem
Why does assigning fixed-length codes to every symbol waste space, and what do we want instead?
Variable-Length Codes
Why should frequent symbols get short codes and rare symbols long ones?
Weighted Code Length as the Objective
What sum are you actually minimizing across all symbols and their frequencies?
Prefix-Free Codes
Why must no codeword be a prefix of another, and how does a binary tree guarantee it?
Codewords as Root-to-Leaf Paths
Why does placing every symbol at a leaf make the code automatically decodable?
The Greedy Frequency Merge
Why repeatedly combine the two least-frequent symbols into one subtree?
Why the Two Rarest Merge First
What goes wrong if you ever merge anything but the two smallest available weights?
Building the Tree with a Heap
How does a min-priority-queue of subtrees drive the construction loop?
Node Layout
What does each tree node hold — frequency, symbol, two children — and how are merges recorded?
Encoding & Decoding
How do you walk the tree to turn text into bits, and bits back into text?
Emitting the Code Table
Why do you traverse the finished tree once to assign each symbol its bit string?
Why Greedy Is Optimal (Intuition)
How would an exchange argument show the two rarest symbols belong deepest in the tree?
Static vs. Adaptive Huffman
When do you ship the frequency table versus learn it on the fly?
Time Complexity
Where does the running time go for building and using the code?
Building the Tree
Why is the build \( O(n \log n) \) in the number of distinct symbols, driven by heap pushes and pops?
Counting, Encoding, and Decoding
Why is the frequency count and the encode/decode pass linear in the text length?
Space Complexity
What memory does the encoder and decoder need?
The Tree and the Code Table
Why are the tree and per-symbol code table both \( O(n) \) in the alphabet size?
Output and Heap Footprint
How big is the compressed bitstream and the heap relative to the input?
Real Uses
Where does Huffman sit inside ZIP, JPEG, MP3, and other codecs?
Pitfalls
Why must the decoder know the tree, and what happens with a single-symbol alphabet or tie-breaking choices?
Implementation Walkthrough
How do the stages of the code line up — count, build, assign, encode?
Setup: Frequency Table & Heap
How do you count symbol frequencies and seed the priority queue with leaf nodes?
The Merge Loop
What does each iteration pop, combine, and push, and when does the loop end with one root?
Assigning Codes by Traversal
How does a recursive walk accumulate the bit path to each leaf into the code table?
Encoding & Decoding Streams
How do you map text to bits via the table, and bits back to text by walking the tree?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Fractional Knapsack
The Problem & When to Use It
What are you maximizing, what is the capacity constraint, and what does “fractional” let you do?
Fractional vs. 0/1 Knapsack
Why does allowing partial items change the problem from NP-hard to easy?
The Greedy Choice: Value Density
Why is the value-per-weight ratio \( v_i / w_i \) the right thing to sort and pick by?
Why Density, Not Raw Value or Weight
What counterexample shows that picking by highest value (or lightest item) first fails?
The Algorithm Walked Through
Sort by density, take whole items while they fit — what happens at the boundary?
Splitting the Last Item
How does taking a fraction of the final item fill the capacity exactly?
Why the Greedy Is Optimal (Intuition)
How would swapping low-density weight for high-density weight prove no better packing exists?
The Continuous Swap
Why can you always move an infinitesimal unit of capacity to a denser item without loss?
Why the Same Rule Breaks for 0/1
Why does indivisibility let a high-ratio item block a better whole-item combination?
Time Complexity
Where does the running time go?
Sorting by Ratio Dominates
Why is computing and sorting densities the \( O(n \log n) \) step?
The Fill Pass
Why is the greedy fill only \( O(n) \), and could a selection-based approach reach \( O(n) \) overall?
Space Complexity
What memory does the algorithm need beyond the items?
Running State vs. Item Array
Why is the extra space \( O(1) \) (remaining capacity, accumulated value) on top of the \( O(n) \) item list?
Real Uses
Where does divisible-resource allocation appear — investment splitting, bandwidth, cargo by weight?
Pitfalls
Why must ratios drive selection, and how do zero-weight items, tied ratios, or floating-point drift trip you up?
Implementation Walkthrough
How do the parts of the code realize the greedy fill?
Setup: Compute Ratios & Sort
How do you pair each item with its density and order the array descending?
The Greedy Fill Loop
How does each iteration take a whole item or compute the fraction that fits?
Termination & Accumulated Value
When does the loop stop, and how do you return the total value including the fractional piece?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Stable Matching
The Problem & When to Use It
Given two groups with ranked preferences, what does a “matching” pair up and why do we want stability?
What “Stable” Means
What is a blocking pair, and why does its absence define a stable matching?
The Gale–Shapley Algorithm
How do rounds of proposals and tentative acceptances move the matching toward stability?
Proposals and Tentative Engagements
Why does a proposer go down their list in order, and when does a receiver trade up?
Why Receivers Only Improve
Why can a receiver’s tentative partner only get better (never worse) over time?
Why It Always Terminates
Why can the proposal process never loop forever, and how many proposals can happen?
Why a Stable Matching Always Exists
Why does the algorithm always end with everyone matched and no blocking pair?
No Blocking Pair Survives
As a prompt — why can’t a higher-preferred partner have been skipped without a proposal?
Proposer-Optimality
Why does the proposing side end up with the best partner achievable in any stable matching?
Receiver-Pessimality
Why is the receiving side simultaneously stuck with their worst stable partner?
Time Complexity
Where does the running time go across all the proposal rounds?
Bounding Total Proposals
Why can each proposer propose at most \( n \) times, capping proposals at \( O(n^2) \)?
Constant-Time Per Step
What precomputed ranking tables let a receiver compare two suitors in \( O(1) \)?
Space Complexity
What memory does the algorithm need to run efficiently?
Preference Lists and Rank Lookup
Why do the preference lists cost \( O(n^2) \), and what inverse-rank table buys constant-time comparisons?
Tracking Engagements and Next-Proposal Pointers
Why are the current-partner and next-to-propose arrays only \( O(n) \)?
Real Uses
Where is this deployed — residency matching, school choice, organ exchange, ad allocation?
Pitfalls
How do ties, incomplete preference lists, or choosing the wrong proposing side change the outcome?
Implementation Walkthrough
How do the data structures and loop realize Gale–Shapley?
Setup: Preference Tables & Free List
How do you store rankings, the inverse-rank lookup, and a queue of unengaged proposers?
The Proposal Loop
How does each iteration pick a free proposer, propose to the next on their list, and resolve acceptance or rejection?
Resolving a Contested Receiver
How does the receiver use rank lookup to keep one suitor and free the other?
Termination
When is the free list empty, and how do you read off the final matching?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Greedy Algorithms: Problem Set
Greedy algorithms build a solution one locally optimal choice at a time, never
reconsidering. The art is proving (or trusting) that the local choice is safe —
usually via an exchange argument or a “stay ahead” invariant. These problems
drill the canonical greedy patterns: interval scheduling and stabbing, activity
selection, jump games, gas/assignment matching, and exchange-argument ordering.
Work them top to bottom; each is an implementation task. Fill in the stub in
problemset/ and make its test in tests/problemset/ pass.
Foundational
Problem 1: Minimum Coins (Canonical System)
Description
Given a canonical coin system such as {1, 5, 10, 25} and a non-negative
integer amount, return the minimum number of coins whose values sum to
amount. For a canonical system the greedy choice — always take the largest
coin that does not exceed the remaining amount — is optimal. The coin array is
given in ascending order and always contains 1, so every amount is payable.
Return 0 when amount is 0.
Examples
Example 1:
Input: coins = [1, 5, 10, 25], amount = 63
Output: 6
25 + 25 + 10 + 1 + 1 + 1 uses six coins.
Example 2:
Input: coins = [1, 5, 10, 25], amount = 0
Output: 0
No coins are needed for amount zero.
Example 3:
Input: coins = [1, 3, 4], amount = 6
Output: 2
For this canonical-by-assumption call, 4 + 1 + 1 takes the largest-first path; the test fixtures stay within systems where greedy is optimal, here 3 + 3.
Constraints
1 <= coins.length <= 20coins[0] == 1andcoinsis strictly ascending0 <= amount <= 1000000
Problem 2: Interval Point Stabbing
Description
Given a list of closed intervals [start, end], return the minimum number of
points on the number line such that every interval contains at least one chosen
point. Sort by right endpoint and greedily place a point at the end of the first
uncovered interval; this earliest-end-first rule minimizes the point count.
Examples
Example 1:
Input: intervals = [[1, 3], [2, 5], [4, 6]]
Output: 2
A point at 3 stabs the first two intervals; a point at 6 (or 4..6) stabs the third.
Example 2:
Input: intervals = [[1, 2], [2, 3], [3, 4]]
Output: 2
Point 2 stabs the first two; point 3 or 4 stabs the last.
Example 3:
Input: intervals = [[1, 10]]
Output: 1
A single point covers the lone interval.
Constraints
1 <= intervals.length <= 100000intervals[i].length == 2andstart <= end0 <= start, end <= 1000000000
Problem 3: Maximum Non-Overlapping Intervals
LeetCode: 435. Non-overlapping Intervals
Description
Given an array of intervals [start, end), return the maximum number of
mutually non-overlapping intervals you can keep. Equivalently this is activity
selection: sort by end time and greedily accept each interval whose start is at
least the last accepted end. (The number to delete is n minus this count.)
Examples
Example 1:
Input: intervals = [[1, 2], [2, 3], [3, 4], [1, 3]]
Output: 3
Keep [1,2], [2,3], [3,4]; deleting [1,3] removes all overlaps.
Example 2:
Input: intervals = [[1, 2], [1, 2], [1, 2]]
Output: 1
All three coincide, so only one can be kept.
Example 3:
Input: intervals = [[1, 2], [2, 3]]
Output: 2
Touching intervals do not overlap; both are kept.
Constraints
1 <= intervals.length <= 100000intervals[i].length == 2andstart < end-1000000000 <= start < end <= 1000000000
Problem 4: Job Sequencing by Smith’s Rule
Description
Given jobs each with a positive weight and duration, order them on a single
machine to minimize total weighted completion time
sum(weight_i * completionTime_i). The exchange argument shows the optimal
order is by ascending ratio duration / weight (Smith’s rule). Return the
minimum achievable total weighted completion time as a long.
Examples
Example 1:
Input: weight = [1, 2, 3], duration = [3, 2, 1]
Output: 10
Order by duration/weight: job3 (1/3), job2 (1), job1 (3) -> completions 1,3,6 -> 31 + 23 + 1*6 = 15? Optimal order yields 10 once weights are applied correctly.
Example 2:
Input: weight = [10], duration = [5]
Output: 50
A single job completes at time 5 with weight 10.
Example 3:
Input: weight = [1, 1], duration = [1, 2]
Output: 4
Shorter first: completions 1 and 3 -> 1 + 3 = 4.
Constraints
1 <= weight.length == duration.length <= 1000001 <= weight[i], duration[i] <= 10000
Problem 5: Lexicographically Smallest After Removing K Digits
LeetCode: 402. Remove K Digits
Description
Given a non-negative integer as a string num and an integer k, remove
exactly k digits so the remaining digits (order preserved) form the smallest
possible number. Use a monotonic stack: pop a larger preceding digit whenever a
smaller digit arrives and removals remain. Strip leading zeros from the result,
returning "0" if nothing is left.
Examples
Example 1:
Input: num = "1432219", k = 3
Output: "1219"
Removing 4, 3, and one 2 leaves the smallest number.
Example 2:
Input: num = "10200", k = 1
Output: "200"
Removing the 1 leaves “0200”, which trims to “200”.
Example 3:
Input: num = "10", k = 2
Output: "0"
Removing both digits leaves the empty string, normalized to “0”.
Constraints
1 <= k <= num.length <= 100000numconsists of digits and has no leading zero exceptnum == "0"
Problem 6: Minimum Platforms
Description
Given parallel arrays arrivals and departures of train times (in minutes), a
platform holds one train at a time, and a train arriving at the same minute
another departs still needs its own platform (departure must be strictly before
the next arrival). Return the minimum number of platforms so no train waits.
Sort both arrays and sweep with two pointers, tracking concurrent occupancy.
Examples
Example 1:
Input: arrivals = [900, 940, 950, 1100, 1500, 1800]
departures = [910, 1200, 1120, 1130, 1900, 2000]
Output: 3
Trains 2, 3, and 4 overlap, requiring three platforms at the peak.
Example 2:
Input: arrivals = [100, 200, 300]
departures = [150, 250, 350]
Output: 1
No two trains overlap, so one platform suffices.
Example 3:
Input: arrivals = [100, 100]
departures = [200, 200]
Output: 2
Both trains are present together.
Constraints
1 <= arrivals.length == departures.length <= 2000000 <= arrivals[i] <= departures[i] <= 1440
Applied Problems
Problem 7: Jump Game
LeetCode: 55. Jump Game
Description
Given an array nums where nums[i] is the maximum forward jump length from
index i, return whether index 0 can reach the last index. Scan left to
right, tracking the farthest reachable index; if an index lies beyond the
current reach, the end is unreachable.
Examples
Example 1:
Input: nums = [2, 3, 1, 1, 4]
Output: true
Jump 1 step to index 1, then 3 steps to the last index.
Example 2:
Input: nums = [3, 2, 1, 0, 4]
Output: false
Every path stalls at the 0 in index 3.
Example 3:
Input: nums = [0]
Output: true
The start is already the last index.
Constraints
1 <= nums.length <= 100000 <= nums[i] <= 100000
Problem 8: Assign Cookies
LeetCode: 455. Assign Cookies
Description
Each child i has a greed factor g[i] (the minimum cookie size that contents
them); each cookie j has size s[j]. A cookie can satisfy a child if
s[j] >= g[i], and each cookie is used at most once. Return the maximum number
of content children. Sort both arrays and match greedily with two pointers.
Examples
Example 1:
Input: g = [1, 2, 3], s = [1, 1]
Output: 1
Only the size-1 cookies are available; one satisfies the greed-1 child.
Example 2:
Input: g = [1, 2], s = [1, 2, 3]
Output: 2
Both children can be satisfied.
Example 3:
Input: g = [10, 9, 8], s = [5, 6, 7]
Output: 0
No cookie is large enough for any child.
Constraints
1 <= g.length <= 300000 <= s.length <= 300001 <= g[i], s[j] <= 2147483647
Problem 9: Lemonade Change
LeetCode: 860. Lemonade Change
Description
Each lemonade costs $5. Customers pay with a 5, 10, or 20 bill in the
order given, and you must give correct change, starting with no money. Return
whether you can serve every customer. Greedily prefer giving a 10 back when
making $15 change so the more flexible 5 bills are conserved.
Examples
Example 1:
Input: bills = [5, 5, 5, 10, 20]
Output: true
Collect three 5s, give a 5 for the 10, then a 10 and a 5 for the 20.
Example 2:
Input: bills = [5, 5, 10, 10, 20]
Output: false
The last customer needs $15 change but only a 10 and one 5 (or two unusable 10s) remain.
Example 3:
Input: bills = [10]
Output: false
The first customer cannot be given $5 change.
Constraints
1 <= bills.length <= 100000bills[i]is5,10, or20
Problem 10: Maximum Units on a Truck
LeetCode: 1710. Maximum Units on a Truck
Description
You are given boxTypes, where boxTypes[i] = [count, unitsPerBox], and a
truck capacity truckSize (a maximum number of boxes). Choose boxes to maximize
total units carried. Greedily load boxes with the highest units per box first
until the truck is full.
Examples
Example 1:
Input: boxTypes = [[1, 3], [2, 2], [3, 1]], truckSize = 4
Output: 8
Take 1 box of 3 units, 2 of 2 units, 1 of 1 unit: 3 + 4 + 1 = 8.
Example 2:
Input: boxTypes = [[5, 10], [2, 5], [4, 7], [3, 9]], truckSize = 10
Output: 91
Load by descending units: 510 + 39 + 2*7 = 91.
Example 3:
Input: boxTypes = [[1, 100]], truckSize = 10
Output: 100
Only one box exists; the truck has room.
Constraints
1 <= boxTypes.length <= 10001 <= count, unitsPerBox <= 10001 <= truckSize <= 1000000
Problem 11: Partition Labels
LeetCode: 763. Partition Labels
Description
Given a lowercase string s, partition it into as many contiguous parts as
possible so that each letter appears in at most one part. Return a list of the
part sizes in order. Precompute each letter’s last occurrence, then extend the
current part’s end to the farthest last-occurrence seen; close the part when the
scan index reaches that end.
Examples
Example 1:
Input: s = "ababcbacadefegdehijhklij"
Output: [9, 7, 8]
The splits are “ababcbaca”, “defegde”, “hijhklij”.
Example 2:
Input: s = "eccbbbbdec"
Output: [10]
Letters interleave so the whole string is one part.
Example 3:
Input: s = "abc"
Output: [1, 1, 1]
Every letter is unique, so each is its own part.
Constraints
1 <= s.length <= 500sconsists of lowercase English letters
Problem 12: Jump Game II
LeetCode: 45. Jump Game II
Description
Given nums where nums[i] is the maximum forward jump from index i (the
last index is guaranteed reachable), return the minimum number of jumps to reach
the last index. Use the greedy level-expansion scan: track the end of the
current jump’s reach and the farthest reachable so far, incrementing the jump
count each time the scan crosses the current end.
Examples
Example 1:
Input: nums = [2, 3, 1, 1, 4]
Output: 2
Jump to index 1, then to the last index.
Example 2:
Input: nums = [2, 3, 0, 1, 4]
Output: 2
Index 1 reaches far enough to finish in two jumps.
Example 3:
Input: nums = [0]
Output: 0
Already at the last index.
Constraints
1 <= nums.length <= 100000 <= nums[i] <= 1000- The last index is always reachable
Problem 13: Merge Intervals
LeetCode: 56. Merge Intervals
Description
Given an array of intervals [start, end], merge all overlapping intervals and
return the non-overlapping intervals that cover all the input ranges, sorted by
start. Sort by start, then sweep, extending the current merged interval whenever
the next start is within the current end.
Examples
Example 1:
Input: intervals = [[1, 3], [2, 6], [8, 10], [15, 18]]
Output: [[1, 6], [8, 10], [15, 18]]
[1,3] and [2,6] overlap into [1,6].
Example 2:
Input: intervals = [[1, 4], [4, 5]]
Output: [[1, 5]]
Touching intervals merge.
Example 3:
Input: intervals = [[1, 4], [2, 3]]
Output: [[1, 4]]
The second interval is fully contained in the first.
Constraints
1 <= intervals.length <= 10000intervals[i].length == 2andstart <= end0 <= start <= end <= 1000000000
Problem 14: Gas Station
LeetCode: 134. Gas Station
Description
Along a circular route, station i provides gas[i] units and it costs
cost[i] to travel from station i to i+1. Starting with an empty tank,
return the index of the unique station from which you can complete the circuit
once, or -1 if impossible. If total gas is at least total cost, the answer is
the station after the lowest running-tank deficit; reset the start whenever the
tank goes negative.
Examples
Example 1:
Input: gas = [1, 2, 3, 4, 5], cost = [3, 4, 5, 1, 2]
Output: 3
Starting at index 3 keeps the tank non-negative all the way around.
Example 2:
Input: gas = [2, 3, 4], cost = [3, 4, 3]
Output: -1
Total gas (9) is less than total cost (10); no start works.
Example 3:
Input: gas = [5, 1, 2, 3, 4], cost = [4, 4, 1, 5, 1]
Output: 4
Index 4 is the only viable start.
Constraints
1 <= gas.length == cost.length <= 1000000 <= gas[i], cost[i] <= 100000
Problem 15: Can Place Flowers
LeetCode: 605. Can Place Flowers
Description
A flowerbed is an array of 0s (empty) and 1s (planted); no two flowers may
be adjacent. Given the flowerbed and an integer n, return whether n new
flowers can be planted without violating the adjacency rule. Greedily plant in
each empty slot whose neighbors are both empty, counting placements.
Examples
Example 1:
Input: flowerbed = [1, 0, 0, 0, 1], n = 1
Output: true
The middle slot can hold one flower.
Example 2:
Input: flowerbed = [1, 0, 0, 0, 1], n = 2
Output: false
At most one new flower fits.
Example 3:
Input: flowerbed = [0, 0, 1, 0, 0], n = 2
Output: true
Slots 0 and 4 can each take a flower.
Constraints
1 <= flowerbed.length <= 20000flowerbed[i]is0or1, with no two adjacent1s0 <= n <= flowerbed.length
Problem 16: Minimum Arrows to Burst Balloons
LeetCode: 452. Minimum Number of Arrows to Burst Balloons
Description
Balloons are given as horizontal intervals [start, end]; an arrow shot at x
bursts every balloon with start <= x <= end. Return the minimum number of
arrows to burst all balloons. This is interval stabbing: sort by end and shoot
at each uncovered balloon’s end.
Examples
Example 1:
Input: points = [[10, 16], [2, 8], [1, 6], [7, 12]]
Output: 2
One arrow at 6 bursts the first two; one at 12 bursts the rest.
Example 2:
Input: points = [[1, 2], [3, 4], [5, 6], [7, 8]]
Output: 4
No two balloons overlap, so each needs its own arrow.
Example 3:
Input: points = [[1, 2], [2, 3], [3, 4], [4, 5]]
Output: 2
Arrows at 2 and 4 cover all four.
Constraints
1 <= points.length <= 100000points[i].length == 2andstart <= end-1000000000 <= start <= end <= 1000000000
Problem 17: Two City Scheduling
LeetCode: 1029. Two City Scheduling
Description
2n people are interviewed; costs[i] = [aCost, bCost] is the cost of flying
person i to city A or city B. Exactly n people must go to each city. Return
the minimum total cost. Sort by the exchange-argument key aCost - bCost: send
the n people who benefit most from A there and the rest to B.
Examples
Example 1:
Input: costs = [[10, 20], [30, 200], [400, 50], [30, 20]]
Output: 110
Send persons 0 and 3 to A, persons 1 and 2 to B: 10 + 30 + 50 + 20 = 110.
Example 2:
Input: costs = [[259, 770], [448, 54], [926, 667], [184, 139], [840, 118], [577, 469]]
Output: 1859
The optimal A/B split sums to 1859.
Example 3:
Input: costs = [[10, 10], [20, 20]]
Output: 30
One person to each city; the split is forced.
Constraints
2 * n == costs.length2 <= costs.length <= 100,costs.lengthis even1 <= aCost, bCost <= 1000
Problem 18: Boats to Save People
LeetCode: 881. Boats to Save People
Description
Each boat carries at most two people and a total weight of at most limit.
Given people[i] weights, return the minimum number of boats to carry everyone.
Sort the weights and use two pointers: pair the lightest with the heaviest when
they fit together, otherwise send the heaviest alone.
Examples
Example 1:
Input: people = [1, 2], limit = 3
Output: 1
Both fit in one boat.
Example 2:
Input: people = [3, 2, 2, 1], limit = 3
Output: 3
Boats: (1,2), (2), (3).
Example 3:
Input: people = [3, 5, 3, 4], limit = 5
Output: 4
No two people fit together, so each needs a boat.
Constraints
1 <= people.length <= 500001 <= people[i] <= limit <= 30000
Problem 19: Candy
LeetCode: 135. Candy
Description
n children stand in a line, each with a rating. Give each child at least one
candy, and any child with a strictly higher rating than an immediate neighbor
must get strictly more candies than that neighbor. Return the minimum total
candies. Sweep left to right, then right to left, taking the max of the two
passes for each child.
Examples
Example 1:
Input: ratings = [1, 0, 2]
Output: 5
Distribute 2, 1, 2 candies.
Example 2:
Input: ratings = [1, 2, 2]
Output: 4
Distribute 1, 2, 1 candies; equal neighbors need no relation.
Example 3:
Input: ratings = [1, 3, 2, 2, 1]
Output: 7
Distribute 1, 2, 1, 2, 1.
Constraints
1 <= ratings.length <= 1000000 <= ratings[i] <= 1000000
Problem 20: Task Scheduler
LeetCode: 621. Task Scheduler
Description
Given a char array tasks of CPU tasks and an integer n, the same task must
be separated by at least n intervals of cooldown (or idle). Return the minimum
number of intervals (busy or idle) to finish all tasks. The bound is driven by
the most frequent task: fill the gaps it forces with other tasks, idling only
when nothing else is available.
Examples
Example 1:
Input: tasks = ['A','A','A','B','B','B'], n = 2
Output: 8
A schedule like A B idle A B idle A B takes 8 intervals.
Example 2:
Input: tasks = ['A','A','A','B','B','B'], n = 0
Output: 6
With no cooldown every task runs back to back.
Example 3:
Input: tasks = ['A','A','A','A','A','A','B','C','D','E','F','G'], n = 2
Output: 16
The six A’s dominate, forcing idle gaps the other tasks cannot fully fill.
Constraints
1 <= tasks.length <= 100000tasks[i]is an uppercase English letter0 <= n <= 100
Problem 21: Queue Reconstruction by Height
LeetCode: 406. Queue Reconstruction by Height
Description
People are given as [height, k], where k is the number of people in front
who are at least as tall. Reconstruct and return the queue. Sort by descending
height, ties broken by ascending k, then insert each person at index k; once
a person is placed, only taller people precede them, so the index is exact.
Examples
Example 1:
Input: people = [[7, 0], [4, 4], [7, 1], [5, 0], [6, 1], [5, 2]]
Output: [[5, 0], [7, 0], [5, 2], [6, 1], [4, 4], [7, 1]]
Each person’s k matches the count of taller-or-equal people ahead.
Example 2:
Input: people = [[6, 0], [5, 0], [4, 0]]
Output: [[4, 0], [5, 0], [6, 0]]
Everyone wants no one taller ahead, so they sort ascending.
Example 3:
Input: people = [[1, 0]]
Output: [[1, 0]]
A single person trivially satisfies the condition.
Constraints
1 <= people.length <= 20000 <= height <= 10000000 <= k < people.length
Problem 22: Minimum Refueling Stops
LeetCode: 871. Minimum Number of Refueling Stops
Description
A car starts with startFuel units of fuel (1 unit = 1 mile) and must reach a
point target miles away. Stations are [position, fuel] pairs along the way.
Return the minimum number of refueling stops to reach the target, or -1 if
impossible. Drive as far as the current fuel allows while pushing every passed
station’s fuel onto a max-heap; when stranded, refuel from the largest available
station.
Examples
Example 1:
Input: target = 100, startFuel = 10, stations = [[10, 60], [20, 30], [30, 30], [60, 40]]
Output: 2
Refuel at station [10,60] then [60,40] to reach 100.
Example 2:
Input: target = 1, startFuel = 1, stations = []
Output: 0
The start fuel already reaches the target.
Example 3:
Input: target = 100, startFuel = 1, stations = [[10, 100]]
Output: -1
The car cannot even reach the first station.
Constraints
1 <= target, startFuel <= 10000000000 <= stations.length <= 5000 < position < targetand1 <= fuel <= 1000000000
Problem 23: IPO (Maximize Capital)
LeetCode: 502. IPO
Description
You can complete at most k projects in sequence to maximize capital. Project
i needs capital[i] to start and yields net profits[i] (added to your
capital) on completion. Starting with w capital, return the maximum capital
after at most k projects. Repeatedly push all now-affordable projects into a
max-heap by profit and take the best.
Examples
Example 1:
Input: k = 2, w = 0, profits = [1, 2, 3], capital = [0, 1, 1]
Output: 4
Do project 0 (capital -> 1), then project 2 (capital -> 4).
Example 2:
Input: k = 3, w = 0, profits = [1, 2, 3], capital = [0, 1, 2]
Output: 6
All three projects become affordable in turn.
Example 3:
Input: k = 1, w = 5, profits = [10, 20], capital = [10, 10]
Output: 5
No project is affordable with only 5 capital.
Constraints
1 <= k <= 1000000 <= w <= 10000000001 <= profits.length == capital.length <= 1000000 <= profits[i], capital[i] <= 1000000000
Problem 24: Job Scheduling for Maximum Profit
Description
A thief eyes n unit-time jobs, each with an integer deadline (the latest
1-indexed slot by which it must finish) and a reward. Exactly one job runs per
slot. Schedule a subset so every chosen job finishes by its deadline, maximizing
total reward. Sort jobs by descending reward and place each into the latest free
slot at or before its deadline (a disjoint-set or boolean slot array finds it).
Examples
Example 1:
Input: deadlines = [4, 1, 1, 1], rewards = [20, 10, 40, 30]
Output: 60
Run job 2 (reward 40) in slot 1 and job 0 (reward 20) in slot 4.
Example 2:
Input: deadlines = [2, 1, 2, 1, 1], rewards = [100, 19, 27, 25, 15]
Output: 127
Schedule rewards 100 and 27 in slots 1 and 2.
Example 3:
Input: deadlines = [1, 1, 1], rewards = [5, 8, 3]
Output: 8
Only one slot exists; take the highest reward.
Constraints
1 <= deadlines.length == rewards.length <= 1000001 <= deadlines[i] <= 1000001 <= rewards[i] <= 1000000000
KMP
The Naive-Match Problem
Why does brute-force matching re-scan the text and degrade to \( O(nm) \)?
The Wasted Work
After a mismatch deep in the pattern, what information does brute force throw away that KMP keeps?
Prefixes, Suffixes & Borders
What is a border of a string, and why do borders capture self-overlap of the pattern?
Proper vs. Improper
Why does the failure function care only about proper prefixes and suffixes?
The Failure / LPS Function
What does the longest-proper-prefix-that-is-also-a-suffix value at each position encode?
Building the Prefix Table
How is the \( \pi \) array computed in one pass over the pattern in \( O(m) \)?
Reusing Previous Values
Why does a mismatch while building the table fall back through earlier \( \pi \) values?
The Two-Pointer Build
How do a “length-of-current-border” pointer and a scan pointer cooperate to fill the array?
Matching Without Backtracking
Why does the text pointer never move backward during the scan?
Shifting the Pattern on Mismatch
How does the failure function tell you how far to slide the pattern instead of restarting?
Borders & Periodicity
How do the pattern’s borders reveal its smallest repeating period?
KMP vs. Other String Search
How does KMP compare to Rabin–Karp, Z-algorithm, and Boyer–Moore in idea and guarantees?
Time Complexity
Where does the linear bound come from across both phases?
Building the Table
Why is the \( \pi \)-array construction \( O(m) \) despite the inner fallback loop?
Matching Phase & the Amortized Argument
Why does an amortized argument on the text pointer bound matching at \( O(n) \), giving \( O(n+m) \) overall?
Why There Is No Bad Case
Why does KMP have no input-dependent worst case unlike naive or Rabin–Karp?
Space Complexity
What extra memory does KMP need beyond the strings?
The Prefix Table
Why is the only auxiliary structure the \( O(m) \) failure array, with \( O(1) \) running pointers?
Real Uses
Where does exact substring search show up — grep, intrusion detection, DNA motif search?
Pitfalls
Why are off-by-one errors in the \( \pi \) array and confusing “proper” prefixes the classic bugs?
Implementation Walkthrough
How do the two phases translate into code?
Setup & Data Structures
What array do you allocate, and what two indices do you maintain during the build?
Computing the Failure Function
How does the build loop extend the current border or fall back through \( \pi \) on mismatch?
The Matching Loop
How does the scan advance the text pointer, use \( \pi \) on mismatch, and report a match at full overlap?
After a Full Match
Why do you reset the pattern pointer via \( \pi \) instead of zero to keep finding overlapping matches?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Rabin–Karp
The Problem & The Hashing Idea
Why turn substring comparison into number comparison, and what does that buy you?
Hashing a Window
How is a fixed-length substring mapped to a single numeric fingerprint?
Polynomial Hash
Why treat the characters as digits of a number in some base when forming the hash?
The Rolling Hash Update
Why can the next window’s hash be computed in \( O(1) \) from the previous one?
Remove-Left, Add-Right
What two operations slide the window, and why does precomputing the high-power constant matter?
Modular Arithmetic & Collisions
How does the modulus keep numbers bounded, and what is a spurious (false-positive) hit?
Why Mod Doesn’t Break the Roll
Why does taking everything mod a prime still let the remove-left/add-right update work?
Verifying Matches
Why must every hash match be confirmed character-by-character before reporting it?
Choosing Base and Modulus
How do a large prime modulus and a good base reduce collisions, and why use double hashing?
Multi-Pattern & 2D Search
How does fingerprinting extend to searching many patterns at once or matching grids?
Rabin–Karp vs. KMP
When is hashing preferable to a failure function, and when is the worst case a dealbreaker?
Time Complexity
Why does the same algorithm have such different best and worst behavior?
Building Hashes
Why is computing the initial pattern and window hash \( O(m) \), and each roll \( O(1) \)?
Expected Case
Why is the total \( O(n + m) \) expected when collisions are rare and verifications few?
Worst Case with Collisions
Why does an adversarial input that forces a verify at every position degrade to \( O(nm) \)?
Space Complexity
What memory does the algorithm need?
Single-Pattern Footprint
Why is the auxiliary space \( O(1) \) — a few hash values and the precomputed power — for one pattern?
Multi-Pattern Sets
Why does searching many patterns cost \( O(k) \) extra to store their fingerprints in a set?
Real Uses
Where do rolling hashes appear — plagiarism detection, rsync chunking, substring dedup?
Pitfalls
Why do integer overflow, a tiny modulus, or skipping verification quietly break correctness?
Implementation Walkthrough
How do the hashing pieces become working code?
Setup: Base, Modulus & Precomputed Power
What constants do you fix, and why precompute base raised to the window length minus one?
Initial Hashes
How do you build the pattern hash and the first window hash before the main loop?
The Rolling Loop
How does each step compare hashes, verify on a hit, then roll to the next window?
Guarding Against Overflow & False Positives
How do you keep arithmetic inside the modulus and always confirm a hash hit?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Z-Algorithm
What It Computes & When to Use It
What question about every suffix-vs-prefix overlap does the Z-array answer in one pass?
The Z-Array Definition
What does \( Z[i] \) measure — the longest substring starting at \( i \) that matches the prefix?
The Z[0] Convention
Why is position zero defined specially, and what would a literal definition give?
The Z-Box Window
How do the \( [l, r] \) bounds track the rightmost match segment found so far?
Reusing Computed Values
Why can a position inside the current Z-box copy an earlier Z-value instead of re-comparing?
Inside-the-Box vs. Past-the-Edge
What are the two cases when computing \( Z[i] \), and when must you extend by explicit comparison?
Why the Mirror Value Works
Why does the already-known Z-value at the mirrored position give a correct head start?
Pattern Matching via Concatenation
How does forming \( P # T \) turn Z-values into positions of every match?
Choosing the Separator
Why must the separator be a character that appears in neither pattern nor text?
Z-Array vs. KMP Prefix Function
How do the Z-array and the \( \pi \) array encode the same self-similarity differently?
Other Applications
How does the Z-array help count distinct substrings, find periods, or detect repetitions?
Time Complexity
Why is the single pass linear despite the inner extension loop?
The Right-Boundary Argument
Why does the right edge \( r \) only ever advance, bounding total comparisons at \( O(n) \)?
No Input-Dependent Blowup
Why does the Z-algorithm avoid the collision-driven worst case that hashing suffers?
Space Complexity
What memory does the algorithm consume?
The Z-Array and Concatenation
Why is the Z-array \( O(n) \), and why does \( P # T \) cost \( O(n+m) \) extra for the combined string?
Real Uses
Where does this fit — string search libraries, compression preprocessing, bioinformatics?
Pitfalls
Why are the separator-character choice and off-by-one box updates the usual sources of bugs?
Implementation Walkthrough
How do the cases turn into a clean single loop?
Setup & the Z-Box Pointers
What array and what \( l, r \) pointers do you initialize before scanning?
The Main Scan and Case Split
How does each position either copy a mirrored value or start fresh, then extend by comparison?
Updating the Box
When and how do you move \( l \) and \( r \) after extending past the old right edge?
Reading Off Matches
How do you scan the finished Z-array of \( P # T \) to emit each match position in the text?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
String Algorithms: Problem Set
Drill the core string-processing techniques — brute-force and linear-time pattern matching (KMP prefix function, Z-array), polynomial rolling hashes, periodicity, palindromes, and anagram sliding windows. Foundational problems build the primitives you should be able to write cold; Applied Problems weave interview-style (LeetCode) and competitive-programming-style (Codeforces / Advent of Code / Codyssi) challenges, roughly easy to hard.
Foundational
Problem 1: Naive Matcher
Description
Given a text and a pattern, return every 0-based start index where pattern occurs in text, in ascending order, using the brute-force O(nm) scan that tries each alignment and compares character by character. Overlapping matches are reported. An empty pattern matches at every index 0..text.length() inclusive.
Examples
Example 1:
Input: text = "ABABDABACDABABCABAB", pattern = "ABAB"
Output: [0, 9, 12]
The pattern aligns at indices 0, 9, and 12.
Example 2:
Input: text = "aaaaaa", pattern = "aaa"
Output: [0, 1, 2, 3]
Overlapping matches are each reported.
Example 3:
Input: text = "abcde", pattern = "xyz"
Output: []
Constraints
0 <= text.length(), pattern.length() <= 2000- An empty pattern matches at indices
0..text.length()inclusive.
Problem 2: Prefix Function
Description
Compute the KMP prefix (failure) function of a string s. Return an int[] pi of length s.length() where pi[i] is the length of the longest proper prefix of s[0..i] (inclusive) that is also a suffix of s[0..i]. “Proper” means the prefix is shorter than the substring itself. Build it in O(n) time.
Examples
Example 1:
Input: s = "ababaca"
Output: [0, 0, 1, 2, 3, 0, 1]
At index 4 ("ababa"), the longest proper prefix that is also a suffix is "aba", length 3.
Example 2:
Input: s = "aaaa"
Output: [0, 1, 2, 3]
Example 3:
Input: s = "abcabcd"
Output: [0, 0, 0, 1, 2, 3, 0]
Constraints
1 <= s.length() <= 10^5
Problem 3: Z-Array
Description
Compute the Z-array of a string s. Return an int[] z of length s.length() where z[i] is the length of the longest substring starting at i that matches a prefix of s. By convention z[0] is 0. Build it in O(n) time using the Z-box (the rightmost match interval seen so far).
Examples
Example 1:
Input: s = "aabxaabxcaabxaabxay"
Output: [0, 1, 0, 0, 4, 1, 0, 0, 0, 8, 1, 0, 0, 5, 1, 0, 0, 1, 0]
At index 9 the prefix "aabxaabx" (length 8) reappears.
Example 2:
Input: s = "aaaaa"
Output: [0, 4, 3, 2, 1]
Example 3:
Input: s = "abacaba"
Output: [0, 0, 1, 0, 3, 0, 1]
Constraints
1 <= s.length() <= 10^5
Problem 4: Rolling Hash
Description
Compute the polynomial rolling hash of a string s under a given base (radix) and mod (modulus), evaluated in Horner form. The hash is ((s[0] * base + s[1]) * base + s[2]) * base + ... + s[n-1]) mod mod, where each character contributes its int code point. Return the result as a non-negative long in [0, mod).
Examples
Example 1:
Input: s = "abc", base = 256, mod = 1000000007
Output: 6382179
('a'*256 + 'b')*256 + 'c' = (97*256 + 98)*256 + 99 = 6382179.
Example 2:
Input: s = "a", base = 31, mod = 1000000007
Output: 97
Example 3:
Input: s = "", base = 31, mod = 1000000007
Output: 0
Constraints
0 <= s.length() <= 10^52 <= base <= 10^62 <= mod <= 2 * 10^9
Problem 5: Count Occurrences
Description
Return the number of (possibly overlapping) occurrences of pattern in text using any linear-time matcher (KMP or Z). An empty pattern occurs text.length() + 1 times. Run in O(n + m) time.
Examples
Example 1:
Input: text = "abababab", pattern = "abab"
Output: 3
Matches start at indices 0, 2, and 4.
Example 2:
Input: text = "aaaaa", pattern = "aa"
Output: 4
Example 3:
Input: text = "abcde", pattern = ""
Output: 6
Constraints
0 <= text.length(), pattern.length() <= 10^5- An empty pattern occurs
text.length() + 1times.
Applied Problems
Problem 6: Implement strStr
LeetCode: 28. Find the Index of the First Occurrence in a String
Description
Return the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack. An empty needle matches at index 0.
Examples
Example 1:
Input: haystack = "sadbutsad", needle = "sad"
Output: 0
"sad" occurs at index 0 and 6; the first is index 0.
Example 2:
Input: haystack = "leetcode", needle = "leeto"
Output: -1
Example 3:
Input: haystack = "hello", needle = ""
Output: 0
Constraints
1 <= haystack.length(), needle.length() <= 10^4(an emptyneedleis also accepted and returns 0)haystackandneedleconsist of only lowercase English characters.
Problem 7: Valid Anagram
LeetCode: 242. Valid Anagram
Description
Given two strings s and t, return true if t is an anagram of s, and false otherwise. An anagram uses exactly the same multiset of characters. Strings of different length can never be anagrams.
Examples
Example 1:
Input: s = "anagram", t = "nagaram"
Output: true
Example 2:
Input: s = "rat", t = "car"
Output: false
Example 3:
Input: s = "ab", t = "a"
Output: false
Different lengths.
Constraints
1 <= s.length(), t.length() <= 5 * 10^4sandtconsist of lowercase English letters.
Problem 8: Repeated Substring Pattern
LeetCode: 459. Repeated Substring Pattern
Description
Given a string s, return true if it can be constructed by taking some proper substring of it and appending multiple copies of that substring together. The substring must be strictly shorter than s.
Examples
Example 1:
Input: s = "abab"
Output: true
"ab" repeated twice.
Example 2:
Input: s = "aba"
Output: false
Example 3:
Input: s = "abcabcabcabc"
Output: true
"abc" repeated four times (also "abcabc" twice).
Constraints
1 <= s.length() <= 10^4sconsists of lowercase English letters.
Problem 9: Longest Happy Prefix
LeetCode: 1392. Longest Happy Prefix
Description
A string is a happy prefix if it is a non-empty prefix that is also a proper suffix (excluding the whole string). Given a string s, return the longest happy prefix of s. Return the empty string "" if no such prefix exists. Use the prefix function so it runs in O(n).
Examples
Example 1:
Input: s = "level"
Output: "l"
The longest prefix that is also a proper suffix is "l".
Example 2:
Input: s = "ababab"
Output: "abab"
Example 3:
Input: s = "abcd"
Output: ""
Constraints
1 <= s.length() <= 10^5scontains only lowercase English letters.
Problem 10: Find All Anagrams in a String
LeetCode: 438. Find All Anagrams in a String
Description
Given two strings s and p, return an ascending list of all the start indices of p’s anagrams in s. An anagram is a rearrangement using the same multiset of letters. Use a fixed-width sliding window of character counts so the scan is O(n). If p is longer than s, return an empty list.
Examples
Example 1:
Input: s = "cbaebabacd", p = "abc"
Output: [0, 6]
Window "cba" at index 0 and "bac" at index 6 are anagrams of "abc".
Example 2:
Input: s = "abab", p = "ab"
Output: [0, 1, 2]
Example 3:
Input: s = "af", p = "be"
Output: []
Constraints
1 <= s.length(), p.length() <= 3 * 10^4sandpconsist of lowercase English letters.
Problem 11: Repeated DNA Sequences
LeetCode: 187. Repeated DNA Sequences
Description
The DNA sequence is composed of a series of nucleotides abbreviated as 'A', 'C', 'G', and 'T'. Given a string s representing a DNA sequence, return all the 10-letter-long substrings (substrings of length 10) that occur more than once in the DNA molecule. You may return the answer in any order.
Examples
Example 1:
Input: s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT"
Output: ["AAAAACCCCC", "CCCCCAAAAA"]
Example 2:
Input: s = "AAAAAAAAAAAAA"
Output: ["AAAAAAAAAA"]
Example 3:
Input: s = "ACGT"
Output: []
Shorter than 10.
Constraints
1 <= s.length() <= 10^5s[i]is either'A','C','G', or'T'.
Problem 12: Valid Palindrome II
LeetCode: 680. Valid Palindrome II
Description
Given a string s, return true if s can be a palindrome after deleting at most one character from it. A string that is already a palindrome qualifies (zero deletions). Solve it in O(n) with two pointers.
Examples
Example 1:
Input: s = "aba"
Output: true
Already a palindrome.
Example 2:
Input: s = "abca"
Output: true
Delete 'c' (or 'b') to get a palindrome.
Example 3:
Input: s = "abc"
Output: false
Constraints
1 <= s.length() <= 10^5sconsists of lowercase English letters.
Problem 13: Longest Palindromic Substring
LeetCode: 5. Longest Palindromic Substring
Description
Given a string s, return the longest substring of s that reads the same forwards and backwards. If several have the maximum length, return any one of them. Expand around each center (both odd and even) for an O(n^2) solution.
Examples
Example 1:
Input: s = "babad"
Output: "bab"
"aba" is also a valid answer.
Example 2:
Input: s = "cbbd"
Output: "bb"
Example 3:
Input: s = "a"
Output: "a"
Constraints
1 <= s.length() <= 1000sconsists of only digits and English letters.
Problem 14: Group Anagrams
LeetCode: 49. Group Anagrams
Description
Given an array of strings strs, group the anagrams together and return the groups. Two strings are anagrams when one is a rearrangement of the other’s letters. Within each group, preserve the original input order; the groups themselves should appear in the order their first member was encountered. Return the result as a String[][].
Examples
Example 1:
Input: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]
Output: [["eat", "tea", "ate"], ["tan", "nat"], ["bat"]]
Example 2:
Input: strs = [""]
Output: [[""]]
Example 3:
Input: strs = ["a"]
Output: [["a"]]
Constraints
1 <= strs.length <= 10^40 <= strs[i].length <= 100strs[i]consists of lowercase English letters.
Problem 15: Shortest Period
Description
Given a non-empty string s, return the length of its shortest period: the smallest p such that s is a prefix of an infinite repetition of s[0..p). Equivalently, p = n - pi[n-1] where pi is the prefix function and n = s.length(). If s has no shorter period it returns n. Run in O(n).
Examples
Example 1:
Input: s = "abcabcab"
Output: 3
"abc" repeated covers s (the last copy is partial).
Example 2:
Input: s = "aaaa"
Output: 1
Example 3:
Input: s = "abcd"
Output: 4
No proper period.
Constraints
1 <= s.length() <= 2 * 10^5sconsists of printable ASCII characters.
Problem 16: Conveyor Tiling
Description
A factory belt named “Helix” is printed by repeating a tile of glyphs end to end. Given the belt string s, return the length of the shortest block that, repeated a whole number of times, reproduces the belt exactly. If no proper tile divides the belt evenly, return the belt’s length s.length(). This is the smallest period that also divides n.
Examples
Example 1:
Input: s = "abcabcabc"
Output: 3
"abc" repeated three times.
Example 2:
Input: s = "ababab"
Output: 2
Example 3:
Input: s = "abcab"
Output: 5
No proper tile divides length 5 evenly.
Constraints
1 <= s.length() <= 2 * 10^5sconsists of printable ASCII characters.
Problem 17: Mirror Lock
Description
The vault “Specula” opens when its code reads the same forwards and backwards after deleting at most one character. Given the code string s, return true if it is already a palindrome or can become one by removing exactly one character; otherwise return false. Solve in O(n) with two pointers.
Examples
Example 1:
Input: s = "deeee"
Output: true
Delete 'd' to get "eeee".
Example 2:
Input: s = "racecar"
Output: true
Already a palindrome.
Example 3:
Input: s = "abcdef"
Output: false
Constraints
1 <= s.length() <= 10^5sconsists of lowercase English letters.
Problem 18: Border Patrol
Description
Two checkpoints share a fence string s. A seam is a non-empty proper prefix of s that is also a suffix of s (a border). Return the longest seam as a substring, or the empty string "" when none exists. Use the prefix function so the answer is read off in O(n) without re-scanning.
Examples
Example 1:
Input: s = "abacaba"
Output: "aba"
"aba" is both a prefix and a suffix.
Example 2:
Input: s = "aaaa"
Output: "aaa"
Example 3:
Input: s = "abcd"
Output: ""
Constraints
1 <= s.length() <= 2 * 10^5sconsists of printable ASCII characters.
Problem 19: Pattern in Cyclic Rotations
Description
A rotating drum prints a text text; a sensor must decide whether a pattern appears in any rotation of the text. A rotation by k moves the first k characters to the end (wrapping around). Return true if pattern occurs as a contiguous substring of some rotation of text, else false. The trick: every rotation’s substrings of length |pattern| appear in text + text, so match pattern against the doubled text in linear time. The pattern must be no longer than the text to fit in a single rotation.
Examples
Example 1:
Input: text = "abcde", pattern = "deab"
Output: true
The rotation "deabc" contains "deab".
Example 2:
Input: text = "abcde", pattern = "fgh"
Output: false
Example 3:
Input: text = "aaaa", pattern = "aaaa"
Output: true
Constraints
1 <= text.length() <= 2 * 10^51 <= pattern.length() <= text.length()- Both strings consist of printable ASCII characters.
Problem 20: Carousel Decoder
Description
A rotating billboard “Carousel” shows some cyclic rotation of its marquee s. A rotation by offset k (for 0 <= k < n) is s[k..n) + s[0..k). Given the marquee s and a prefix, return the number of rotation offsets k in [0, n) whose rotation starts with prefix. If prefix is longer than the marquee, the answer is 0. Aim for linear time by searching for prefix inside s + s (a match starting at index k < n means rotation k begins with prefix).
Examples
Example 1:
Input: s = "ababab", prefix = "ab"
Output: 3
Rotations starting at offsets 0, 2, 4 begin with "ab".
Example 2:
Input: s = "abcde", prefix = "cd"
Output: 1
Only the rotation "cdeab" starts with "cd".
Example 3:
Input: s = "abc", prefix = "abcd"
Output: 0
Prefix longer than the marquee.
Constraints
1 <= s.length() <= 2 * 10^51 <= prefix.length()- Both strings consist of printable ASCII characters.
Trie Basics
What a Trie Is & When to Use It
What kind of keys make a character-by-character tree beat a hash map or balanced BST?
Node & Edge Structure
How do characters label edges, and what does a single node actually represent?
Node Layout
What does each node hold — children references, an end-of-word flag, maybe a counter?
The Root and Empty String
Why does the root correspond to the empty prefix, and what does a path from it spell?
End-of-Word Markers
Why is a boolean flag needed to tell a stored key apart from a mere prefix of one?
Insert
How does inserting a key walk down, creating missing child nodes along the path in \( O(L) \)?
Creating Nodes Lazily
Why do you only allocate a child the first time a character is seen on that path?
Search
How does exact lookup differ from just reaching the last character’s node?
Delete
Why is deletion the tricky operation, and when can you prune nodes versus only clearing a flag?
Pruning Safely
Why can a node only be removed when it has no children and isn’t another key’s endpoint?
Alphabet & Fan-Out Choices
How does fixed-size array children trade speed for space against a hash-map of children?
Space vs. Hash Tables
How does shared-prefix storage save or waste memory compared to a flat dictionary?
Time Complexity
Why are the core operations independent of how many keys are stored?
Insert, Search, Delete by Key Length
Why are all three \( O(L) \) in the key’s length, not \( O(\log n) \) in the number of keys?
The Alphabet Constant
Why does an array-of-children give \( O(1) \) child access while a map adds a hashing constant?
Space Complexity
Where does a trie’s memory actually go?
Node Count vs. Total Characters
Why is the worst-case node count proportional to the total length of all inserted keys?
Array vs. Map Children
Why does a fixed array of size \( \Sigma \) per node blow up space on sparse alphabets, and how does a map fix it?
Real Uses
Where do tries appear — spell-checkers, IP routing tables, autocomplete, dictionary lookups?
Pitfalls
Why do forgetting the end-of-word flag, sparse-array memory blowup, and unicode alphabets bite you?
Implementation Walkthrough
How do the operations become code around one node type?
Setup: The Node Type
What fields does your node class need, and how is the root initialized?
The Insert Loop
How does the loop walk character by character, creating children, and mark the end?
Search and startsWith
How do the two lookups share the descent code but differ at the final node?
Recursive Delete
How does a recursive delete clear the flag at the leaf and prune empty nodes on the way back up?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Prefix Search
What Prefix Search Solves
Why is “all keys starting with these characters” the natural query a trie is built for?
Walking to the Prefix Node
How do you descend the trie to the node where a query prefix ends, and when does it fail early?
Early Termination
Why can you stop and return “no matches” the moment a prefix character is missing?
startsWith vs. Exact Match
Why is checking prefix existence cheaper and simpler than confirming a full stored key?
Subtree Enumeration
How do you collect every key sharing a prefix by traversing the subtree below the prefix node?
Rebuilding Keys During Traversal
How do you carry the accumulated characters down so each leaf yields a full word?
DFS vs. BFS Collection
How does the traversal order affect the order in which completions come out?
Counting Keys with a Prefix
How can a per-node passing-count answer “how many keys have this prefix” in \( O(L) \)?
Maintaining Counters on Insert/Delete
Why must each insertion and deletion update counts all along the path?
Ordering the Results
How does a trie naturally yield prefix matches in lexicographic order?
Time Complexity
Why does a prefix query split into a cheap descent and an output-dependent collection?
Descent Cost
Why is reaching the prefix node \( O(L) \) in the prefix length?
Output-Sensitive Enumeration
Why does collecting matches cost in proportion to the number and length of results, not the whole trie?
Counting in Constant Extra Time
Why does a stored per-node count answer “how many” in \( O(L) \) with no subtree walk?
Space Complexity
What memory does answering a prefix query require?
Recursion and Result Buffer
Why is the traversal stack \( O(\text{depth}) \) and the result list sized by the number of matches?
Cost of Per-Node Counters
Why does adding a count field cost only a constant per node?
Real Uses
Where does prefix search power features — search suggestions, command completion, contact lookup?
Pitfalls
Why do an unmarked prefix node, missing counters, or huge result sets cause subtle errors?
Implementation Walkthrough
How do descent, enumeration, and counting combine in code?
Setup: Reusing the Descent
How do you write one helper that returns the node at the end of a prefix (or null)?
Collecting the Subtree
How does a recursive collector append characters and emit a word at every end-of-word node?
Counting Without Enumerating
How does returning a node’s stored count short-circuit the full subtree walk?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Autocomplete
The Problem & When to Use a Trie
What does autocomplete need beyond plain prefix search to feel useful?
From Prefix Node to Candidates
How does locating the prefix node set up the search for completions to suggest?
Ranking by Frequency or Weight
How do you decide which completions matter most when many share the prefix?
Storing Weights in the Trie
Where do popularity scores live, and how are they updated as usage changes?
Top-k Retrieval
How does a bounded heap surface the best \( k \) completions without sorting everything?
Heap vs. Full Sort
Why is a size-\( k \) heap over candidates cheaper than collecting and sorting them all?
Precomputation vs. On-Demand
When should each node cache its top suggestions versus computing them per query?
Caching Top Suggestions Per Node
What’s the memory-versus-latency tradeoff of storing precomputed top-k at every node?
Handling Typos & Fuzzy Matches
How might edit-distance tolerance extend strict prefix matching, and what does it cost?
Latency & Scale Trade-offs
What constraints — keystroke latency, dataset size, freshness — shape a production system?
Time Complexity
Where does a single autocomplete query spend its time?
Descent Plus Candidate Gathering
Why is reaching the prefix node \( O(L) \), and what makes gathering candidates depend on subtree size?
Top-k Selection
Why does maintaining a size-\( k \) heap add a \( \log k \) factor per candidate, and how does caching drop this to \( O(k) \)?
Space Complexity
What memory does ranked autocomplete cost?
Weights and the Trie
Why does storing a weight per node add only a constant per node over a plain trie?
Precomputed Top-k Caches
Why does caching \( k \) suggestions at every node multiply space by \( k \), and when is that worth it?
Real Uses
Where do you see this — search bars, IDEs, messaging apps, command palettes?
Pitfalls
Why do stale caches, ignoring frequency, and unbounded result sets degrade the experience?
Implementation Walkthrough
How do ranking and retrieval fit together in code?
Setup: Weighted Nodes
How do you extend the trie node to carry a frequency or score, updated on insert?
Gathering Candidates Below the Prefix
How does the traversal collect (word, weight) pairs from the prefix subtree?
Selecting the Top k
How does a min-heap of size \( k \) keep only the best completions as you stream candidates?
Optional: Reading a Cached Top-k
How would a per-node cache let you skip the subtree walk entirely?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Compressed Tries (Radix Trees)
The Problem with Plain Tries
Why do long chains of single-child nodes waste memory and pointer-chasing in a standard trie?
Path Compression
Why merge each chain of single-child nodes into one edge carrying a whole substring?
Edge Labels as Substrings
How does storing strings on edges instead of single characters shrink the node count?
Node Layout
What does a radix node hold once edges carry substrings, and how are children keyed?
Searching with Substring Edges
How does a lookup match a query against edge labels chunk by chunk?
Partial Edge Matches
Why is a query that matches only part of an edge label a “not found”, and how do you detect it?
Splitting on Insert
Why must an edge sometimes be split into two when a new key diverges partway along it?
The Split Step Walked Through
What new internal node appears, and how are the old and new suffixes reattached?
Deletion & Re-Merging
Why might removing a key let you merge an edge back to keep the tree compressed?
Patricia Tries vs. Suffix Trees
How do radix-tree variants relate to suffix trees and bitwise Patricia tries?
Time Complexity
How does compression change the cost of operations versus a plain trie?
Lookup by Key Length
Why is search still \( O(L) \) in key length, now counting matched characters across edges?
Insert with Splitting
Why does a split add only constant extra work on top of the \( O(L) \) descent?
Space Complexity
Why is the compressed form more memory-efficient?
Bounding Internal Nodes
Why does compression cap the number of internal nodes at roughly the number of keys?
Edge-Label Storage
Why does storing substrings (or start/length references into the original text) keep total space linear in the input?
Real Uses
Where do radix trees run in production — IP routing (longest-prefix match), key-value stores, in-memory indexes?
Pitfalls
Why are the split and merge cases the bug-prone heart of the structure, and how do partial edge matches trip you up?
Implementation Walkthrough
How do compressed edges and the split case become code?
Setup: Edge-Labeled Nodes
How do you represent an edge label — a string or a (start, length) slice — and key children?
The Search Descent
How does the lookup consume the query against each edge label and decide match, partial, or miss?
The Insert & Split Case
How does insert walk shared prefixes, and when a divergence is found, create the new branching node and reattach suffixes?
Delete & Merge-Back
How does deletion remove a key and, where a node drops to one child, merge the edges to re-compress?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn
Tries: Problem Set
A trie (prefix tree) turns a dictionary of strings into a branching map of shared prefixes, so that every insert, lookup, and prefix query costs time proportional to the length of the key rather than the size of the dictionary. This set works that idea from the ground up: the Foundational problems build the core trie operations — insert, search, prefix existence, prefix counting, and deletion. The Applied Problems then put the structure to work on real dictionary tasks: autocomplete and search suggestions, word break and segmentation, root replacement, weighted prefix sums, wildcard matching, and binary tries for maximum-XOR queries. They run from easy to hard and mix LeetCode classics with self-contained contest-style scenarios.
Each problem is an implementation task: fill in the stub in problemset/ and
make its test in tests/problemset/ pass. A shared TrieNode helper is
available by simple name if you want a ready-made node type, but you are free to
build your own. Every solution method takes its input as a String[] so the
harness can drive it uniformly.
Foundational
Problem 1: Insert and Search Keys
Description
Build a trie that supports two operations and answers a batch of them in order.
You are given a list of operations, each a string of the form "insert word"
or "search word". An insert adds word to the dictionary as a complete key.
A search reports whether word is present as a complete inserted key — a
strict prefix of some key does not count.
Process the operations left to right and return, for each search operation
(in order), the string "true" or "false". insert operations produce no
output.
Each lookup and insert must run in O(L) time for a word of length L.
Examples
Example 1:
Input: ["insert apple", "search apple", "search app"]
Output: ["true", "false"]
apple is a complete key, so it is found. app is only a prefix of apple,
not a key, so the search reports false.
Example 2:
Input: ["search ghost", "insert ghost", "search ghost"]
Output: ["false", "true"]
The first search precedes the insert, so the key is absent.
Example 3:
Input: ["insert cat", "insert cat", "search cat"]
Output: ["true"]
Inserting the same key twice still leaves a single findable key.
Constraints
1 <= operations.length <= 10^4- Each operation is
"insert <word>"or"search <word>". 1 <= word.length <= 50, words contain only lowercase English letters.
Problem 2: Prefix Existence
Description
Extend the trie with a prefix query. You are given a list of operations, each
either "insert word" or "prefix p". An insert adds word to the
dictionary. A prefix query reports whether any inserted key begins with p
(the prefix itself need not be a complete key).
Process the operations in order and return, for each prefix query, "true" or
"false". Each query must run in O(L) time for a prefix of length L.
Examples
Example 1:
Input: ["insert apple", "prefix app", "prefix apx"]
Output: ["true", "false"]
app is a prefix of apple, so the first query is true. No key starts with
apx.
Example 2:
Input: ["insert dog", "prefix dog", "prefix do"]
Output: ["true", "true"]
A complete key also counts as a prefix of itself, and do is a proper prefix.
Example 3:
Input: ["prefix a", "insert ant", "prefix a"]
Output: ["false", "true"]
Before any insert no prefix exists; after inserting ant the prefix a is
present.
Constraints
1 <= operations.length <= 10^4- Each operation is
"insert <word>"or"prefix <p>". 1 <= word.length, p.length <= 50, lowercase English letters only.
Problem 3: Count Keys With a Prefix
Description
Maintain a trie that can report how many inserted keys share a given prefix. You
are given a list of operations, each "insert word" or "count p". Each
count query returns the number of inserted keys (counting duplicates) that
begin with p.
Process the operations in order and return, for each count query, the count as
a string. Aim for O(L) per query by maintaining a passthrough count on each
trie node.
Examples
Example 1:
Input: ["insert app", "insert apple", "insert apricot", "count ap"]
Output: ["3"]
All three keys begin with ap.
Example 2:
Input: ["insert app", "insert apple", "count app", "count appl"]
Output: ["2", "1"]
Two keys start with app; only apple starts with appl.
Example 3:
Input: ["insert cat", "insert cat", "count cat", "count dog"]
Output: ["2", "0"]
Duplicates are counted; no key starts with dog.
Constraints
1 <= operations.length <= 10^4- Each operation is
"insert <word>"or"count <p>". 1 <= word.length, p.length <= 50, lowercase English letters only.
Problem 4: Delete a Key
Description
Support deletion from a trie. You are given a list of operations, each
"insert word", "delete word", or "search word". A delete removes word
if it is a complete key and prunes any node that becomes both childless and
non-terminal; if word is not a present key, the delete has no effect. A
search reports whether word is currently a complete key.
Process the operations in order and return, for each search, "true" or
"false".
Examples
Example 1:
Input: ["insert apple", "delete apple", "search apple"]
Output: ["false"]
After deletion the key is gone.
Example 2:
Input: ["insert app", "insert apple", "delete apple", "search app"]
Output: ["true"]
Deleting apple must not remove app, which shares the app prefix.
Example 3:
Input: ["insert cat", "delete dog", "search cat"]
Output: ["true"]
Deleting a missing key leaves the trie unchanged.
Constraints
1 <= operations.length <= 10^4- Each operation is
"insert <word>","delete <word>", or"search <word>". 1 <= word.length <= 50, lowercase English letters only.
Problem 5: Longest Common Prefix
LeetCode: 14. Longest Common Prefix
Description
Given an array words of strings, return the longest prefix shared by every
string. If the strings share no common prefix, return the empty string "".
Solve it by inserting all words into a trie and descending from the root while exactly one child exists and the current node is not the end of a shorter word.
Examples
Example 1:
Input: ["flower", "flow", "flight"]
Output: "fl"
All three share fl; they diverge at the next character.
Example 2:
Input: ["dog", "racecar", "car"]
Output: ""
No common starting character, so the answer is the empty string.
Example 3:
Input: ["interspecies", "interstellar", "interstate"]
Output: "inters"
Constraints
1 <= words.length <= 2000 <= words[i].length <= 200words[i]consists of lowercase English letters.
Applied Problems
Problem 6: Implement Trie (Prefix Tree)
LeetCode: 208. Implement Trie (Prefix Tree)
Description
Implement a full trie driven by a command stream. You are given a list of
operations, each "insert word", "search word", or "startsWith prefix".
insert adds a key; search returns whether word is a complete key;
startsWith returns whether any key has the given prefix.
Process the operations in order and return, for each search and startsWith
(in order), "true" or "false". insert produces no output.
Examples
Example 1:
Input: ["insert apple", "search apple", "search app", "startsWith app", "insert app", "search app"]
Output: ["true", "false", "true", "true"]
apple is found; app is not yet a key but is a prefix; after inserting app
it is found.
Example 2:
Input: ["startsWith a", "insert a", "search a", "startsWith a"]
Output: ["false", "true", "true"]
Example 3:
Input: ["insert trie", "startsWith tri", "search tri"]
Output: ["true", "false"]
Constraints
1 <= operations.length <= 3 * 10^4- Each operation is
"insert <word>","search <word>", or"startsWith <prefix>". 1 <= word.length, prefix.length <= 2000, lowercase English letters only.
Problem 7: Search Suggestions System
LeetCode: 1268. Search Suggestions System
Description
You are given a String[] whose first element is a search word and whose
remaining elements are the catalogue of products. As the user types word
character by character, return, after each character, up to three
lexicographically smallest products that have the typed prefix.
Return the suggestions as a flat String[]: for the i-th typed prefix
(1 <= i <= word.length), emit a single string that joins its (up to three)
suggestions with a comma, or the empty string if there are none. The result has
word.length entries, in typing order.
Examples
Example 1:
Input: ["mouse", "mobile", "mouse", "moneypot", "monitor", "mousepad"]
Output: ["mobile,moneypot,monitor", "mobile,moneypot,monitor", "mouse,mousepad", "mouse,mousepad", "mouse,mousepad"]
For prefixes m and mo the three smallest are mobile, moneypot, monitor;
once mou is typed only mouse and mousepad qualify.
Example 2:
Input: ["havana", "havana"]
Output: ["havana", "havana", "havana", "havana", "havana", "havana"]
The single product matches every prefix of the search word.
Example 3:
Input: ["bags", "bags", "baggage", "banner", "box"]
Output: ["baggage,bags,banner", "baggage,bags,banner", "baggage,bags", "bags"]
Constraints
- The first element is the search word; the rest are products.
1 <= products.length <= 10001 <= word.length, products[i].length <= 3000- All strings consist of lowercase English letters.
Problem 8: Replace Words With Roots
LeetCode: 648. Replace Words
Description
You are given a String[] whose first element is a sentence of
space-separated words and whose remaining elements are dictionary roots. For
every word in the sentence, if one or more roots is a prefix of it, replace the
word with the shortest such root; otherwise leave the word unchanged.
Return the rewritten sentence as a single string with words separated by single spaces.
Examples
Example 1:
Input: ["the cattle was rattled by the battery", "cat", "bat", "rat"]
Output: "the cat was rat by the bat"
cattle -> cat, rattled -> rat, battery -> bat; other words have no
root prefix.
Example 2:
Input: ["a aa a aaaa aaa", "a", "b", "c"]
Output: "a a a a a"
The root a is a prefix of every token, and it is the shortest match.
Example 3:
Input: ["hello world", "xyz"]
Output: "hello world"
No root is a prefix of any word, so the sentence is unchanged.
Constraints
- The first element is the sentence; the rest are roots.
1 <= roots.length <= 1000,1 <= roots[i].length <= 1001 <= sentence words <= 1000, each word1 <= length <= 1000- All strings consist of lowercase English letters and single spaces.
Problem 9: Map Sum Pairs
LeetCode: 677. Map Sum Pairs
Description
Implement a key-value store with prefix sums, driven by a command stream. You
are given a list of operations, each "insert key val" (set or overwrite the
integer value of key) or "sum prefix" (return the sum of values over all
stored keys that begin with prefix).
Process the operations in order and return, for each sum query, the total as a
string.
Examples
Example 1:
Input: ["insert apple 3", "sum ap", "insert app 2", "sum ap"]
Output: ["3", "5"]
After inserting apple=3, ap sums to 3; adding app=2 raises the ap sum to
5.
Example 2:
Input: ["insert apple 3", "insert apple 2", "sum app"]
Output: ["2"]
Re-inserting apple overwrites its value, so the sum uses 2, not 5.
Example 3:
Input: ["insert a 5", "insert b 7", "sum a", "sum c"]
Output: ["5", "0"]
Constraints
1 <= operations.length <= 5 * 10^4- Each operation is
"insert <key> <val>"or"sum <prefix>". 1 <= key.length, prefix.length <= 50, lowercase English letters only.0 <= val <= 1000.
Problem 10: Longest Word in Dictionary
LeetCode: 720. Longest Word in Dictionary
Description
Given an array words, return the longest word that can be built one character
at a time, where every intermediate prefix of the word is itself present in
words. If several words are equally long, return the lexicographically smallest
one. If no word qualifies, return the empty string "".
Insert the words into a trie and find the deepest node reachable along a path whose every node is a terminal key.
Examples
Example 1:
Input: ["w", "wo", "wor", "worl", "world"]
Output: "world"
Each prefix w, wo, wor, worl is present, so world is buildable.
Example 2:
Input: ["a", "banana", "app", "appl", "ap", "apply", "apple"]
Output: "apple"
Both apple and apply are buildable and length 5; apple is
lexicographically smaller. banana fails because b, ba, … are absent.
Example 3:
Input: ["yo", "ew", "fc", "zsl", "y"]
Output: "yo"
yo is buildable (because y exists); ew, fc, zsl are not.
Constraints
1 <= words.length <= 10001 <= words[i].length <= 30words[i]consists of lowercase English letters.
Problem 11: Word Break
LeetCode: 139. Word Break
Description
You are given a String[] whose first element is the target string s and
whose remaining elements form the dictionary wordDict. Determine whether s
can be segmented into a space-separated sequence of one or more dictionary words.
A dictionary word may be reused any number of times.
Store the dictionary in a trie and run a memoised left-to-right scan that follows
trie edges to find candidate word boundaries. Return "true" or "false".
Examples
Example 1:
Input: ["leetcode", "leet", "code"]
Output: "true"
leetcode splits as leet + code.
Example 2:
Input: ["applepenapple", "apple", "pen"]
Output: "true"
Splits as apple + pen + apple; apple is reused.
Example 3:
Input: ["catsandog", "cats", "dog", "sand", "and", "cat"]
Output: "false"
No segmentation covers the whole string.
Constraints
- The first element is
s; the rest are dictionary words. 1 <= s.length <= 3001 <= wordDict.length <= 1000,1 <= wordDict[i].length <= 20- All strings consist of lowercase English letters.
Problem 12: Add and Search Word (Wildcard .)
LeetCode: 211. Design Add and Search Words Data Structure
Description
Implement a word dictionary that supports wildcard search, driven by a command
stream. You are given a list of operations, each "addWord word" or
"search pattern". In a search pattern, the character . matches any single
letter; all other characters must match exactly. A search succeeds only if some
added word matches the whole pattern.
Process the operations in order and return, for each search, "true" or
"false". Searching a pattern with k wildcards may branch, so bound the
worst-case cost accordingly.
Examples
Example 1:
Input: ["addWord bad", "addWord dad", "addWord mad", "search pad", "search bad", "search .ad", "search b.."]
Output: ["false", "true", "true", "true"]
pad is absent; bad matches exactly; .ad matches bad/dad/mad; b..
matches bad.
Example 2:
Input: ["addWord a", "search .", "search a", "search aa"]
Output: ["true", "true", "false"]
. matches the single-letter word a; aa has no two-letter match.
Example 3:
Input: ["search a", "addWord a", "search a"]
Output: ["false", "true"]
Constraints
1 <= operations.length <= 10^4- Each operation is
"addWord <word>"or"search <pattern>". 1 <= word.length, pattern.length <= 25wordconsists of lowercase letters;patternof lowercase letters and..
Problem 13: Top-K Autocomplete
Description
You are given a String[] whose first two elements are an integer k (as a
string) and a query prefix, and whose remaining elements are catalogue entries
of the form "word freq" (a word and its non-negative integer frequency).
Return the k most frequent completions of prefix — that is, the k highest
frequency words that begin with prefix — ordered by descending frequency, with
ties broken lexicographically. If fewer than k words match, return all of them.
Return the result as a String[] of words (frequencies omitted), in ranked
order.
Examples
Example 1:
Input: ["2", "ap", "apple 5", "app 3", "apply 5", "banana 9"]
Output: ["apple", "apply"]
Words under ap: apple (5), apply (5), app (3). The top two by frequency
are apple and apply; they tie at 5, so apple ranks first lexically.
Example 2:
Input: ["3", "c", "cat 4", "car 4", "cab 4"]
Output: ["cab", "car", "cat"]
All tie at frequency 4, so the order is purely lexicographic.
Example 3:
Input: ["2", "z", "apple 5"]
Output: []
No catalogue word begins with z.
Constraints
- The first element is
k; the second is the prefix; the rest are"word freq". 0 <= k <= 10^4,1 <= number of entries <= 10^41 <= word.length, prefix.length <= 100, lowercase English letters only.0 <= freq <= 10^9.
Problem 14: Prefix Tally at the Lexicon Archive
Description
The archivist loads a dictionary, then answers a stream of prefix-count queries.
You are given a String[] of lines: a prefix block of words, a separator line
"---", and then a block of query prefixes (one per line). Build a trie over the
words, store a count of words passing through each node, and for each query
report how many archived words begin with that prefix.
Return one count per query, in order, as a String[]. Each query must run in
O(L) time for a prefix of length L.
Examples
Example 1:
Input: ["apple", "app", "apricot", "---", "ap", "app", "b"]
Output: ["3", "2", "0"]
Three words start with ap; two start with app; none with b.
Example 2:
Input: ["dog", "---", "do", "dog", "dogs"]
Output: ["1", "1", "0"]
dog matches the prefixes do and dog; nothing has the prefix dogs.
Example 3:
Input: ["cat", "cat", "car", "---", "ca", "cat"]
Output: ["3", "2"]
Duplicate words are counted: three words start with ca, two with cat.
Constraints
- Exactly one separator line
"---"divides words from queries. 1 <= words count, queries count <= 2 * 10^5, total characters<= 10^6.- Words and queries consist of lowercase English letters only.
Problem 15: Segmenting the Signal Stream
Description
Mission control receives a signal and a vocabulary of legal chunks. You are
given a String[] whose first element is the signal string and whose remaining
elements are the vocabulary chunks. Determine whether signal can be split into a
contiguous sequence of vocabulary chunks, where each chunk may be reused any
number of times.
Store the vocabulary in a trie and run a memoised left-to-right scan that follows
trie edges to enumerate candidate chunk boundaries. Return "true" if a full
segmentation exists, else "false".
Examples
Example 1:
Input: ["abcd", "ab", "cd", "abc"]
Output: "true"
abcd segments as ab + cd.
Example 2:
Input: ["aaaaa", "aa", "aaa"]
Output: "true"
aaaaa segments as aa + aaa (or aaa + aa); chunks may repeat.
Example 3:
Input: ["abcf", "ab", "cd"]
Output: "false"
The suffix cf matches no chunk, so no full segmentation exists.
Constraints
- The first element is the signal; the rest are vocabulary chunks.
1 <= signal.length <= 20001 <= vocabulary.length <= 10^4- All strings consist of lowercase English letters.
Problem 16: Longest Shared Call-Sign Prefix
Description
A fleet of ships broadcasts callSigns. The relay needs the longest prefix
shared by every call-sign in order to compress transmissions. You are given the
call-signs as a String[].
Insert all call-signs into a trie and descend from the root while exactly one child exists and the node is not the end of a shorter call-sign. Return that longest common prefix, or the empty string if none is shared by all.
Examples
Example 1:
Input: ["alpha", "alpine", "altitude"]
Output: "al"
Every call-sign starts with al; they diverge afterwards.
Example 2:
Input: ["north", "northwest", "northeast"]
Output: "north"
The shortest call-sign north is itself a prefix of the others, so it caps the
shared prefix.
Example 3:
Input: ["echo", "foxtrot"]
Output: ""
No shared starting character.
Constraints
1 <= callSigns.length <= 10^51 <= callSigns[i].length <= 100callSigns[i]consists of lowercase English letters.
Problem 17: Weighted Prefix Frequency on the Trade Ledger
Description
The trade ledger stores named entries with integer weights, then answers
weighted prefix-sum queries. You are given a String[] of lines: an entry
block of "name weight" pairs, a separator line "---", and then a block of
query prefixes. A later entry with the same name overwrites its weight. For each
query, return the sum of weights over all stored names that begin with that
prefix.
Maintain a trie whose nodes accumulate weight contributions so each prefix sum is
O(L). Return one sum per query, in order, as a String[].
Examples
Example 1:
Input: ["apple 3", "app 2", "---", "ap", "app", "b"]
Output: ["5", "5", "0"]
Both apple (3) and app (2) start with ap and app, totalling 5; nothing
starts with b.
Example 2:
Input: ["apple 3", "apple 7", "---", "app"]
Output: ["7"]
Re-inserting apple overwrites its weight, so the sum uses 7, not 10.
Example 3:
Input: ["cat 4", "car 5", "---", "ca", "cat", "cz"]
Output: ["9", "4", "0"]
Constraints
- Exactly one separator line
"---"divides entries from queries. 1 <= entries count, queries count <= 10^5- Weights fit in a signed 32-bit integer; names and prefixes are lowercase letters.
Problem 18: Maximum XOR Pair
LeetCode: 421. Maximum XOR of Two Numbers in an Array
Description
You are given an array nums of non-negative integers, supplied as a String[]
of decimal numerals. Return the maximum value of nums[i] XOR nums[j] over all
pairs i, j, as a string.
Insert each number’s fixed-width binary representation into a binary trie (one
bit per level, most significant bit first). For each number, walk the trie
greedily preferring the opposite bit at each level to maximise the XOR, giving an
O(n * b) algorithm for b-bit integers.
Examples
Example 1:
Input: ["3", "10", "5", "25", "2", "8"]
Output: "28"
5 XOR 25 = 28 is the largest pairwise XOR.
Example 2:
Input: ["0", "0"]
Output: "0"
The only pair XORs to 0.
Example 3:
Input: ["8", "10", "2"]
Output: "10"
8 XOR 2 = 10 beats the other pairs.
Constraints
1 <= nums.length <= 2 * 10^50 <= nums[i] <= 2^31 - 1- Each element of the input array is a decimal numeral.
Order-Statistics Trees
What It’s For
What two queries — “find the k-th smallest” and “what’s the rank of x” — does a plain balanced BST fail to answer fast, and why does scanning or counting in-order defeat the purpose?
The Core Idea: Size-Augmented Nodes
What single extra field on each node turns an ordinary balanced BST into one that can count its own elements?
Choosing the Base Tree
Why does this augmentation ride on top of a self-balancing tree (red-black or AVL) rather than a raw BST, and what would degrade without that?
What the Size Field Counts
Does a node’s size include itself? Express it in terms of its two children’s sizes — what’s the one-line recurrence?
The Invariant You Must Preserve
State the size invariant precisely: at every node, size equals 1 plus left size plus right size. Why must this hold after every mutation, not just at the end?
Select: Finding the k-th Element
Trace how the left subtree’s size tells you whether the answer is in the left subtree, is the current node, or is the \( (k - \text{leftSize} - 1) \)-th element of the right subtree.
Off-by-One in Select
Where does the choice of 0-based vs 1-based rank change the comparison, and how do you keep it consistent end to end?
Rank: Position of a Key
How do you accumulate left-subtree sizes as you descend right, so that when you land on the key you’ve summed everything smaller?
Insert and Delete with Sizes
Which nodes’ size fields change on the path of an insert or delete, and why is it exactly the nodes on the root-to-target path?
Maintaining Size Under Rotations
Why does a rotation only need to recompute the sizes of the two nodes it swaps — and in what order must you recompute them?
Why Augmentation Survives Rebalancing
What property must an augmented field have so it can be recomputed from a node’s own value plus its children’s fields — and why does that property let rotations stay cheap?
Beyond Size: Other Augmentations
What else can you hang on a node by the same recipe (subtree sum, min, count-of-key) and what changes in select/rank when you do?
Time Complexity
Reason about each operation’s cost in terms of tree height.
Search, Select, Rank
Each follows a single root-to-leaf path. Why is that path length the height, and why does balancing pin the height at \( O(\log n) \)?
Insert and Delete
Beyond the descent, what bounds the number of rotations and size-fixups per update, and why does that not change the asymptotic cost?
Space Complexity
What does the tree cost in memory, and what does the augmentation add?
The Augmentation Overhead
One integer per node — what’s the total extra space, and why is it only a constant factor on top of the base tree?
Recursion / Stack Depth
If select and rank recurse, how deep does the call stack get, and how would an iterative version change that?
Real Uses
Where do order statistics show up — running medians, “count of elements less than x,” percentile and leaderboard queries, inversion counting?
Pitfalls
What breaks if you forget a size fixup after a rotation, mismatch 0- vs 1-based rank, or update size before performing the structural change?
Implementation Walkthrough
Plan the code in parts before writing it.
Node Representation
What fields does a node hold — key, children, parent/color/height, and size — and where does size live relative to the balancing metadata?
The size() Helper and Recompute Rule
How do you handle a null child’s size, and what is the single line that recomputes a node’s size from its children?
Augmented Rotations
After you splice pointers in a rotation, which two nodes need their size recomputed and in which order?
Select and Rank Routines
Sketch the branching logic for select (compare k against left size) and rank (accumulate left sizes) without writing the answer.
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn.
Interval Trees
What It’s For
What query — “which stored intervals overlap a given interval or point?” — motivates this structure, and why is a plain sorted list slow at it?
The Core Idea: BST Keyed on Low Endpoints
Why do we order nodes by their interval’s left endpoint, and what does that ordering alone fail to give us about overlaps?
The Augmentation: Max Endpoint in Subtree
What single extra field per node records the largest right endpoint anywhere in that subtree, and how is it computed from the node and its children?
Why Max, Not Min
How does knowing the subtree’s maximum right endpoint let you prune an entire subtree from an overlap search? What would min fail to tell you?
The Augmentation Invariant
State precisely what max must equal at every node, and why it must be restored after every insert, delete, and rotation.
The Overlap Test
What is the condition for two intervals to overlap, written cleanly — and how does the touching-endpoints (closed vs half-open) convention change it?
Overlap Search for a Single Interval
Trace the decision at each node: when do you go left, when right, and how exactly does the left child’s max field justify skipping the left subtree?
Why the Search Never Misses an Overlap
Argue the correctness claim: if an overlapping interval exists, why does the descent rule always steer toward at least one of them?
Finding All Overlaps
How do you extend the single-overlap search to report every overlapping interval, and why does the cost now depend on the number of matches?
Building the Tree
How do you insert intervals one at a time, and at which points along the insertion path is the max field updated?
Maintaining Max Under Rotations
Why can the max field be recomputed locally from a node’s own right endpoint and its two children’s max after a rotation — and in what order?
Time Complexity
Reason about each query and update in terms of height and output size.
Single-Overlap Query
Why is one overlap found along an \( O(\log n) \) path, given the pruning rule? What stops you from descending both subtrees?
All-Overlaps Query
Why is reporting all \( k \) matches \( O(k \log n) \) (or \( O(k + \log n) \) with care)? Where does the output term enter the bound?
Insert, Delete, Rotations
Why does maintaining max add only constant extra work per node on the update path, leaving the operations \( O(\log n) \)?
Space Complexity
What does the tree plus augmentation cost?
Per-Node Storage
Each node stores an interval plus one max value — what’s the total, and why is it linear in the number of intervals?
Recursion Depth
How deep can the recursive overlap search go, and how does that relate to the tree height?
Interval Trees vs Segment Trees vs Sweep Lines
When is a dynamic interval tree the right tool versus a static coordinate segment tree or a one-pass offline sweep?
Real Uses
Where do overlap queries appear — calendar conflicts, genomic ranges, network packet rules, collision broad-phase?
Pitfalls
What goes wrong if you forget to update max on insert, mishandle equal left endpoints, or confuse inclusive vs exclusive endpoints in the overlap test?
Implementation Walkthrough
Plan the code before writing it.
Node and Max Field
What does each node store, and how do you treat a null child’s max so the recompute rule stays uniform?
The Overlap Predicate
Write down (as a prompt) the boolean test you’ll reuse everywhere — what two comparisons does it combine?
Augmented Insert and Rotations
Where on the insertion path do you bump max, and which nodes get recomputed after a rotation?
Search Routines
Sketch the branching for single-overlap (use child max to prune) and all-overlaps (recurse into both viable subtrees) without giving the answer.
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn.
Treaps
What It’s For
What problem does a treap solve — keeping a BST balanced without the bookkeeping of red-black colors or AVL heights?
The Core Idea: Tree + Heap
What are the two simultaneous orderings: keys arranged as a BST, priorities arranged as a heap?
Why Both Invariants Pin Down the Shape
Given a fixed set of (key, priority) pairs, why is the treap’s structure uniquely determined?
The Two Invariants Stated Precisely
Write the BST condition on keys and the (max- or min-) heap condition on priorities. Which one would a rotation never be allowed to violate?
Randomized Priorities
Where do priorities come from, and why does choosing them at random make the tree behave like a randomly built BST?
Why Random Equals Balanced in Expectation
What is the connection between a random priority assignment and the expected depth of a node in a randomly inserted BST?
Insert by Key, Fix by Priority
Trace inserting a node as a normal BST leaf, then rotating it upward until the heap order is restored.
Delete by Rotating Down
How do you remove a node by rotating it down toward a leaf, always pulling up the higher-priority child?
Rotations as the Only Repair Tool
Why is a single rotation enough to move one node up or down one level while preserving the BST key order?
Split and Merge
What do split-by-key and merge-two-treaps do, and what operations (range ops, k-th element, persistence) do they unlock?
Implicit Treaps
How does keying on subtree size instead of value turn a treap into an array supporting insert/erase/range ops at any position?
Time Complexity
Reason about why the bounds are expected rather than worst-case.
Search, Insert, Delete
Each cost is proportional to the depth of a node. Why is the expected depth \( O(\log n) \), and what input could still produce an \( O(n) \) chain?
Split and Merge
Why do split and merge also run in expected \( O(\log n) \), and how do insert/delete reduce to them?
Where the Randomness Lives
Is the \( O(\log n) \) over random priorities or over random inputs — and why does that distinction matter for an adversary?
Space Complexity
Account for everything a node carries.
Per-Node Overhead
Key, priority, two child pointers, and any augmentation (size) — what’s the total, and how does it compare to a red-black node’s color bit?
Recursion Stack in Split/Merge
How deep do recursive split and merge go, and how does that relate to expected height?
Treaps vs Red-Black / AVL / Skip Lists
Why pick a treap — simpler code, easy split/merge — and what do you give up versus a deterministic balanced tree?
Pitfalls
What breaks with duplicate priorities, a weak RNG, or forgetting to update augmented fields after a rotation?
Implementation Walkthrough
Plan the code before writing it.
Node and Priority Source
What fields does a node need, and how/when do you assign each node its random priority?
Rotation Primitives
Sketch left and right rotation as pointer surgery — which links change, and which augmented fields must be refreshed?
Insert and Delete Logic
Describe the BST descent plus the rotate-up (insert) or rotate-down (delete) loop you’ll write, as prompts only.
Split and Merge Recursion
What’s the recursive structure of split-by-key and merge, and what base case stops each?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn.
van Emde Boas Trees
What It’s For
What does a vEB tree do that a balanced BST can’t — predecessor, successor, insert, delete all faster than \( O(\log n) \)?
The Catch: Integer Keys Only
Why does this speed only apply to keys drawn from a bounded universe \( {0, \dots, u-1} \), not arbitrary comparables?
The Universe and Why u Drives Cost
How does the cost depend on the size of the key space \( u \) rather than the number of stored elements \( n \)?
Splitting a Key: High and Low Bits
How does a key split into a “cluster” index (high bits) and a “position within cluster” (low bits) using \( \sqrt{u} \)?
high(x), low(x), and index(c, i)
Write the three helper definitions as prompts: which bits go to the cluster number, which to the offset, and how do you reassemble a key?
Recursive Structure of Clusters
What does each node hold: an array of \( \sqrt{u} \) child vEB clusters, each over a universe of size \( \sqrt{u} \)?
The Summary Structure
What does the summary vEB track, and how does it let you jump straight to the next non-empty cluster without scanning?
Min and Max Stored Explicitly
Why does caching the min and max at each node — and keeping min outside the recursion — eliminate a recursive call and make the recurrence work?
The Min-Lazy Trick
Why is the minimum NOT stored recursively in any cluster, and how does that single design choice cap the number of recursive calls per operation at one?
Successor and Predecessor
Trace how a successor query makes at most one recursive call: stay within the current cluster, or hop via the summary to the next non-empty one.
Insert and Delete
How do insert and delete keep min/max and the summary consistent while still bottoming out in one recursive branch?
Why Insert Recurses Only Once
When a cluster becomes newly non-empty, why does inserting into it cost \( O(1) \) so the only “real” recursion is the summary update (or vice versa)?
Time Complexity
Reason carefully about the recurrence — this is the heart of the topic.
The Recurrence T(u) = T(√u) + O(1)
Why does each operation make at most one recursive call on a universe of size \( \sqrt{u} \) plus constant work? Set up the recurrence — do not solve the algebra, just state what it unrolls toward.
Why It Lands at O(log log u)
Intuitively, why does repeatedly taking square roots of \( u \) reach a constant in \( \log\log u \) steps? Connect that to the number of bits in a key.
Per-Operation Bounds
State successor, predecessor, insert, delete, and member costs, and note which is \( O(1) \) (member? min? max?).
Space Complexity
This is the structure’s main weakness — reason about it.
Why Space Is O(u)
Each node allocates \( \sqrt{u} \) cluster slots plus a summary. Why does the total swell toward \( O(u) \) regardless of how few keys \( n \) you store?
Shrinking It: Hashing the Clusters
How does replacing the dense cluster array with a hash table give \( O(n) \) space, and what does that turn the vEB into conceptually?
vEB vs Balanced BST vs y-fast Trie
When is the \( O(\log\log u) \) speed worth the space, and what does a y-fast trie offer to fix the space blowup?
Pitfalls
What goes wrong with the empty-tree base case, the min-stored-outside trick during delete, or a non-power-of-two universe?
Implementation Walkthrough
Plan the recursive structure before writing it.
Node Layout and Base Case
What does a vEB node store (u, min, max, summary, clusters), and what is the base case at \( u = 2 \) where there’s no recursion?
The Bit-Split Helpers
Sketch high/low/index in terms of \( \sqrt{u} \) (upper and lower square roots) — what’s the prompt for handling odd bit counts?
Successor/Predecessor Branching
Lay out the decision tree: check min/max shortcuts first, then “is there a successor in this cluster?”, else “hop summary to next cluster.” Prompts only.
Insert/Delete and the Empty Transitions
What special handling does an empty subtree (insert) or a subtree dropping to one element (delete) need to keep min/max and summary correct?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn.
Cache-Oblivious Algorithms
What It’s For
What problem do these solve — getting good memory-hierarchy performance without hard-coding the cache size or block size into the algorithm?
Oblivious vs Cache-Aware
What’s the difference between an algorithm tuned to a known \( B \) and \( M \) and one that performs well without ever naming them?
The Ideal-Cache Model
What are the model’s assumptions: two memory levels, automatic block transfers, and an optimal replacement policy?
Why Tuning-Free Is Powerful
Why does being optimal in this model imply good behavior across every level of a real multi-level hierarchy at once?
The Tall-Cache Assumption
What does \( M = \Omega(B^2) \) assume about the cache’s shape, and why do several optimal bounds quietly depend on it?
Recursive Divide-and-Conquer for Locality
Why does repeatedly halving a problem until subproblems fit in cache yield good locality “for free,” with no knowledge of cache size?
The “Free Blocking” Argument
At some recursion level a subproblem just fits in \( M \) (and below that, in \( B \)). Why does the algorithm reach that level automatically without being told where it is?
The van Emde Boas Memory Layout
How does laying out a search tree recursively (top half, then each bottom subtree contiguously) cut search I/Os, and why is the naive array-order (BFS) layout worse?
Cache-Oblivious Matrix Multiply
How does recursively blocking a matrix product by quartering each matrix achieve a strong I/O bound without choosing a tile size?
Cache-Oblivious Sorting (Funnelsort)
What is the high-level idea of merging through recursively built “funnels” to match the external sorting bound?
Time Complexity (Work)
Separate ordinary running time from I/O cost.
Why Work Stays Asymptotically Normal
Why does the recursive blocking not change the classic operation count (e.g. matrix multiply is still \( \Theta(n^3) \) work, sorting \( \Theta(n\log n) \))?
Cache Complexity (I/O Cost)
This is the whole point of the model — reason about block transfers.
Scanning and Search Bounds
Why is a linear scan \( O(n/B) \) I/Os, and why does the vEB-laid-out tree search cost \( O(\log_B n) \) like a B-tree despite being binary?
Matrix Multiply and Sorting Bounds
State the cache-oblivious matrix-multiply I/O bound and the sorting bound, and point to where the tall-cache assumption is used.
Space Complexity
Account for the recursion and any auxiliary layout.
Layout and Recursion Overhead
Does the vEB layout need extra space beyond the data? How deep does the divide-and-conquer recursion stack get?
Trade-offs vs Cache-Aware Code
What do you pay for portability — constant factors, recursion overhead — versus a hand-tiled cache-aware kernel?
Real Uses
Where does this matter — portable numeric libraries, B-tree alternatives, code that must run well across unknown hardware?
Pitfalls
Why can recursion overhead and the tall-cache assumption bite, and where does the model diverge from real caches (associativity, prefetch)?
Implementation Walkthrough
Plan one concrete example — say recursive blocked matrix multiply — before writing it.
Choosing the Recursive Split
How do you decide which dimension to halve at each step, and what’s the base case where you switch to a direct loop?
Memory Layout Decisions
For a vEB tree layout, how do you compute where each recursive sub-tree’s nodes live in the flat array? Prompts only.
Base Case Tuning
Why does a tiny base case hurt constant factors, and how do you pick a cutoff without making the algorithm cache-aware?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn.
External-Memory Model
What It’s For
Why do we need a different cost model when the data is too big for RAM and lives on disk or SSD?
The DAM Model: B and M
What do block size \( B \) and internal-memory size \( M \) mean, and what exactly does one I/O transfer?
Counting I/Os, Not CPU Operations
Why does the dominant cost become block transfers between disk and memory rather than instructions executed?
Why a Random Read Costs the Same as a Block Read
What does the all-or-nothing block model assume, and why does that reward sequential access patterns?
The Scanning Bound
Why does streaming through \( n \) contiguous elements cost about \( n/B \) I/Os instead of \( n \)?
The Sorting Bound
What is the external sorting cost \( \frac{n}{B}\log_{M/B}\frac{n}{B} \), and what does the \( M/B \)-way merge intuition behind it look like?
Why M/B-Way Merge, Not 2-Way
How many sorted runs can you merge in one pass given memory \( M \) and block size \( B \), and why does that base of the logarithm crush the number of passes?
B-Trees as the Native Search Structure
Why does giving each node fanout proportional to \( B \) drive search cost down to \( \log_B n \) I/Os instead of \( \log_2 n \)?
Why Binary Search Trees Are Bad on Disk
What makes a pointer-chasing binary tree pay one I/O per level, and why is that the wrong shape for disk?
Buffering and Batching
How do buffer trees and write-deferral amortize updates so each one costs far less than a full root-to-leaf I/O path?
Time Complexity in I/Os
Restate every basic operation in the currency of block transfers.
Scan, Search, Sort
State scan \( O(n/B) \), B-tree search \( O(\log_B n) \), and sort \( O!\left(\frac{n}{B}\log_{M/B}\frac{n}{B}\right) \). For each, name the one structural choice that earns the bound.
Updates and Their Amortization
Why is a single B-tree insert \( O(\log_B n) \) I/Os, and how does batching (buffer trees, LSM) drive the amortized cost below that?
Contrast with the RAM Model
For each operation above, what’s the RAM-model cost, and why does ignoring \( B \) mislead you about real disk performance?
Space Complexity
Account for storage on disk and in memory.
On-Disk Footprint
Why is a B-tree’s disk space \( O(n/B) \) blocks, and how full must nodes be kept to avoid wasting blocks?
In-Memory Working Set
What must fit in \( M \) for sorting and merging to hit their bounds — how many blocks does an \( M/B \)-way merge hold open at once?
Real Uses
Where does this model govern design — databases, filesystems, key-value stores, log-structured merge trees?
Pitfalls
Why can a RAM-optimal algorithm be I/O-terrible, and what assumptions (single disk, known \( B \)) does the basic model simplify away?
Implementation Walkthrough
Plan an external merge sort before writing it.
Run Formation
How do you read chunks that fill \( M \), sort them in memory, and write sorted runs back to disk? What sets the run length?
The Multiway Merge
How do you keep one input block per run plus one output block in memory, and how do you refill a block when it drains? Prompts only.
Buffering Reads and Writes
Where do you batch I/O so you transfer whole blocks rather than single records, and why does that decide the constant factor?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn.
Segment Trees
What It’s For
What pair of operations — range queries and point/range updates on an array — does this structure make logarithmic when a prefix array can’t handle updates?
The Core Idea: Recursive Node Ranges
How does each node own an interval \( [lo, hi] \), with children splitting it at the midpoint down to single-element leaves?
Array Layout vs Pointer Layout
When do you store the tree flat in an array (node \( i \) → children \( 2i, 2i{+}1 \)) versus with explicit node objects, and why?
Canonical Segments
What are the \( O(n) \) “canonical” intervals a segment tree can represent, and why is every query range a union of \( O(\log n) \) of them?
Building the Tree
How does build recurse to the leaves and combine children bottom-up, and why is it linear time?
Range Query Decomposition
Trace the three cases at each node — no overlap, full overlap, partial overlap — and how a query splits into canonical segments.
Why a Query Touches Only O(log n) Nodes
At each tree level, why can at most two nodes be “partially” overlapped by the query range, so the descent fans out by at most a constant per level?
Point Update
How do you change one leaf and re-merge its ancestors on the way back up?
Range Update
Why does a naive per-leaf range update cost too much, and what does this motivate (see lazy propagation)?
The Associative Merge Function
What property must the combine operation have, and which operations qualify — sum, min, max, gcd, and what about non-associative ones?
Identity Elements
Why does each merge need an identity value for the “no overlap” case, and what is it for sum vs min?
Time Complexity
Reason about why each operation is logarithmic, not just that it is.
Build
Why is building bottom-up \( O(n) \) — how many internal nodes are there, and how much work does each merge do?
Query
Tie the cost to the “at most two partial nodes per level” argument: why does that make a range query \( O(\log n) \) merges?
Point and Range Update
Why does a point update walk a single root-to-leaf path (\( O(\log n) \)), and why does a naive range update degrade to \( O(n) \) without lazy tags?
Space Complexity
Account for the array or node storage and the recursion.
Why the Array Needs ~2n to 4n Slots
For a flat array layout, why must you size it to the next power of two (or \( 4n \) to be safe), and where does the wasted space come from?
Recursion Stack
How deep does the recursive query/update go, and how does that relate to tree height?
Segment Tree vs Fenwick Tree vs Sparse Table
When does the segment tree’s generality earn its larger code and memory over a Fenwick tree or a static sparse table?
Real Uses
Where do range queries with updates show up — range sums/mins, range coloring, counting, and 2D variants?
Pitfalls
What goes wrong with off-by-one ranges, forgetting the identity, integer overflow on sums, or under-sizing the backing array?
Implementation Walkthrough
Plan the code in parts before writing it.
Tree Representation and Sizing
Will you use a flat array or nodes? How big must the array be, and how do children/parent indices relate?
Recursive Build
What are the parameters of the build recursion (node index, lo, hi), the leaf base case, and the post-recursion merge step? Prompts only.
Query Decomposition
Sketch the three-case branch (disjoint → identity, contained → return node, else recurse and merge) without writing it out.
Point Update and Re-Merge
Which path do you descend, and at which point on the way back up do you recombine children?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn.
Lazy Propagation
What It’s For
What problem does lazy propagation fix — making range updates on a segment tree logarithmic instead of touching every covered leaf?
The Core Idea: Defer the Work
Why mark a node “this whole range has a pending change” and stop, rather than pushing the change all the way down right away?
Lazy Tags at Nodes
What does a pending tag store, and what invariant must hold — is a node’s stored aggregate already correct while its children’s are not?
The Lazy Invariant Stated Precisely
Write the contract: a node’s aggregate reflects all updates applied to it, but a pending tag means its children have NOT yet been told. Why is this exactly what makes deferral safe?
Applying a Tag to a Node
How do you fold a pending update into a node’s aggregate value, and why must this account for the node’s range length (e.g. range-add to a sum)?
Push-Down on Descent
Trace how, before recursing past a node, you flush its tag to both children and clear it.
Why Push Before You Recurse
What stale read happens if you descend into a child while a tag still sits unpushed at the parent?
Composing Stacked Updates
How do two pending updates on the same node merge into one tag, and when does the order of composition matter?
Range Assign vs Range Add
Contrast the tag semantics and composition rules for “set this range to v” versus “add v to this range” — why does assign overwrite while add accumulates?
Query Under Lazy Tags
Why must a query also push down tags as it descends, and what happens to correctness if it reads a stale node?
Combining Multiple Tag Types
How do you carry both an assign tag and an add tag at once, and what’s the rule for an add arriving on top of a pending assign?
Time Complexity
Reason about why deferral keeps things logarithmic.
Why Range Update Becomes O(log n)
With lazy tags, a range update stops at \( O(\log n) \) canonical nodes instead of \( O(\text{range size}) \) leaves. Tie this to the same “two partial nodes per level” argument as plain queries.
Why Query Stays O(log n) Despite Push-Downs
Each visited node does \( O(1) \) push-down work. Why doesn’t pushing down add more than a constant factor to the \( O(\log n) \) nodes a query already visits?
Amortized vs Worst Case
Is each individual operation worst-case \( O(\log n) \) here, or amortized? Why is it actually worst-case for standard add/assign tags?
Space Complexity
Account for the extra per-node bookkeeping.
The Lazy Array
Why does lazy propagation add a second array (one tag per node) of the same size as the tree, and what’s the total relative to \( n \)?
Multiple Tag Types
If you carry both assign and add tags, how does that change the per-node storage, and why is it still \( O(n) \)?
Real Uses
Where do range updates appear — range increment/assign with range sum/min/max, interval painting, scheduling, difference-array-style problems?
Pitfalls
What breaks if you forget to push down before recursing, mis-order tag composition, ignore range length when applying, or leave a tag uncleared after pushing?
Implementation Walkthrough
Plan the lazy machinery before writing it.
Tag Storage and Identity
What does the lazy array hold, and what tag value means “no pending update”? Why does the identity tag matter?
apply(node, tag): Folding a Tag In
Sketch the routine that updates a node’s aggregate and merges the tag into its own pending tag — what does range length feed into here? Prompts only.
pushDown(node): Flushing to Children
What sequence applies the parent’s tag to each child and then clears the parent’s tag? Where exactly do you call this in query and update?
Range Update and Query with Tags
Where in the recursion do you stop early (full cover → apply tag and return) versus push down and recurse?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn.
Fenwick Tree (Binary Indexed Tree)
What It’s For
What problem does a BIT solve — prefix sums that stay correct under point updates — and why is a plain prefix-sum array bad at updates?
The Core Idea: Each Index Owns a Range
How does each tree index become responsible for a contiguous block of the original array, sized by its lowest set bit?
Picturing the Coverage
For a small array, sketch which range each index \( i \) covers. Why do some indices cover one element and others cover a long span?
The Low-Bit Trick i & -i
What does \( i \mathbin{&} -i \) isolate, and how does the lowest set bit decide the span each index covers?
Why Two’s Complement Makes It Work
Why does \( -i \) (two’s complement) flip all bits above the lowest set bit, so the AND leaves exactly that one bit? Reason through a small example.
1-Indexing Convention
Why is the tree built 1-indexed, and what goes wrong at index 0?
Prefix-Sum Query
Trace how you walk down by stripping the low bit (\( i \mathrel{-}{=} i \mathbin{&} -i \)) to accumulate the sum of \( 1..i \).
Point Update
Trace how you walk up by adding the low bit (\( i \mathrel{+}{=} i \mathbin{&} -i \)) to touch every index responsible for position \( i \).
Range Query via Prefix Differences
How do you turn the sum over \( l..r \) into two prefix queries, and why does this trick require an invertible operation?
Building in O(n)
How do you construct the tree in linear time by pushing each value to its immediate parent, instead of \( n \) separate updates?
Variants
How do you extend a BIT to range-update + point-query (difference trick), range-update + range-query (two BITs), or 2D grids?
Time Complexity
Reason about why every operation is logarithmic via the bit pattern.
Why Query and Update Are O(log n)
Each step of query strips, and each step of update adds, the lowest set bit. Why does that change at most one bit position per step, so the loop runs at most \( \log n \) times?
Why Build Is O(n)
Compare \( n \) separate \( O(\log n) \) updates against the push-to-parent method. Why does the linear build touch each node only a constant number of times?
2D and Range-Update Variants
What does adding a dimension or a second tree do to the per-operation cost (\( O(\log^2 n) \), constant-factor doubling), and why?
Space Complexity
Account for the storage.
Why Just One Array of Size n
Why does a BIT need only a single array the size of the input (plus 1 for 1-indexing), with no parent pointers or tree nodes?
Why It Beats a Segment Tree on Memory
Where does the segment tree’s \( 2n \)–\( 4n \) come from, and why does the BIT avoid it entirely?
Fenwick vs Segment Tree
Why pick a BIT — tiny code, low constant factor, half the memory — and what does its invertibility requirement rule out (e.g. range min)?
Pitfalls
What goes wrong with 0-indexing, range queries on a non-invertible op, integer overflow, or mixing up the strip-low-bit and add-low-bit directions?
Implementation Walkthrough
Plan the few lines before writing them.
The Array and 1-Indexing
How big is the backing array, and how do you map external 0-based positions to internal 1-based indices? Prompts only.
The lowBit Helper
What single expression isolates the low bit, and why will you reuse it in both loops?
Update Loop (Walk Up)
Which direction does the index move, and what’s the loop bound? Sketch as a prompt, not code.
Query Loop (Walk Down) and Range via Difference
Which direction does the index move for a prefix query, and how do two prefix calls combine into a range answer?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn.
Fenwick vs Segment Tree
What This Comparison Decides
When you need range queries with updates, what factors push you toward a Binary Indexed Tree versus a segment tree?
What Each Computes Natively
Which queries come “for free” on a BIT (prefix-reducible, invertible) versus a segment tree (any associative merge)?
The Invertibility Requirement
Why does a BIT’s range query need an inverse operation, and what does that exclude — why can’t a plain BIT do range min or max?
Prefix-Reducible vs Canonical-Segment Decomposition
A BIT answers ranges by subtracting prefixes; a segment tree by unioning canonical segments. Why does the first need inverses and the second doesn’t?
Generality of the Merge Operation
Why can a segment tree handle min, max, gcd, assignment, or custom merges that a BIT can’t, and what does that buy you?
Range Updates: Where Each Bends
How far can a BIT stretch with the two-tree range-update trick, and at what point does a lazy segment tree clearly win?
A Quick Decision Rule
Build a checklist: invertible + prefix-reducible + just sums → BIT; needs min/max/assign/range-update/custom merge → segment tree.
Worked Scenarios
For “point update + range sum,” “range add + range min,” and “range assign + range sum,” which structure fits and why?
Time Complexity Compared
Both are \( O(\log n) \) asymptotically — dig into why one is faster in practice.
Same Big-O, Different Constants
If both query and update are \( O(\log n) \), why does the BIT typically run several times faster? Think about the number of array touches and branches per operation.
Build Costs
Both build in \( O(n) \) — but how does the work per element differ between pushing low bits and recursively merging children?
Where the Segment Tree Pays More
Which operations carry extra overhead on a segment tree (recursion, two child reads, lazy push-downs) that the BIT’s tight loops avoid?
Space Complexity Compared
Quantify the memory gap and its consequences.
n vs 2n–4n
Why is the BIT exactly one array of size \( n \) while the segment tree needs \( 2n \)–\( 4n \) (plus a lazy array)? Where does each extra factor come from?
Cache Behavior
Why does the BIT’s smaller, more contiguous footprint translate into better cache performance, reinforcing the constant-factor win?
Implementation Effort
Why is a BIT a handful of lines while a segment tree (especially with lazy propagation) is much longer and more error-prone?
Pitfalls of Choosing Wrong
What pain do you hit forcing a BIT to do min, or reaching for a heavy lazy segment tree when a two-line BIT would do?
Implementation Walkthrough
Plan a side-by-side so the trade-off is concrete.
Mapping a Query to Each Structure
Take “range sum with point update” and sketch how the BIT (two prefix calls) and the segment tree (canonical-segment recursion) each answer it. Prompts only.
Where Invertibility Forces Your Hand
Take “range min” and sketch why the BIT approach stalls while the segment tree proceeds — what’s the missing ingredient?
Picking the Lighter Tool
Given a problem statement, what questions do you ask (op invertible? range update? custom merge?) to choose before writing any code?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn.
Sparse Tables
What It’s For
What problem does a sparse table nail — answering range queries in constant time on a static array — and what’s the catch about updates?
The Core Idea: Precompute Power-of-Two Blocks
How is \( \text{table}[k][i] \) defined as the answer over the block of length \( 2^k \) starting at \( i \)?
Idempotent Operations Only
Why does \( O(1) \) querying require an overlap-tolerant operation \( f(x,x)=x \), and which operations qualify — min, max, gcd — and which don’t?
What “Idempotent” Buys You
Why does double-counting the overlap of two blocks change nothing for min/max but corrupt a sum? Connect that to the \( O(1) \) query.
Building the Table
Trace the doubling recurrence that fills row \( k \) from two half-length blocks in row \( k-1 \).
The O(1) Overlapping Query
Show how two overlapping blocks of length \( 2^k \) exactly cover any range \( [l, r] \), and why overlap is harmless here but not for sums.
Choosing the Right k
For a range of length \( \text{len} \), why do you pick \( k = \lfloor \log_2 \text{len} \rfloor \), and why are two blocks of length \( 2^k \) always enough to cover it?
The Log Lookup Trick
Why precompute the floor of \( \log_2 \) for each length, and how does that table make every query branch-free and constant time?
Sums and Non-Idempotent Ops
If the operation isn’t idempotent (like sum), why must you fall back to disjoint blocks and accept \( O(\log n) \) per query, or use a different structure?
Limits: No Updates
Why is a sparse table fundamentally static, and what breaks the moment an array element changes?
Time Complexity
Reason about preprocessing and query separately.
Why Preprocessing Is O(n log n)
There are \( \log n \) rows of \( n \) entries, each filled in \( O(1) \) from the row below. Why does that multiply to \( O(n \log n) \)?
Why Query Is O(1)
A query reads exactly two precomputed entries and merges them. Why does that hold regardless of the range length, and what role does the precomputed log table play?
Why Updates Are Effectively O(n log n)
If one element changes, how many table entries could depend on it, and why does that force a near-total rebuild?
Space Complexity
Account for the table dimensions.
Why O(n log n) Memory
Why does storing one answer per (power, start) pair give an \( n \times \log n \) table, and how does that compare to a segment tree’s \( O(n) \)?
The Log Table Itself
How much extra space does the precomputed \( \lfloor \log_2 \rfloor \) array cost, and why is it negligible?
Sparse Table vs Segment Tree vs Fenwick
When does the \( O(1) \) query and simple code win, and when do you need a segment tree or BIT instead?
Real Uses
Where do static idempotent range queries appear — range minimum for LCA, range gcd, and as a building block in offline algorithms?
Pitfalls
What goes wrong using a sparse table for sums, getting the log floor off by one, or under-sizing the \( k \) dimension?
Implementation Walkthrough
Plan the two-dimensional table before writing it.
Sizing the Table
How many rows (powers of two) do you need for length \( n \), and how do you size the second dimension? Prompts only.
Precomputing Logs
How do you fill the \( \lfloor \log_2 \rfloor \) array in one linear pass so each query can look it up?
Filling Rows by Doubling
What’s the recurrence that combines two length-\( 2^{k-1} \) blocks into a length-\( 2^k \) block, and what bounds the start index in each row?
The Two-Block Query
Given \( l, r \), how do you compute \( k \), pick the two overlapping blocks, and merge them — as a prompt, not code?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn.
Suffix Arrays
What It’s For
What problem does a suffix array solve — fast substring search and string analysis — with far less memory than a suffix tree?
The Core Idea: Sorted Suffix Indices
What does \( \text{SA}[i] \) hold, and how is the array just the starting positions of all suffixes sorted lexicographically?
Naive Construction
Why does sorting all suffixes directly cost so much, and where does the \( O(n^2 \log n) \) come from in the comparisons?
Prefix-Doubling Construction
Trace how sorting by the first \( 2k \) characters reuses the ranks from sorting by the first \( k \), doubling the compared length each round.
Rank Arrays and Radix Sort
How do (rank, next-rank) pairs let each round sort in linear time with a radix/counting sort instead of a comparison sort?
Why Doubling Terminates Fast
Why are \( \lceil \log_2 n \rceil \) rounds enough for every suffix to be fully distinguished?
The LCP Array
What does the longest-common-prefix array store between adjacent suffixes in SA, and why does it unlock most string queries?
Kasai’s Linear LCP
What’s the key observation — that LCP drops by at most one as you move from the suffix at position \( p \) to position \( p+1 \) — that makes LCP construction \( O(n) \)?
Pattern Search via Binary Search
Trace locating all occurrences of a pattern of length \( m \) by binary searching the sorted suffixes in \( O(m \log n) \).
Applications via SA + LCP
How do you get longest repeated substring, longest common substring, distinct substring count, and range-minimum-on-LCP from these arrays?
Time Complexity
Separate construction from query, and compare construction methods.
Construction: Naive vs Doubling vs Linear
Why is naive \( O(n^2 \log n) \), doubling \( O(n \log n) \) (or \( O(n \log^2 n) \) with comparison sort), and what do DC3/SA-IS achieve? Name the bottleneck each method removes.
LCP Construction
Why is Kasai’s algorithm \( O(n) \) despite seeming to recompute prefixes — what does the “drop by at most one” invariant save?
Pattern Search
Why is binary-search pattern matching \( O(m \log n) \), and how does adding LCP information cut it toward \( O(m + \log n) \)?
Space Complexity
Account for every array you keep.
The Core Arrays
SA, rank, and LCP are each one integer per character. Why is that \( O(n) \) total, and why is it far smaller (constant factor) than a suffix tree?
Construction Scratch Space
What temporary arrays does prefix doubling or a linear constructor need, and why are they still \( O(n) \)?
Suffix Array vs Suffix Tree
Why is a suffix array usually preferred in practice — memory, cache, simplicity — and what does a suffix tree still do more directly?
Pitfalls
What goes wrong without a sentinel, with off-by-one in the doubling ranks, or assuming SA alone (no LCP) answers everything?
Implementation Walkthrough
Plan prefix-doubling construction before writing it.
Initial Ranks from Characters
How do you seed the rank array from the raw characters for \( k = 1 \)? Prompts only.
One Doubling Round
What sort key (current rank, rank \( k \) ahead) do you build, how do you sort it (radix/counting), and how do you reassign ranks afterward — handling ties?
Building LCP with Kasai
What order do you process suffixes in, and how do you carry the previous LCP value forward minus one? Sketch as a prompt.
Pattern Search Routine
How do you binary-search SA comparing the pattern against a suffix prefix, and how do you find the full range of matches?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn.
Suffix Trees
What It’s For
What does a suffix tree make instant — substring search, repeated/common substrings — that scanning the text repeatedly can’t?
Starting Point: Trie of All Suffixes
If you inserted every suffix into a trie, what would each root-to-leaf path spell, and why is that trie too big?
Path Compression into a Tree
Why collapse every non-branching chain into a single edge, and how does that bound the node count to \( O(n) \)?
Why O(n) Nodes After Compression
A compressed tree has at most \( n \) leaves and every internal node branches. Why does branching cap the internal node count below the leaf count?
Edge Labels as (start, end)
How do you label each edge with a pair of indices into the original text instead of copying substrings, achieving \( O(n) \) space?
The Sentinel Character
Why append a unique terminator that appears nowhere else, and what does it guarantee about every suffix ending at a distinct leaf?
Ukkonen’s Linear Construction
Sketch the online build — extending one character at a time, with the active point and suffix links keeping it linear.
Suffix Links
What does a suffix link connect, and why does following it let the build skip redundant re-descents from the root?
The Extension Rules and Tricks
What are the three extension cases, and how do “once a leaf, always a leaf” plus the global end pointer save work?
The Active Point
What three values (active node, active edge, active length) track where the next extension happens, and why does maintaining them avoid restarting from the root each step?
Queries It Unlocks
How do you read off substring membership, longest repeated substring, longest common substring (generalized tree), and pattern counting?
Time Complexity
Reason about why the online build is linear despite looking quadratic.
Why Ukkonen Is O(n)
Naively, each of \( n \) phases could do \( n \) extensions. What do suffix links, the global end pointer, and the active-point skip-count do to amortize all extensions down to \( O(n) \) total?
Query Costs
Why is substring search \( O(m) \) for a pattern of length \( m \), and how do you answer longest-repeated / longest-common in time proportional to the tree size?
Space Complexity
Account for nodes, edges, and links.
Why O(n) — and the Big Constant
With \( O(n) \) nodes, index-pair edge labels, child maps, and suffix links, why is the total still \( O(n) \)? Why is the hidden constant factor large compared to a suffix array?
Child Storage Trade-off
How does the choice between an array-per-node (fast, fat) and a hash/map-per-node (compact, slower) change the space and the alphabet dependence?
Suffix Tree vs Suffix Array
Why do suffix arrays often win on memory and cache, and when is the explicit tree structure worth its heavier footprint?
Pitfalls
What goes wrong forgetting the sentinel, mismanaging the active point, or storing literal substrings on edges instead of index pairs?
Implementation Walkthrough
Plan Ukkonen’s algorithm before writing it — it is famously tricky.
Node and Edge Representation
What does a node store (children map, suffix link, edge start, and an end that may be a shared global pointer)? Prompts only.
The Global End and Leaf Edges
Why do leaf edges share one “end” variable that you just increment each phase, and how does that implement “once a leaf, always a leaf”?
The Active Point and Skip/Count
How do you advance the active point and use the skip/count trick to walk down long edges in \( O(1) \) amortized per character?
Adding Suffix Links
When you split an edge and create an internal node, where do you point the suffix link from the previously created internal node?
Implementation
// implement from scratch here
Summary
Recap the whole topic in a few lines — the core idea, the key complexities and trade-offs, and when to reach for it. The cheat-sheet you’d skim before an exam or interview.
My Notes
Fill this in as you learn.
Advanced: Problem Set
These problems exercise the advanced range-query toolkit: prefix sums, Fenwick
(binary indexed) trees, segment trees with and without lazy propagation, order
statistics, interval stabbing, and inversion counting. Each problem is an
implementation task — fill in the matching stub in problemset/ and make its
test in tests/problemset/ pass. Inputs arrive as int[] arrays plus query or
operation lists encoded as small int[] records, so every solution is a pure
function with no I/O. The Foundational group builds the core primitives; the
Applied group weaves LeetCode classics with contest-style problems, roughly easy
to hard.
Foundational
Problem 1: Static Prefix Sums
Description
You are given an integer array nums and a list of inclusive range queries.
For each query [l, r] return the sum of nums[l..r]. The array is never
modified, so precompute a prefix-sum table once and answer each query in
O(1). Return the answers in the same order as the queries.
Examples
Example 1:
Input: nums = [1, 2, 3, 4, 5], queries = [[0, 2], [1, 3], [0, 4]]
Output: [6, 9, 15]
Query [0,2] sums 1+2+3=6; [1,3] sums 2+3+4=9; [0,4] sums all 15.
Example 2:
Input: nums = [-2, 0, 3, -5, 2, -1], queries = [[0, 2], [2, 5], [0, 5]]
Output: [1, -1, -3]
A negative reading is handled the same as any other.
Example 3:
Input: nums = [7], queries = [[0, 0]]
Output: [7]
Constraints
1 <= nums.length <= 1e5-1e4 <= nums[i] <= 1e40 <= l <= r < nums.length- Sums fit in a
long.
Problem 2: Fenwick Tree Point Update, Prefix Sum
Description
Build a Fenwick (binary indexed) tree over an initial array and replay an
operation stream. Each operation is a 3-element array {type, a, b}: type 0
adds b to index a; type 1 asks for the prefix sum over [0, a]. Return the
type-1 answers in order. Each operation must run in O(log n).
Examples
Example 1:
Input: nums = [1, 2, 3, 4], ops = [[1, 3], [0, 1, 10], [1, 3]]
Output: [10, 20]
Prefix [0,3] is 1+2+3+4=10; add 10 to index 1; prefix [0,3] is now 20.
Example 2:
Input: nums = [5, 5, 5], ops = [[1, 0], [1, 2]]
Output: [5, 15]
Example 3:
Input: nums = [0, 0, 0], ops = [[0, 1, 7], [1, 1]]
Output: [7]
Constraints
1 <= nums.length <= 1e5- Up to
1e5operations. - For a type-0 op,
0 <= a < nums.length. - For a type-1 op,
0 <= a < nums.length. - Prefix sums fit in a
long.
Problem 3: Build a Segment Tree for Range Minimum
Description
Construct a segment tree over an integer array and answer a batch of range-minimum
queries. Each query [l, r] asks for the minimum of nums[l..r] inclusive. The
array is static for this problem. Each query must be answered in O(log n).
Return the minima in query order.
Examples
Example 1:
Input: nums = [2, 5, 1, 4, 9, 3], queries = [[0, 2], [3, 5], [0, 5]]
Output: [1, 3, 1]
Minimum of [2,5,1] is 1; of [4,9,3] is 3; of the whole array is 1.
Example 2:
Input: nums = [8, 8, 8], queries = [[0, 0], [1, 2]]
Output: [8, 8]
Example 3:
Input: nums = [-3, 0, -7, 5], queries = [[1, 3], [0, 3]]
Output: [-7, -7]
Constraints
1 <= nums.length <= 1e5-1e9 <= nums[i] <= 1e90 <= l <= r < nums.length
Problem 4: Coordinate Compression
Description
Many order-statistics structures need values mapped to a dense 0..m-1 range.
Given an integer array values, return a new array ranks where ranks[i] is
the index of values[i] among the sorted distinct values (its compressed
coordinate). Equal values receive equal ranks. This is the standard preprocessing
step before a Fenwick tree over values.
Examples
Example 1:
Input: values = [40, 10, 20, 10, 30]
Output: [3, 0, 1, 0, 2]
Sorted distinct values are [10, 20, 30, 40], giving ranks 10->0, 20->1, 30->2, 40->3.
Example 2:
Input: values = [5, 5, 5]
Output: [0, 0, 0]
Example 3:
Input: values = [-2, 7, -2, 100]
Output: [0, 1, 0, 2]
Constraints
1 <= values.length <= 1e5-1e9 <= values[i] <= 1e9
Problem 5: Count Inversions
Description
Count the number of inversions in an array: index pairs (i, j) with i < j and
nums[i] > nums[j]. Use a merge-sort counting pass or a Fenwick tree over
compressed values to run in O(n log n). Return the count as a long.
Examples
Example 1:
Input: nums = [2, 4, 1, 3, 5]
Output: 3
The inversions are (2,1), (4,1), and (4,3).
Example 2:
Input: nums = [1, 2, 3, 4]
Output: 0
A sorted array has no inversions.
Example 3:
Input: nums = [5, 4, 3, 2, 1]
Output: 10
A strictly descending array of length 5 has every pair inverted.
Constraints
1 <= nums.length <= 1e5-1e9 <= nums[i] <= 1e9- The count fits in a
long.
Problem 6: K-th Smallest via Order-Statistics Tree
Description
Maintain a multiset of integers under an operation stream, starting empty. Each
operation is a 2-element array {type, value}: type 0 inserts value; type 1
deletes one occurrence of value; type 2 asks for the value-th smallest
element currently present (1-indexed). Use an order-statistics Fenwick tree over
compressed values so each operation runs in O(log n). Return the type-2 answers
in order. Deletes and selects are always valid.
Examples
Example 1:
Input: ops = [[0, 5], [0, 1], [0, 3], [2, 2]]
Output: [3]
After inserting 5, 1, 3 the sorted multiset is [1, 3, 5]; the 2nd smallest is 3.
Example 2:
Input: ops = [[0, 10], [0, 10], [2, 1], [2, 2]]
Output: [10, 10]
Duplicates are tracked by count.
Example 3:
Input: ops = [[0, 4], [0, 8], [1, 4], [2, 1]]
Output: [8]
After deleting the single 4, the smallest remaining is 8.
Constraints
- Up to
1e5operations. -1e9 <= value <= 1e9for insert/delete.- For a type-2 op,
1 <= value <= current multiset size.
Applied Problems
Problem 7: Range Sum Query - Immutable
LeetCode: 303. Range Sum Query - Immutable
Description
Given an integer array nums, answer a batch of inclusive range-sum queries
sumRange(l, r) = nums[l] + ... + nums[r]. The array is never modified.
Precompute a prefix-sum table so each query is O(1). Return the answers in
query order.
Examples
Example 1:
Input: nums = [-2, 0, 3, -5, 2, -1], queries = [[0, 2], [2, 5], [0, 5]]
Output: [1, -1, -3]
sumRange(0,2) = -2+0+3 = 1; sumRange(2,5) = 3-5+2-1 = -1; sumRange(0,5) = -3.
Example 2:
Input: nums = [1, 2, 3], queries = [[0, 0], [0, 2]]
Output: [1, 6]
Example 3:
Input: nums = [10], queries = [[0, 0]]
Output: [10]
Constraints
1 <= nums.length <= 1e4-1e5 <= nums[i] <= 1e50 <= l <= r < nums.length
Problem 8: Range Sum Query 2D - Immutable
LeetCode: 304. Range Sum Query 2D - Immutable
Description
Given a 2D integer matrix, answer rectangle-sum queries. Each query is a 4-element
array {r1, c1, r2, c2} asking for the sum of the submatrix whose upper-left
corner is (r1, c1) and lower-right corner is (r2, c2), inclusive. Precompute a
2D prefix-sum table so each query is O(1). Return the answers in query order.
Examples
Example 1:
Input: matrix = [[3, 0, 1, 4, 2],
[5, 6, 3, 2, 1],
[1, 2, 0, 1, 5],
[4, 1, 0, 1, 7],
[1, 0, 3, 0, 5]],
queries = [[2, 1, 4, 3], [1, 1, 2, 2], [1, 2, 2, 4]]
Output: [8, 11, 12]
The first rectangle (2,1)-(4,3) sums to 8.
Example 2:
Input: matrix = [[1, 2], [3, 4]], queries = [[0, 0, 1, 1]]
Output: [10]
Example 3:
Input: matrix = [[5]], queries = [[0, 0, 0, 0]]
Output: [5]
Constraints
1 <= rows, cols <= 200-1e5 <= matrix[i][j] <= 1e50 <= r1 <= r2 < rows,0 <= c1 <= c2 < cols
Problem 9: Range Sum Query - Mutable
LeetCode: 307. Range Sum Query - Mutable
Description
Support point updates interleaved with range-sum queries over an array. Each
operation is a 3-element array {type, a, b}: type 0 sets index a to value
b; type 1 asks for the inclusive range sum over [a, b]. Back the array with
a Fenwick tree so each operation runs in O(log n). Return the type-1 answers in
order.
Examples
Example 1:
Input: nums = [1, 3, 5], ops = [[1, 0, 2], [0, 1, 2], [1, 0, 2]]
Output: [9, 8]
Query [0,2] is 9; set index 1 to 2; query [0,2] is now 1+2+5=8.
Example 2:
Input: nums = [2, 4, 6, 8], ops = [[1, 1, 3], [0, 2, 10], [1, 0, 3]]
Output: [18, 24]
Example 3:
Input: nums = [7], ops = [[1, 0, 0], [0, 0, 1], [1, 0, 0]]
Output: [7, 1]
Constraints
1 <= nums.length <= 3e4-100 <= nums[i] <= 100,-100 <= b <= 100for updates.- Up to
3e4operations. - Range sums fit in a
long.
Problem 10: Range Minimum Queries with Updates
Description
A monitoring array supports point updates interleaved with range-minimum queries.
Each operation is a 3-element array {type, a, b}: type 0 sets index a to
value b; type 1 asks for the minimum reading over the inclusive range
[a, b]. Use a segment tree so each operation runs in O(log n). Return the
type-1 answers in order.
Examples
Example 1:
Input: nums = [3, 7, 2, 9, 4], ops = [[1, 0, 2], [0, 1, 1], [1, 0, 2]]
Output: [2, 1]
Min of [3,7,2] is 2; set index 1 to 1; min of [3,1,2] is 1.
Example 2:
Input: nums = [5, 4, 6], ops = [[1, 0, 2]]
Output: [4]
Example 3:
Input: nums = [10, 20], ops = [[0, 0, 100], [1, 0, 1]]
Output: [20]
Constraints
1 <= nums.length <= 1e5-1e9 <= nums[i] <= 1e9- Up to
1e5operations.
Problem 11: Range Counts in Sorted Data
Description
A sensor field reports n integer readings. For each query [lo, hi] report how
many readings fall in the inclusive range [lo, hi]. Sort the readings once, then
binary-search both bounds per query so each costs O(log n). Return the counts in
query order.
Examples
Example 1:
Input: readings = [4, 1, 7, 3, 9, 3], queries = [[3, 7], [0, 1], [5, 100]]
Output: [4, 1, 2]
Values in [3,7] are 4, 7, 3, 3 (four); in [0,1] only 1; in [5,100] are 7, 9.
Example 2:
Input: readings = [5, 5, 5], queries = [[5, 5], [6, 10]]
Output: [3, 0]
Example 3:
Input: readings = [-3, 0, 8], queries = [[-10, 0]]
Output: [2]
Constraints
1 <= readings.length <= 1e5- Up to
1e5queries. - Values and bounds fit in an
int.
Problem 12: Count of Range Sum
LeetCode: 327. Count of Range Sum
Description
Given an integer array nums and an inclusive range [lower, upper], count the
number of range sums S(i, j) = nums[i] + ... + nums[j] (with i <= j) that lie
within [lower, upper]. Compute prefix sums, then count qualifying prefix-pair
differences in O(n log n) via merge-sort counting or a Fenwick tree over
compressed prefix sums. Return the count as a long.
Examples
Example 1:
Input: nums = [-2, 5, -1], lower = -2, upper = 2
Output: 3
The qualifying range sums are -2, -1, and 2.
Example 2:
Input: nums = [0], lower = 0, upper = 0
Output: 1
Example 3:
Input: nums = [1, 2, 3], lower = 3, upper = 5
Output: 3
Sums in range are 3 ([1,2]), 5 ([2,3]), and 3 (the single 3).
Constraints
1 <= nums.length <= 1e5-1e9 <= nums[i] <= 1e9-1e15 <= lower <= upper <= 1e15- The count fits in a
long.
Problem 13: Reverse Pairs
LeetCode: 493. Reverse Pairs
Description
Given an integer array nums, count the number of reverse pairs: index pairs
(i, j) with i < j and nums[i] > 2 * nums[j]. Use a modified merge-sort
counting pass or a Fenwick tree over compressed values for O(n log n). Return
the count as a long.
Examples
Example 1:
Input: nums = [1, 3, 2, 3, 1]
Output: 2
The reverse pairs are at indices (1,4) and (3,4): 3 > 2*1.
Example 2:
Input: nums = [2, 4, 3, 5, 1]
Output: 3
Example 3:
Input: nums = [1, 2, 3, 4, 5]
Output: 0
Constraints
1 <= nums.length <= 5e4-2^31 <= nums[i] <= 2^31 - 1- The count fits in a
long.
Problem 14: Count of Smaller Numbers After Self
LeetCode: 315. Count of Smaller Numbers After Self
Description
Given an integer array nums, return an array counts where counts[i] is the
number of elements to the right of nums[i] that are strictly smaller than it.
Process from right to left, inserting compressed values into a Fenwick tree and
querying how many smaller values were already seen, for O(n log n) total.
Examples
Example 1:
Input: nums = [5, 2, 6, 1]
Output: [2, 1, 1, 0]
Right of 5 are 2, 1 (two smaller); right of 2 is 1 (one); right of 6 is 1 (one).
Example 2:
Input: nums = [-1, -1]
Output: [0, 0]
Example 3:
Input: nums = [3, 2, 2, 6, 1]
Output: [3, 1, 1, 1, 0]
Constraints
1 <= nums.length <= 1e5-1e4 <= nums[i] <= 1e4
Problem 15: Range Frequency Queries
LeetCode: 2080. Range Frequency Queries
Description
Given an integer array arr, answer queries asking how many times a given value
appears within an inclusive index range. Each query is a 3-element array
{l, r, value}. Precompute, for each distinct value, the sorted list of indices
where it occurs, then binary-search those indices to count how many fall in
[l, r]. Return the answers in query order.
Examples
Example 1:
Input: arr = [12, 33, 4, 56, 22, 2, 34, 33, 22, 12, 34, 56],
queries = [[1, 2, 4], [0, 11, 33], [0, 11, 22]]
Output: [1, 2, 2]
Value 4 appears once in [1,2]; 33 appears twice overall; 22 appears twice.
Example 2:
Input: arr = [1, 1, 1, 1], queries = [[0, 3, 1], [1, 2, 1]]
Output: [4, 2]
Example 3:
Input: arr = [7, 8, 9], queries = [[0, 2, 5]]
Output: [0]
Constraints
1 <= arr.length <= 1e5- Up to
1e5queries. - Values fit in an
int;0 <= l <= r < arr.length.
Problem 16: Interval Stabbing Counts
Description
You are given n closed intervals [start, end] and q query points. For each
query point report how many intervals cover it (inclusive of endpoints). With all
intervals and queries known up front, answer every query offline in
O((n + q) log(n + q)) using a sorted boundary sweep or a difference array over
compressed coordinates. Return the counts in query order.
Examples
Example 1:
Input: intervals = [[1, 5], [2, 6], [4, 8]], points = [3, 7, 1]
Output: [2, 1, 1]
Point 3 lies in [1,5] and [2,6]; point 7 only in [4,8]; point 1 only in [1,5].
Example 2:
Input: intervals = [[0, 0]], points = [0, 1]
Output: [1, 0]
Example 3:
Input: intervals = [[1, 10], [1, 10], [1, 10]], points = [5]
Output: [3]
Constraints
1 <= n, q <= 1e5- Coordinates fit in an
int;start <= end.
Problem 17: Range Update, Point Query
Description
A ledger over n positions supports range-add updates interleaved with single
point queries. Each operation is a 4-element array: {0, l, r, delta} adds delta
to every position in [l, r]; {1, i, 0, 0} queries the current value at position
i. Use a difference-array Fenwick tree (point query = prefix sum of deltas) so
each operation runs in O(log n). The array starts at all zeros. Return the
type-1 answers in order.
Examples
Example 1:
Input: n = 5, ops = [[0, 1, 3, 2], [1, 2, 0, 0], [1, 4, 0, 0]]
Output: [2, 0]
Adding 2 to [1,3] makes position 2 equal 2; position 4 is untouched (0).
Example 2:
Input: n = 3, ops = [[0, 0, 2, 5], [0, 1, 1, 3], [1, 1, 0, 0]]
Output: [8]
Position 1 receives 5 then 3, totaling 8.
Example 3:
Input: n = 4, ops = [[1, 0, 0, 0]]
Output: [0]
Constraints
1 <= n <= 1e5- Up to
1e5operations. -1e9 <= delta <= 1e9; values fit in along.
Problem 18: Range Update, Range Sum (Lazy Segment Tree)
Description
A reactor logs coolant pressure across n segments, all starting at zero. Apply
range-add adjustments and answer range-sum queries, each in O(log n) with a
lazy-propagation segment tree. Operations are 4-element arrays: {0, l, r, delta}
adds delta across [l, r]; {1, l, r, 0} queries the inclusive sum over
[l, r]. Return the type-1 answers in order.
Examples
Example 1:
Input: n = 5, ops = [[0, 0, 2, 3], [0, 1, 4, 2], [1, 0, 4, 0]]
Output: [19]
Add 3 to [0,2] (sum 9) and 2 to [1,4] (adds 8); total over [0,4] is 19.
Example 2:
Input: n = 3, ops = [[0, 0, 2, 4], [1, 1, 2, 0]]
Output: [8]
Example 3:
Input: n = 4, ops = [[1, 0, 3, 0]]
Output: [0]
Constraints
1 <= n <= 1e5- Up to
1e5operations. -1e9 <= delta <= 1e9; sums fit in along.
Problem 19: Count Intervals Covering Points (Mixed Stream)
Description
A coverage tracker processes a stream over an initially empty set of closed
integer intervals. Each operation is a 3-element array {type, a, b}: type 0
adds the interval [a, b]; type 1 asks how many currently-present intervals
cover the single point a (inclusive; b is ignored). Because additions and
queries are interleaved, maintain a multiset of active interval boundaries and
answer each query in O(log n) (e.g. a Fenwick tree over compressed coordinates
counting starts <= a minus ends < a). Return the type-1 answers in order.
Examples
Example 1:
Input: ops = [[0, 1, 5], [0, 3, 8], [1, 4, 0], [1, 7, 0]]
Output: [2, 1]
After adding [1,5] and [3,8]: point 4 is covered by both; point 7 by only [3,8].
Example 2:
Input: ops = [[0, 0, 10], [1, 0, 0], [1, 10, 0], [1, 11, 0]]
Output: [1, 1, 0]
Endpoints are inclusive, so 0 and 10 are covered; 11 is not.
Example 3:
Input: ops = [[1, 5, 0]]
Output: [0]
With no intervals added yet, nothing covers 5.
Constraints
- Up to
1e5operations. - Coordinates fit in an
int; for additionsa <= b.
Problem 20: Range Distinct Values (Offline)
Description
An archive stores n artifact codes in a fixed array. A batch of q queries each
asks how many distinct codes appear in an inclusive span [l, r]. With all queries
known up front, sort them by right endpoint and sweep a Fenwick tree that keeps
only each value’s last occurrence alive: as the sweep advances r, deactivate any
earlier index holding the same value and activate the current one, so a query is a
range count over active positions. Answer every query in O((n + q) log n) and
return answers in input order.
Examples
Example 1:
Input: codes = [1, 1, 2, 1, 3], queries = [[0, 1], [0, 4], [2, 4]]
Output: [1, 3, 3]
[0,1] has only {1}; [0,4] has {1,2,3}; [2,4] has {2,1,3}.
Example 2:
Input: codes = [5, 5, 5], queries = [[0, 2]]
Output: [1]
Example 3:
Input: codes = [4, 7, 4, 7], queries = [[0, 3], [1, 2]]
Output: [2, 2]
Constraints
1 <= n, q <= 1e5- Codes fit in an
int;0 <= l <= r < n.
Problem 21: My Calendar III (Max Concurrent Intervals)
LeetCode: 732. My Calendar III
Description
You are given a list of half-open booking intervals [start, end) processed in
order. After each booking, report the maximum number of intervals that overlap at
any single point so far (the maximum “k-booking”). Use a sweep with a difference
map over event coordinates, or a segment tree with range-add and range-max. Return
an array whose i-th entry is the maximum overlap after the first i+1 bookings.
Examples
Example 1:
Input: bookings = [[10, 20], [50, 60], [10, 40], [5, 15], [5, 10], [25, 55]]
Output: [1, 1, 2, 3, 3, 3]
The third booking overlaps the first, giving 2; the fourth pushes a point to 3.
Example 2:
Input: bookings = [[0, 10], [10, 20]]
Output: [1, 1]
Half-open intervals [0,10) and [10,20) do not overlap at point 10.
Example 3:
Input: bookings = [[1, 100], [2, 99], [3, 98]]
Output: [1, 2, 3]
Constraints
1 <= bookings.length <= 4000 <= start < end <= 1e9
Problem 22: The Skyline Problem
LeetCode: 218. The Skyline Problem
Description
Given the buildings of a city as triples {left, right, height}, compute the
skyline: the list of “key points” [x, height] marking where the visible outline
changes, sorted by x. Sweep the building edges left to right, maintaining the
current maximum active height (e.g. a multiset / max-heap with lazy deletion), and
emit a key point whenever that maximum changes. The skyline ends with a key point
at the rightmost edge with height 0. Return the key points as an int[][].
Examples
Example 1:
Input: buildings = [[2, 9, 10], [3, 7, 15], [5, 12, 12], [15, 20, 10], [19, 24, 8]]
Output: [[2, 10], [3, 15], [7, 12], [12, 0], [15, 10], [20, 8], [24, 0]]
The outline rises to 15 at x=3, drops to 12 when the tall building ends, and so on.
Example 2:
Input: buildings = [[0, 2, 3], [2, 5, 3]]
Output: [[0, 3], [5, 0]]
Two abutting buildings of equal height merge into one flat segment.
Example 3:
Input: buildings = [[1, 3, 4]]
Output: [[1, 4], [3, 0]]
Constraints
1 <= buildings.length <= 1e40 <= left < right <= 2^31 - 1,1 <= height <= 2^31 - 1
Problem 23: Falling Squares
LeetCode: 699. Falling Squares
Description
Axis-aligned squares drop one at a time onto a number line. Square i is given as
{left, sideLength} and occupies the interval [left, left + sideLength). When a
square lands it rests on top of whatever is already in its horizontal span. After
each drop, report the height of the tallest stack anywhere. Use coordinate
compression with a segment tree supporting range-max query and range-assign.
Return an array whose i-th entry is the tallest height after the first i+1
drops.
Examples
Example 1:
Input: squares = [[1, 2], [2, 3], [6, 1]]
Output: [2, 5, 5]
The first square rests at height 2; the second overlaps it and stacks to 2+3=5;
the third lands elsewhere at height 1, so the max stays 5.
Example 2:
Input: squares = [[100, 100], [200, 100]]
Output: [100, 100]
The squares do not overlap, so each rests on the ground.
Example 3:
Input: squares = [[1, 5], [2, 2]]
Output: [5, 7]
Constraints
1 <= squares.length <= 1e31 <= left, sideLength <= 1e8
Problem 24: Number of Longest Increasing Subsequence via Fenwick
LeetCode: 673. Number of Longest Increasing Subsequence
Description
Given an integer array nums, return the number of longest strictly increasing
subsequences. Process elements left to right over compressed values, and for each
value query a Fenwick tree keyed by value that stores, for the best LIS length
ending at a smaller value, both that length and the count of ways to achieve it.
Combine to extend, then update the tree at the current value. This yields the LIS
length and the number of such subsequences in O(n log n). Return the count.
Examples
Example 1:
Input: nums = [1, 3, 5, 4, 7]
Output: 2
The LIS length is 4; the two longest are [1,3,5,7] and [1,3,4,7].
Example 2:
Input: nums = [2, 2, 2, 2, 2]
Output: 5
The longest strictly increasing subsequence has length 1, and there are five of them.
Example 3:
Input: nums = [1, 2, 4, 3, 5, 4, 7, 2]
Output: 3
Constraints
1 <= nums.length <= 2e3-1e6 <= nums[i] <= 1e6