Minimum Falling Path Sum
Find the minimum sum path falling from the top row to the bottom row, moving straight down or one column diagonally each step.
- n == matrix.length == matrix[i].length
- 1 <= n <= 100
- -100 <= matrix[i][j] <= 100
Intuition
Minimum falling path sum finds the cheapest path from the top row to the bottom, where each step moves down to the cell directly below or diagonally adjacent.
A greedy that always takes the cheapest next cell fails, because a cheap cell can lead into an expensive region. Every path must be considered, but the paths overlap heavily — many share the same cells — and that overlap is what a DP exploits.
The state is direct:
- dp[i][j] is the minimum sum of any falling path ending at cell (i, j), built from the three cells that can reach it.
So dp[i][j] = matrix[i][j] + min(dp[i-1][j-1], dp[i-1][j], dp[i-1][j+1]), considering only the neighbours that exist.
The edge columns are where implementations break. Column 0 has no upper-left neighbour and the last column has no upper-right, so those terms must be excluded rather than read. Reading them either throws an index error or, worse in languages that wrap negative indices, silently reads from the opposite side of the row and produces a wrong answer that looks plausible.
The first row needs no computation — a path ending there is just the cell's own value.
The answer is the minimum across the entire bottom row, since a path may end at any column. Returning dp[n-1][0] or the last cell is a common slip.
Only the previous row is ever read, so the table collapses to a single row and O(n) space. The matrix can also be modified in place for O(1) extra space when mutating the input is acceptable.
Row-by-row DP where each cell draws from the three cells diagonally above and directly above. Only the previous row matters, so one array rolls forward. The answer is the minimum over the final row, not a fixed corner.
Approach
Before reading on: price up what the direct approach costs here, then ask what a single cell should mean, and which earlier cells it needs. Aim for O(n^2) time and O(n) space.
Reject the greedy
Choosing the cheapest next cell fails, since a cheap cell can lead into an expensive region. Paths overlap heavily, which is what makes a DP efficient.
Define the state
dp[i][j] is the minimum falling-path sum ending at cell (i, j). Each cell is reachable from at most three cells in the row above.
Seed the first row
A path ending in row 0 is just that cell's value, so the first row copies the matrix directly with no computation.
Take the minimum of three above
dp[i][j] = matrix[i][j] + min of the up-left, up, and up-right entries. Only these three cells can reach (i, j).
Exclude missing edge neighbours
Column 0 has no up-left and the last column no up-right. Reading them throws, or worse, wraps to the opposite side of the row and returns a plausible wrong answer.
Scan the whole bottom row
A path may end at any column, so the answer is the minimum across the final row — not dp[n-1][0] or the last cell.
Cost of the tabulation
Every cell is computed once with O(1) work, giving O(n²) time and O(n) space with a rolling row, or O(1) if the matrix may be modified in place.
Solution & live demo
Common pitfalls
Overwriting the row in place
for j in range(n):
dp[j] = matrix[i][j] + min(dp[j-1], dp[j], dp[j+1])new_dp = [0] * n ... dp = new_dp
Writing dp[j] destroys the value that dp[j+1]'s computation still needs — the neighbour to its left. Either use a fresh array or save the overwritten value before moving on.
Returning dp[0]
return dp[0]
return min(dp)
A falling path may end at any column of the last row. Reading a single position reports one particular path rather than the cheapest available.
Not guarding the column edges
min(dp[j-1], dp[j], dp[j+1])
if j - 1 >= 0: candidates.append(dp[j - 1]) if j + 1 < n: candidates.append(dp[j + 1])
Column 0 has no upper-left neighbour, and in Python dp[-1] silently wraps to the far end of the row — producing a legal-looking sum from a path that doesn't exist.
Edge cases
Only dp[0] and dp[1] are valid parents; dp[-1] is never included.
Only dp[n-2] and dp[n-1] are valid parents; dp[n] is never included.
The loop over rows never runs; the answer is the single cell's value.
min() still correctly finds the most negative total sum with no special handling needed.