We have a grid of non-negative integers with m rows and n columns. Starting from the top-left cell (0, 0), we need to reach the bottom-right cell (m - 1, n - 1). At each step, we can only move right or down. Among all possible paths from start to destination, we want the one whose cell values add up to the smallest total.
This is closely related to the "Unique Paths" problem, but instead of counting paths, we are optimizing over them. The restricted movement (only right or down) means there are no cycles, which gives the acyclic subproblem structure that dynamic programming relies on. Every path makes exactly (m - 1) down moves and (n - 1) right moves, so every path visits the same number of cells: m + n - 1. The question is which combination of cells gives us the smallest sum.
The minimum-cost path to any cell (i, j) must arrive from either (i - 1, j) or (i, j - 1), whichever offers the cheaper route. This optimal substructure property is what makes dynamic programming applicable.
1 <= m, n <= 200 → The grid has at most 200 x 200 = 40,000 cells. An O(m * n) solution runs in well under a millisecond. Anything exponential is out.0 <= grid[i][j] <= 200 → All values are non-negative. This means we never benefit from taking a longer path. The minimum sum fits easily in an integer: worst case is 200 * (200 + 200 - 1) = 79,800.We can define the answer recursively. The minimum path sum to cell (i, j) is grid[i][j] plus the cheaper of the two ways to reach it: the best path to (i - 1, j) or the best path to (i, j - 1).
The base case is the top-left cell (0, 0), where the cost is grid[0][0]. Going out of bounds (negative row or column) returns infinity, since that is not a valid path.
This is correct but slow. The recursion tree branches into two at every cell, and the same subproblems get solved over and over.
minCost(i, j) that returns the minimum path sum from (0, 0) to (i, j).i == 0 and j == 0, return grid[0][0].i < 0 or j < 0, return a very large number (infinity).grid[i][j] + min(minCost(i - 1, j), minCost(i, j - 1)).minCost(m - 1, n - 1).There are only m * n distinct subproblems, but the recursion solves each one many times. The next approach computes the minimum cost for each cell exactly once, building up from the top-left corner.
Instead of recursing backward and recomputing the same cells, we build the answer forward. We create a 2D table dp where dp[i][j] is the minimum path sum from (0, 0) to (i, j).
Since the only ways to reach (i, j) are from above or from the left, the recurrence is:
dp[i][j] = grid[i][j] + min(dp[i - 1][j], dp[i][j - 1])
The first row and first column are the base cases. A cell in the first row can only be reached by moving right from the start, so dp[0][j] is the prefix sum of the first row. A cell in the first column can only be reached by moving down, so dp[i][0] is the prefix sum of the first column.
Every path to (i, j) passes through either (i - 1, j) or (i, j - 1) as its second-to-last cell. The cheapest path to (i, j) therefore extends the cheapest path to one of those two predecessors. A more expensive path to a predecessor can never produce a cheaper total, because the suffix grid[i][j] added to both is identical and all grid values are non-negative.
dp of size m x n.dp[0][0] = grid[0][0].dp[0][j] = dp[0][j - 1] + grid[0][j].dp[i][0] = dp[i - 1][0] + grid[i][0].(i, j): set dp[i][j] = grid[i][j] + min(dp[i - 1][j], dp[i][j - 1]).dp[m - 1][n - 1].The time complexity is optimal, but the DP table uses O(m * n) extra space. Filling row i only reads row i - 1 and the cell to the left in the current row, so the next approach replaces the full table with a single 1D array updated row by row.
Since each row of the DP table only depends on the row directly above it, we can compress the 2D table into a single 1D array of length n. When we process columns left to right, dp[j] still holds the value from the previous row (the "from above" value) because we have not updated it yet, and dp[j - 1] holds the current row's value (the "from left" value) because we just updated it.
The update becomes: dp[j] = grid[i][j] + min(dp[j], dp[j - 1])
Processing columns left to right keeps both needed values valid at the moment of the update. When we reach column j, dp[j - 1] has already been updated for the current row, so it holds the "left" value, and dp[j] has not been touched yet, so it still holds the previous row's value, which is the "above" value. After the assignment, dp[j] holds the current row's value, ready to serve as the "above" value for the next row.
dp of size n.dp with the prefix sums of the first row.i from 1 to m - 1:dp[0] = dp[0] + grid[i][0] (first column, can only come from above).j from 1 to n - 1: update dp[j] = grid[i][j] + min(dp[j], dp[j - 1]).dp[n - 1].