skip to content

Problem Archetypes

A handful of recurring problem families cover most interview DP. Each family is defined by a recurrence you can state and defend — recognize the family and the state, transition, and complexity follow almost mechanically.

part ofData structures & algorithmsoverview, primer and where to startread it →
on this pageshow

questions

page 1 of 2

In a right-and-down-only grid, why is a cell's route count the sum of the cells above and left?

level: juniorimportance: must knowfreq 78%

answer

  1. think about the very last step
  2. how can a route enter this cell
  3. only two predecessors exist here
  4. can one route arrive from both?
  5. disjoint alternatives mean counts add

basics

~20 s

Every route into a cell takes its last step from either the cell above or the cell to its left. Those two sets of routes are disjoint and together cover every route, so the two counts simply add.

solid answer

~40 s

Fix a cell and look only at the final step of any route that ends there. Because moves are restricted to right and down, that step came from exactly one of two cells: the one directly above or the one directly to the left. No route arrives from both, so the sets do not overlap and nothing needs subtracting; no other cell can be the predecessor, so nothing is missed. That makes `routes[i][j] = routes[i-1][j] + routes[i][j-1]`, with the top row and left column seeded to 1 on an open grid because there is a single straight-line approach along each border. Filling the table left to right, row by row, is `O(m*n)` time and `O(m*n)` space, and every cell is evaluated exactly once.

go deeper

for a junior

Be ready to state the recurrence and its border seeds out loud, and to fill a three-by-four grid by hand in under a minute without hesitating on the base row.

for a middle

Expect to justify why the two predecessor sets are disjoint and exhaustive, and to give the time and space bounds plus the cost of the un-memoized recursion without hedging.

for a senior

Show that you know when the table is the wrong tool — an open floor has a closed-form count, and per-cell costs turn counting into optimizing with the same skeleton. Name the memory footprint at real grid sizes.

for a principal

Own the framing before the recurrence: does the product need a count, a single cheapest route, or every route enumerated? The first two are cheap table fills; the third has exponential output and no technique rescues it.

