You are given a m x n grid filled with non-negative numbers, find a path from top left to bottom right, which minimizes the sum of all numbers along its path.
Note: You can only move either down or right at any point in time.
Example 1:
Input: grid = [
[1,2,0],
[5,4,2],
[1,1,3]
]
Output: 8Explanation: The path with minimum sum is 1 -> 2 -> 0 -> 2 -> 3.
Example 2:
Input: grid = [
[2,2],
[1,0]
]
Output: 3Constraints:
m == grid.lengthn == grid[i].length1 <= m, n <= 2000 <= grid[i][j] <= 200From any cell, we can only move right or down. To find the minimum path sum to the bottom-right corner, we consider both choices and take the minimum. At the destination cell, we simply return its value. This naturally leads to a recursive solution where we explore all possible paths by branching at each cell.
dfs(r, c) that returns the minimum path sum from cell (r, c) to the bottom-right.grid[r][c].grid[r][c] + min(dfs(r+1, c), dfs(r, c+1)).dfs(0, 0).class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
def dfs(r, c):
if r == len(grid) - 1 and c == len(grid[0]) - 1:
return grid[r][c]
if r == len(grid) or c == len(grid[0]):
return float('inf')
return grid[r][c] + min(dfs(r + 1, c), dfs(r, c + 1))
return dfs(0, 0)Where is the number of rows and is the number of columns.
The plain recursive solution recalculates the same cells many times. For instance, both dfs(0,1) and dfs(1,0) might call dfs(1,1). By caching (memoizing) results for each cell, we ensure each subproblem is solved only once. This transforms exponential time into polynomial time.
dp initialized to -1 (indicating uncomputed).dfs(r, c) as before, but first check if dp[r][c] has been computed.dp[r][c] != -1, return the cached value.dp[r][c] = grid[r][c] + min(dfs(r+1, c), dfs(r, c+1)) and return it.dfs(0, 0).class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
dp = [[-1] * n for _ in range(m)]
def dfs(r, c):
if r == m - 1 and c == n - 1:
return grid[r][c]
if r == m or c == n:
return float('inf')
if dp[r][c] != -1:
return dp[r][c]
dp[r][c] = grid[r][c] + min(dfs(r + 1, c), dfs(r, c + 1))
return dp[r][c]
return dfs(0, 0)Where is the number of rows and is the number of columns.
Instead of recursing from top-left to bottom-right, we can fill a DP table starting from the bottom-right corner. For each cell, we know the minimum path sum to reach the destination from the cell below and from the cell to the right. We take the minimum of these two and add the current cell value. This iterative approach avoids recursion overhead.
(ROWS+1) x (COLS+1) initialized to infinity. The extra row and column handle boundary conditions.dp[ROWS-1][COLS] = 0 as the base case (one position past the destination, allowing the destination cell to be computed correctly).(r, c), set dp[r][c] = grid[r][c] + min(dp[r+1][c], dp[r][c+1]).dp[0][0].class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
ROWS, COLS = len(grid), len(grid[0])
dp = [[float("inf")] * (COLS + 1) for _ in range(ROWS + 1)]
dp[ROWS - 1][COLS] = 0
for r in range(ROWS - 1, -1, -1):
for c in range(COLS - 1, -1, -1):
dp[r][c] = grid[r][c] + min(dp[r + 1][c], dp[r][c + 1])
return dp[0][0]Where is the number of rows and is the number of columns.
When filling the DP table row by row (from bottom to top), we only need the current row and the row below. In fact, since we process columns right to left, we can overwrite the same 1D array. The value at dp[c] represents the minimum path sum from (r+1, c), and dp[c+1] represents the path from (r, c+1). After updating, dp[c] will hold the result for (r, c).
dp of size COLS+1 initialized to infinity.dp[COLS-1] = 0 as the base case.dp[c] = grid[r][c] + min(dp[c], dp[c+1]).dp[0].class Solution:
def minPathSum(self, grid: List[List[int]]) -> int:
ROWS, COLS = len(grid), len(grid[0])
dp = [float("inf")] * (COLS + 1)
dp[COLS - 1] = 0
for r in range(ROWS - 1, -1, -1):
for c in range(COLS - 1, -1, -1):
dp[c] = grid[r][c] + min(dp[c], dp[c + 1])
return dp[0]Where is the number of rows and is the number of columns.