Before attempting this problem, you should be comfortable with:
A falling path starts at any cell in the first row and moves to an adjacent cell in the next row (directly below, diagonally left, or diagonally right). We want to find the path with the minimum sum. For each starting column in the first row, we recursively explore all valid paths and track the minimum total. This brute force approach considers all possible paths, leading to exponential time complexity.
dfs(r, c) that returns the minimum path sum starting from cell (r, c) to the bottom row.r == n, we've gone past the last row, return 0.c is out of bounds, return Infinity (invalid path).matrix[r][c] plus the minimum of dfs(r+1, c-1), dfs(r+1, c), and dfs(r+1, c+1).0 and return the minimum result.class Solution:
def minFallingPathSum(self, matrix: List[List[int]]) -> int:
N = len(matrix)
def dfs(r, c):
if r == N:
return 0
if c < 0 or c >= N:
return float("inf")
return matrix[r][c] + min(
dfs(r + 1, c - 1),
dfs(r + 1, c),
dfs(r + 1, c + 1)
)
res = float("inf")
for c in range(N):
res = min(res, dfs(0, c))
return resThe recursive solution has overlapping subproblems: the same (r, c) state is computed multiple times. By caching results in a memoization table, we avoid redundant calculations. Each unique (r, c) pair is computed only once, reducing time complexity from exponential to polynomial.
cache (dictionary or 2D array) to store computed results.dfs(r, c) that returns the minimum path sum from (r, c) to the bottom.(r, c) is already in the cache; if so, return the cached value.matrix[r][c] plus the minimum of the three possible moves.cache and return it.0.class Solution:
def minFallingPathSum(self, matrix: List[List[int]]) -> int:
N = len(matrix)
cache = {}
def dfs(r, c):
if r == N:
return 0
if c < 0 or c >= N:
return float("inf")
if (r, c) in cache:
return cache[(r, c)]
cache[(r, c)] = matrix[r][c] + min(
dfs(r + 1, c - 1),
dfs(r + 1, c),
dfs(r + 1, c + 1)
)
return cache[(r, c)]
res = float("inf")
for c in range(N):
res = min(res, dfs(0, c))
return resWe can solve this iteratively by building up solutions row by row. For each cell, the minimum path sum to reach it equals its value plus the minimum of the three cells above it that could lead here. By processing rows from top to bottom and only keeping the previous row's values, we achieve O(n) space complexity.
dp array with the first row of the matrix.r:leftUp (the previous row's value to the left) to avoid overwriting issues.c:midUp = dp[c] (directly above).rightUp = dp[c+1] if within bounds, else infinity.dp[c] = matrix[r][c] + min(leftUp, midUp, rightUp).leftUp = midUp for the next iteration.dp array.class Solution:
def minFallingPathSum(self, matrix: List[List[int]]) -> int:
N = len(matrix)
dp = matrix[0][:]
for r in range(1, N):
leftUp = float('inf')
for c in range(N):
midUp = dp[c]
rightUp = dp[c + 1] if c < N - 1 else float('inf')
dp[c] = matrix[r][c] + min(midUp, leftUp, rightUp)
leftUp = midUp
return min(dp)If we are allowed to modify the input matrix, we can avoid using any extra space for DP. We update each cell in place to store the minimum path sum to reach that cell. This is the most space-efficient approach, using only O(1) extra space beyond the input.
1).(r, c):(r-1, c-1), (r-1, c), (r-1, c+1) (treating out-of-bounds as infinity).matrix[r][c].class Solution:
def minFallingPathSum(self, matrix: List[List[int]]) -> int:
N = len(matrix)
for r in range(1, N):
for c in range(N):
mid = matrix[r - 1][c]
left = matrix[r - 1][c - 1] if c > 0 else float("inf")
right = matrix[r - 1][c + 1] if c < N - 1 else float("inf")
matrix[r][c] = matrix[r][c] + min(mid, left, right)
return min(matrix[-1])When computing the minimum from the three cells above (left-diagonal, directly above, right-diagonal), forgetting to check column boundaries causes index errors. The leftmost column has no left-diagonal parent, and the rightmost column has no right-diagonal parent. Always handle these edge cases by treating out-of-bounds values as infinity.
In the in-place DP approach, you update matrix[r][c] using values from the previous row. Since you read from the same row you just wrote to, there is no conflict. However, if you mistakenly read from cells you have already updated in the current row, you will get incorrect results. Process each row independently from the previous row.
The path can start from any column in the first row. A common mistake is starting only from column 0 or assuming the minimum value in the first row is the optimal start. You must either try all starting columns explicitly or let the DP naturally propagate all possibilities.