## The setting Picture a warehouse floor as a rectangular grid of `m` rows and `n` columns. A picking robot starts in the top-left cell, must finish in the bottom-right cell, and physically can only drive one aisle to the right or one aisle down — it never reverses. The question "how many distinct routes exist?" is the smallest complete example of grid dynamic programming, and it is worth being able to derive rather than recite. ## The state Define `routes[i][j]` as the number of distinct routes from the start cell to cell `(i, j)`. That single number is the entire state: it does not matter which of those routes was taken, how long ago, or in what order the moves happened. Everything the rest of the computation needs about cell `(i, j)` is captured by that count. This is what makes the problem a dynamic-programming problem rather than a search — the past collapses into one number per cell. ## The last-step argument Take any route that ends at `(i, j)` and delete its final move. The move was either a downward step, in which case the route previously stood at `(i-1, j)`, or a rightward step, in which case it stood at `(i, j-1)`. Two properties make the recurrence fall out: - **Exhaustive.** Those are the only two legal ways to enter the cell, so no route is unaccounted for. - **Disjoint.** A single route has exactly one last step, so it is counted under exactly one predecessor. There is no overlap to subtract. When alternatives are disjoint and exhaustive, their counts add. Multiplication would be the answer to a different question — the number of ways to make two independent choices *in sequence*, not the number of ways to make one choice *out of two*. Confusing the two is the single most common error here. ``` routes[i][j] = routes[i-1][j] + routes[i][j-1] ``` ## Base cases On an open grid, every cell in the top row has exactly one approach — drive straight right from the start — so it holds 1. The same holds down the left column. The start cell itself holds 1 (the empty route). Those seeds are not arbitrary: they are what the recurrence degenerates to when one predecessor is off the grid and therefore contributes 0. ## A worked fill A 3-row, 4-column floor fills like this: | | col 0 | col 1 | col 2 | col 3 | |---|---|---|---|---| | **row 0** | 1 | 1 | 1 | 1 | | **row 1** | 1 | 2 | 3 | 4 | | **row 2** | 1 | 3 | 6 | 10 | Ten routes. Notice the fill order: each cell is computed only after both predecessors already hold final values, which is why a simple left-to-right, top-to-bottom sweep is a valid evaluation order. ## Cost, and why the naive recursion is not this The table has `m*n` cells and each costs constant work, so the fill is `O(m*n)` time and `O(m*n)` space. The plain recursion that asks "routes to me = routes to my two predecessors" without storing anything has a completely different cost: it re-explores a subtree once per route reaching a cell, so its running time is proportional to the answer itself — which grows exponentially in grid size. Adding a memo table, or sweeping bottom-up, collapses that to one evaluation per cell. The asymptotic gap between the two is the whole reason the technique has a name. ## Why there is no visited set Engineers arriving from graph traversal often reach for a visited set out of habit, expecting to guard against revisiting a cell. Here it is unnecessary, and saying otherwise signals a misunderstanding of what the table is. Every legal move increases `i + j` by exactly one, so that quantity strictly increases along any route: a cell can never be re-entered, and cycles are impossible by construction. The subproblem graph is acyclic, and the table is a cache of answers, not a record of where a walker has been. A visited set exists to break cycles; a memo table exists to avoid recomputation. They solve different problems and one is not a substitute for the other. ## Where the shape stops being this easy Two edits break the simple version. Blocked cells invalidate the uniform border seeding, because a block in the top row makes every cell past it unreachable. Per-cell costs change the question from counting to optimizing, which swaps the `+` for a `min` over the two predecessors and keeps everything else identical. Both variants reuse the same last-step argument — only the operator and the seeds change.

  • Why does this DP need no visited set, when a grid traversal usually does?
    Because every legal move increases row plus column by one, so that quantity strictly increases along any route and no cell can ever be re-entered. The subproblem graph is acyclic by construction. A visited set exists to break cycles; the table here exists only to avoid recomputing an answer, and those are different jobs.
  • On a grid with nothing blocked, can you skip the table entirely?
    Yes. Any route is a fixed sequence of `m-1` downward steps and `n-1` rightward steps, so counting routes is counting which positions in that sequence are the downward ones: `C(m+n-2, m-1)`. That is `O(m+n)` arithmetic instead of `O(m*n)`. It collapses the moment blocked cells or per-cell costs appear, and the value itself explodes — a 35-by-35 floor already exceeds a signed 64-bit range.
  • How would the recurrence change if the robot could also move diagonally down-right?
    A third disjoint predecessor joins the sum: `routes[i-1][j-1]`. The last-step argument is unchanged — the alternatives are still mutually exclusive and still exhaustive — so the counts still add. Time stays `O(m*n)` with a slightly larger constant, and the border seeding still holds because the diagonal predecessor is off-grid there.

Every guest in a room walked through one of two doors. Count the arrivals at each door and add — nobody squeezed through both at once, so there is no double-counting to correct.

saying these in an interview costs you the question

  • Multiplies the two predecessor counts instead of adding them
  • Subtracts an overlap that cannot exist
  • Claims a visited set is needed to avoid revisiting cells
  • Seeds the top row and left column with 0
  • Says plain recursion is fine because the grid is small

context

open as a page

In 0/1 knapsack, what does dp[i][w] hold and which two candidates does the recurrence compare?

level: juniorimportance: must knowfreq 78%

basics

~20 s

dp[i][w] is the best total value reachable using only the first i items within a capacity of w. The recurrence takes the larger of leaving item i out, dp[i-1][w], and taking it, value[i] + dp[i-1][w - weight[i]], when it fits.

open as a page

In minimum-coins change-making, what does dp[a] hold and how do you mark unreachable amounts?

level: juniorimportance: must knowfreq 78%

basics

~10 s

dp[a] holds the fewest coins summing exactly to amount a: one plus the smallest dp[a - c] over each denomination c that fits. Amounts no combination reaches carry an infinity sentinel, never 0.

open as a page

Why does greedily picking the highest-value non-adjacent ad slots miss the optimal schedule?

level: juniorimportance: must knowfreq 74%

basics

~20 s

Greedy commits to a big slot before knowing what it blocks: taking a slot forbids both neighbours, so two merely good neighbours can outvalue one great slot. The take-or-skip recurrence best(i) = max(best(i-1), value(i) + best(i-2)) weighs both futures.

open as a page

In an edit distance table over two contact names, what does dp[i][j] mean and what fills row 0?

level: juniorimportance: must knowfreq 72%

basics

~20 s

