What do you give up by compressing a min-cost grid route DP to a single rolling row?
answer
- which rows still exist after the sweep
- the cost versus the route itself
- each row overwrites the one before it
- one decision bit per cell
- split at the middle row and recurse
basics
~20 sTime 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.
solid answer
~50 sThe compression is sound: sweeping the row from low index upward, the entry at `j` still holds the value from the row above when you read it, while `j-1` has already been updated to this row's left neighbour — exactly the two predecessors the recurrence needs. Time is unchanged at `O(m*n)`, space falls to `O(min(m, n))` if you orient the sweep along the shorter side. What you lose is history: with the earlier rows gone there is nothing to walk backwards through, so you can report the cheapest *cost* but not the cheapest *route*. If the caller needs the route, either store one decision bit per cell — which is roughly a sixty-fourth of the full table — or split at the middle row, sweeping forward from the start and backward from the destination to find where the optimum crosses, then recurse, which keeps row-sized memory at about twice the time.
code
pseudocode · 9 linesbest[0] = cost[0][0]
for j in 1..n-1:
best[j] = best[j-1] + cost[0][j]
for i in 1..m-1:
best[0] = best[0] + cost[i][0]
for j in 1..n-1:
# best[j] still holds the row above; best[j-1] is already this row
best[j] = min(best[j], best[j-1]) + cost[i][j]
answer = best[n-1]go deeper
Know that the same recurrence can be evaluated with a single row of scratch instead of the whole table, and that this changes memory only — the number of cells visited is the same.
Be ready to state the invariant that makes one row sufficient and to explain why the sweep must run in the direction that keeps the row above readable at the current index.
Demonstrate that you separate the cost from the route, and that you can name concrete recovery options with their memory and time prices rather than assuming backtracking is always available.
Own the sequencing: establish what the caller consumes and the actual memory ceiling before choosing, and weigh the middle-row split's cleverness against the years of maintenance it commits the team to.
## What the compression does The cheapest right-and-down route over a terrain-cost grid obeys `best[i][j] = cost[i][j] + min(best[i-1][j], best[i][j-1])`. Every cell depends on the row above and on its own row's left neighbour, and on nothing older. That is the licence to keep one row instead of the whole table: as the sweep moves left to right, the entry at index `j` still holds the row above's value at the moment it is read, and index `j-1` was already overwritten with this row's value. One read of each, then the overwrite. The sweep direction is load-bearing. Going from high index down to low, `j-1` would still hold the row above, so the recurrence would combine the cell above with the cell above-left — a different, wrong problem. Nothing crashes; the numbers simply come out wrong on grids where those two cells differ, which is most of them. ## What it costs and what it saves | Property | Full table | Rolling row | |---|---|---| | Time | `O(m*n)` | `O(m*n)` — identical | | Working memory | one number per cell | one row of numbers | | Cheapest cost available | yes | yes | | Cheapest route recoverable | yes, by walking back | no | Compression buys memory and nothing else. Claiming it also speeds things up is a common overreach — the same number of cells is visited either way, though the smaller footprint can help a large grid stay in fast memory, which is a constant-factor effect, not an asymptotic one. Orient the sweep along the shorter dimension so the retained row is `min(m, n)` wide. ## Recovering the route when the caller needs it A robot-routing service usually needs the aisle-by-aisle route, not just its price. Three options, in increasing cleverness: **Keep the full table.** Simplest and often correct. One number per cell, then walk back from the destination comparing the two predecessors. Fine until the grid is large enough that the table dominates the process's memory. **Store one decision bit per cell.** During the compressed sweep, record for each cell which predecessor won — above or left. That is a single bit, against a full-width number in the naive table, so the footprint drops by roughly the width of a number in bits while remaining fully sufficient to walk the route back from the destination. It is still `O(m*n)` memory, just with a far smaller constant, and it is the option most production code lands on. **Split at the middle row.** Because every legal route moves down monotonically, it passes through exactly one cell of every row — including the middle one. So sweep forward from the start to the middle row, sweep backward from the destination to the middle row, add the two arrays elementwise, and the smallest entry names the column where the optimal route crosses. That fixes one cell of the answer; recurse on the two halves it splits the grid into. Each level of recursion halves the work, so the total is about twice a single sweep, and the memory never exceeds a row plus the recursion's own bookkeeping. This is the divide-and-conquer idea behind linear-space sequence alignment, applied to a grid, and it is the only option that keeps memory row-sized when the grid is genuinely huge. ## Choosing between them The decision is driven by what the caller consumes and what the memory ceiling is, not by which is most elegant. If the answer is a number on a dashboard, compress and stop. If a route is displayed once per request on grids of a few thousand cells, keep the full table — the code is shorter and the memory is nothing. If routes are computed continuously on very large grids under a fixed per-process ceiling, decision bits first, and the middle-row split only when even a bit per cell is too much. Each step up costs code that a future engineer must understand, and the middle-row version in particular is easy to get subtly wrong in the backward sweep's index convention, so it earns its place only when the memory pressure is real and measured. ## What an interviewer is listening for That you name the invariant justifying the single row, that you know the sweep direction is not free choice, that you separate *time unchanged* from *memory reduced* rather than blurring them, and above all that you ask what the caller actually needs before optimizing. A candidate who compresses to one row and then confidently offers to "just backtrack from the end" has not understood what was thrown away.
- Which dimension should the rolling row run along, and why?Along the shorter one, so the retained row is `min(m, n)` wide — sweep by rows on a wide short grid and by columns on a tall narrow one. Time is identical either way since the same cells are visited; only the retained slice changes size, so a 10-by-100000 grid should keep a slice of 10, not of 100000.
- Why is the in-row sweep direction load-bearing rather than a matter of taste?Sweeping from low index upward, the entry at `j` still holds the row above when read and `j-1` already holds this row's left neighbour — the two predecessors the recurrence wants. Sweeping downward, `j-1` still holds the row above, so you would combine the cell above with the cell above-left. The result is silently wrong, not a crash.
- How much does one decision bit per cell actually save over the full table?A bit per cell against a full-width number per cell, so roughly two orders of magnitude less memory for typical numeric widths, while still being enough to walk the route back from the destination. It remains `O(m*n)` memory, so on grids where even that is too much the middle-row split is the only way to stay row-sized.
- Does the compression change what happens with unreachable cells in the grid?No — reachability is orthogonal to how many rows you keep. Unreachable cells still need a value that no minimum will choose, and doing arithmetic on a huge sentinel is as dangerous compressed as uncompressed. Carrying a separate reachable flag alongside the row keeps that safe.
saying these in an interview costs you the question
- Claims dropping rows also lowers the time cost
- Thinks the route can be recovered from the final row
- Sweeps the row in the direction that reads a stale neighbour
- Keeps the whole table when only the total cost is consumed
- Optimizes memory before asking what the caller needs