dp[i][j] is the edit distance between the first i characters of one name and the first j of the other — the indexes are prefix LENGTHS, not positions. Row 0 holds 0,1,2,...,j: turning nothing into a j-character prefix costs j insertions.

open as a page

Palindromic substring vs subsequence: why do the two answers differ on the same gene string?

level: juniorimportance: must knowfreq 72%

basics

~20 s

A substring is contiguous; a subsequence keeps order but may skip characters. Every palindromic substring is also a palindromic subsequence, so the subsequence answer is never shorter — and on real sequence data it is usually far longer.

open as a page

Why does subset DP over a courier's route need dp[mask][last] rather than dp[mask] alone?

level: middleimportance: must knowfreq 45%

basics

~20 s

The visited set alone does not determine the remaining cost: the next leg's travel time depends on which site the courier is standing at. dp[mask][last] carries both the set of visited sites and the current position.

open as a page

Why is interval DP O(n^3) when its dp table holds only O(n^2) entries?

level: middleimportance: must knowfreq 55%

basics

~20 s

Each of the O(n^2) subranges is not filled in constant time: computing dp[i][j] scans every split point between i and j, which is up to O(n) work per entry. States times transitions-per-state gives O(n^2) * O(n) = O(n^3).

open as a page

In a tree DP that forbids picking a node and its parent together, why keep two values per node?

level: middleimportance: must knowfreq 55%

basics

~20 s

Keep two numbers per node: the best subtree total with that node picked, and with it skipped. A parent that picks itself needs its children's skipped totals, so collapsing to one best-per-node throws away exactly the value the parent needs.

open as a page

In a warehouse grid with blocked aisles, why can't the whole top row be seeded with 1 route?

level: middleimportance: must knowfreq 58%

basics

~20 s

A blocked cell in the top row cuts the row: every cell past it is unreachable and must hold 0, not 1. The same applies down the left column, and a blocked start cell makes the whole answer 0.

open as a page

How does subset-sum decide whether valued items split into two equal-value halves?

level: middleimportance: must knowfreq 66%

basics

~20 s

Add everything up; an odd total cannot halve, so answer no immediately. Otherwise ask subset-sum whether some subset reaches exactly half the total, using a boolean table where a cell means the sum is reachable rather than storing a maximum.

open as a page

In 1D unbounded knapsack, why must the capacity loop ascend and reuse cells updated in the same pass?

level: middleimportance: must knowfreq 62%

basics

~20 s

Ascending capacity means dp[w - weight] was already updated for the current item, so it may already contain copies of that item — which is exactly how unlimited reuse is encoded. Descending would freeze it at its pre-item value.

open as a page

In Kadane's algorithm over signed sensor drift readings, why does clamping the running sum at zero break?

level: middleimportance: must knowfreq 66%

basics

~20 s

Clamping the running sum at zero silently allows the empty window, so on readings that are all negative the scan returns 0 instead of the least-negative reading. Use running = max(reading, running + reading), seeded from the first reading.

open as a page

In expand-around-center palindrome search, why must you consider 2n-1 centers, not n?

level: middleimportance: must knowfreq 66%

basics

~20 s

Palindromes come in odd and even lengths. Expanding from single characters only finds odd ones; an even-length palindrome is centred in the gap between two adjacent characters. That is n character centres plus n-1 gap centres, so 2n-1 in all.

open as a page

In a tree DP for the longest path, why does each node combine two child branches but return only one?

level: seniorimportance: must knowfreq 50%

basics

~20 s

A longest path bends at exactly one node, arriving up one branch and leaving down another, so every node tests its two deepest branches against a running global best. It returns only its single deepest branch, because a parent can extend just one.

open as a page

Why does bitmask DP over subsets stay feasible near n = 20 but collapse by n = 30?

level: middleimportance: should knowfreq 42%

basics

~10 s

Each extra element doubles the table. Over 20 elements a dp[mask][last] table holds about 21 million entries; over 30 it holds roughly 32 billion — far past memory before a single transition is counted.

open as a page

In interval DP, why can't the dp[i][j] table be filled in plain row-major order?

level: middleimportance: should knowfreq 45%

basics

~20 s

An interval DP entry dp[i][j] is built from dp[i][k] and dp[k][j] for split points i < k < j, which are always shorter subranges. Row-major order reaches dp[k][j] before that row is written, so fill by increasing interval length instead.

open as a page

Why does a post-order tree DP that combines every child at each node still run in O(n)?

level: middleimportance: should knowfreq 45%

basics

~20 s

Count the work per edge, not per node. Every parent-child link is used exactly once, and a tree on n nodes has n-1 links, so the total combining work is O(n) as long as each node spends constant time per child.

open as a page

In a triangular cost pyramid, why is filling from the bottom row upward simpler than from the apex?

level: middleimportance: should knowfreq 44%

basics

~20 s

Filling upward, every cell has exactly two children below it, so one uniform rule fills the whole pyramid and the answer lands at the apex. Filling downward needs edge-case handling and a final scan of the bottom row.

open as a page

In count-the-ways DP with unlimited item types, why does moving the target loop outside change the count?

level: middleimportance: should knowfreq 55%

basics

~20 s

An outer item loop fixes one item order, so each unordered selection is counted once — combinations. An outer target loop lets every item be the last one added at every target, so orderings count separately — permutations.

open as a page

Why doesn't the longest increasing run of daily active users equal the longest increasing subsequence?

level: middleimportance: should knowfreq 58%

basics

~20 s

A run must be contiguous; a subsequence only keeps the original order and may skip days. So the longest run is a lower bound on the longest increasing subsequence — one pass finds it, while the subsequence needs a dynamic program.

open as a page

After filling an LCS table for two deployment config files, how do you walk it back to emit the diff?

level: middleimportance: should knowfreq 55%

basics

~20 s

Start at the bottom-right cell and walk to the origin. Equal lines mean a diagonal step and an unchanged line; otherwise move toward the larger neighbour — up emits a removal, left an addition. The walk is O(m+n) and its output arrives reversed.

open as a page

How does the longest-common-subsequence recurrence really differ from edit distance, beyond max versus min?

level: middleimportance: should knowfreq 58%

basics

~20 s

The mismatch branch differs, not just the direction of optimization. On unequal characters edit distance may still take the diagonal — that is a replacement — while a subsequence table never can, since unequal symbols cannot pair. Base rows differ too.

open as a page

Why does the LCS of a gene string with its reverse give its longest palindrome by deletion?

level: middleimportance: should knowfreq 58%

basics

~20 s

A palindrome reads identically forwards and backwards, so any palindromic subsequence of a string is also a subsequence of its reverse — making it a common subsequence of the two. No common subsequence can beat the best palindrome either, so the two lengths are equal.

open as a page

Why must a palindrome interval DP table be filled by increasing substring length?

level: middleimportance: should knowfreq 52%

basics

~20 s

The recurrence reads dp[i+1][j-1] — a shorter interval sitting one row below and one column left. A plain row-by-row, left-to-right fill reaches that cell before writing it, so ordering by span length guarantees every dependency is already computed.

open as a page

In n-worker n-shift assignment DP, why does dp[mask] alone suffice with no worker index?

level: seniorimportance: should knowfreq 26%

basics

~20 s

Processing workers in a fixed index order makes the worker index a function of the mask: the number of set bits is how many workers are already placed. Carrying that index separately multiplies the table by n and adds nothing.

open as a page

In a pipe-cutting model where each cut costs the length of the piece it splits, why does taking the cheapest cut first fail?

level: seniorimportance: should knowfreq 40%

basics

~20 s

A cut's price is set by the piece it lands in, so every choice reprices the remaining cuts and no exchange argument holds. Order is a global optimisation over subranges, solved by interval DP branching on the first cut.

open as a page

What do you give up by compressing a min-cost grid route DP to a single rolling row?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Time stays the same and memory drops from one number per cell to one row, but each row overwrites the last, so the cheapest route can no longer be traced back. Recovering the route needs stored decisions or a recompute.

open as a page

In a compressed 1D 0/1 knapsack, why must the inner capacity loop run downward?

level: seniorimportance: should knowfreq 55%

basics

~20 s

Downward iteration keeps the lower-index cell holding its value from before the current item was offered, so each item is used at most once. Upward iteration reads a cell this item already updated, silently letting one item be counted repeatedly.

open as a page

Change-making DP runs in O(n * amount) — why is that called pseudo-polynomial, and when does it hurt?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Cost scales with the target's numeric value, while the input writes that target in about log(amount) digits — so the table is exponential in input size. It bites when amounts are large: fine-grained currency units, unvalidated budgets.

open as a page

showing 1–30 of 37