64. Minimum Path Sum
Problem Description
The problem presents a m x n
grid full of non-negative integers. The objective is to find a path from the top-left corner of the grid to the bottom-right corner which has the minimum sum of all numbers along the path. The rules specify that you can move only to the right or down at any given step. The aim is to compute this minimum sum.
Imagine standing at the top-left corner of the grid and needing to reach the bottom-right corner. With only two possible moves (right and down), you must choose the path with lower numbers to minimize the sum. However, the challenge lies in making choices that might not result in the minimum sum at the current step but will lead to an overall minimum path sum.
Intuition
When faced with a grid-based pathfinding problem that asks for a minimum or maximum sum, dynamic programming is a common approach. The idea is to break the problem down into smaller problems, solve those smaller problems, and use their solutions to build up an answer to the bigger problem.
For this particular problem, we use a 2-dimensional array f
to store the minimum path sum to each cell. To fill this array, we start with the top-left corner, which is the starting point and has only one possible sum – the value of the cell itself.
Then, for the first row and column, since there's only one way to reach any of these cells (moving right for the first row and down for the first column), each cell in these positions can only have a minimum sum equal to the sum of the current cell value and the minimum sum of the previous cell.
For the rest of the grid, each cell can be reached either from the cell above it or the cell to its left. We take the minimum of the two possible cells' minimum sums and add it to the current cell's value. This ensures that for every cell, we have computed the minimum sum required to reach it.
When we reach the bottom-right corner of the grid, the f
array will contain the minimum path sum to that cell, which will be our answer.
Learn more about Dynamic Programming patterns.
Solution Approach
The solution utilizes dynamic programming, which is a method for solving complex problems by breaking them down into simpler subproblems. It's particularly apt for problems where the same subproblems are solved multiple times, allowing us to store those solutions and reuse them, effectively cutting down on the computation time.
Here’s how the implementation unfolds:
- Initialize a 2D array
f
to hold the minimum sums with the same dimensions asgrid
. - Set
f[0][0]
togrid[0][0]
, as this is the starting point and there's only one path to this cell, which is the cell itself. - Fill out the first row and first column of
f
by accumulating sums, since there's only one way to reach any cell in the first row and column.- For the first column, do
f[i][0] = f[i - 1][0] + grid[i][0]
for each rowi
. - For the first row, do
f[0][j] = f[0][j - 1] + grid[0][j]
for each columnj
.
- For the first column, do
- Loop through the rest of the
grid
, starting fromf[1][1]
, and at each cellf[i][j]
, calculate the minimum sum by comparing the sum through the cell abovef[i - 1][j]
and the cell to the leftf[i][j - 1]
. The minimum sum up to the current cell is the minimum of the two possible sums plus the current cell's value:f[i][j] = min(f[i - 1][j], f[i][j - 1]) + grid[i][j]
. - After the loops complete, the bottom-right cell
f[-1][-1]
which corresponds tof[m - 1][n - 1]
, will contain the minimum path sum from top left to bottom right.
Let's break down why this works:
- At any cell, the decision to choose the cell above or the cell to the left is based on which of those two cells provides a lesser sum.
- Since those sums represent the minimum sum to get to those cells, we ensure the minimum sum to get to the current cell by adding the current cell's value to the lesser of those two sums.
- This builds up to the final solution by ensuring at each step, we have the optimal (minimal) path chosen.
Ultimately, by using a dynamic programming table (f
), we efficiently calculate the required sums without re-computing sums for each cell multiple times. This is not only a powerful strategy for minimizing computation but is also fundamental in demonstrating the concept of optimal substructure, which is characteristic of a dynamic programming approach: optimal solutions to a problem incorporate optimal solutions to its subproblems.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's consider a small 3 x 3
grid to illustrate the solution approach:
[ [1, 3, 1], [1, 5, 1], [4, 2, 1] ]
Following the steps in the solution approach:
- We initialize a 2D array
f
with the same dimensions asgrid
, which is3 x 3
in this case.
f = [ [0, 0, 0], [0, 0, 0], [0, 0, 0] ]
- Set
f[0][0]
togrid[0][0]
. Sof[0][0]
will be1
.
f = [ [1, 0, 0], [0, 0, 0], [0, 0, 0] ]
-
Now, fill out the first row and first column of
f
:For the first column:
f[1][0] = f[0][0] + grid[1][0]
equals1 + 1 = 2
f[2][0] = f[1][0] + grid[2][0]
equals2 + 4 = 6
For the first row:
f[0][1] = f[0][0] + grid[0][1]
equals1 + 3 = 4
f[0][2] = f[0][1] + grid[0][2]
equals4 + 1 = 5
The f
array now looks like this:
f = [ [1, 4, 5], [2, 0, 0], [6, 0, 0] ]
-
We then proceed to fill the rest:
-
For
f[1][1]
(middle of the grid), the minimum sum ismin(f[0][1], f[1][0]) + grid[1][1]
which ismin(4, 2) + 5 = 7
. -
For
f[1][2]
, takemin(f[0][2], f[1][1]) + grid[1][2]
which ismin(5, 7) + 1 = 6
. -
For
f[2][1]
, takemin(f[1][1], f[2][0]) + grid[2][1]
which ismin(7, 6) + 2 = 8
. -
Finally, for
f[2][2]
, takemin(f[1][2], f[2][1]) + grid[2][2]
which ismin(6, 8) + 1 = 7
.
-
The final f
array is:
f = [ [1, 4, 5], [2, 7, 6], [6, 8, 7] ]
- The bottom-right cell
f[-1][-1]
orf[2][2]
contains7
, which is the minimum path sum to reach from the top-left corner to the bottom-right corner. Our path would look like1 → 1 → 3 → 1 → 1
.
By following these steps and reasoning, we establish the power of dynamic programming to compute the minimum sum in an efficient way without repeated calculations for each cell. The approach demonstrates optimal substructure as it builds the solution for the entire grid from the solutions of its sub-grids.
Solution Implementation
1class Solution:
2 def minPathSum(self, grid: List[List[int]]) -> int:
3 # Get the number of rows (m) and columns (n) in the grid
4 num_rows, num_cols = len(grid), len(grid[0])
5
6 # Initialize a 2D array 'cost' with the same dimensions as grid to store the min path sum
7 cost = [[0] * num_cols for _ in range(num_rows)]
8
9 # The top-left corner has a path sum equal to its value as there's no other path.
10 cost[0][0] = grid[0][0]
11
12 # Populate the first column of the 'cost' array
13 # Since you can only move down, the minimum path sum is just the sum of the current cell value
14 # and the minimum path sum of the cell directly above it
15 for i in range(1, num_rows):
16 cost[i][0] = cost[i - 1][0] + grid[i][0]
17
18 # Populate the first row of the 'cost' array
19 # Since you can only move right, the minimum path sum is the sum of the current cell value
20 # and the minimum path sum of the cell directly to the left of it
21 for j in range(1, num_cols):
22 cost[0][j] = cost[0][j - 1] + grid[0][j]
23
24 # Fill in the rest of the 'cost' array
25 # For each cell, consider the minimum path sum from the cell above and the cell to the left
26 # Add the smaller of those two values to the current cell value to get the min path sum for the current cell
27 for i in range(1, num_rows):
28 for j in range(1, num_cols):
29 cost[i][j] = min(cost[i - 1][j], cost[i][j - 1]) + grid[i][j]
30
31 # The bottom-right corner of the 'cost' array contains the minimum path sum for the entire grid
32 return cost[-1][-1]
33```
34
35The code is written in Python 3, utilizing type hints (`List` from `typing` module), and follows Pythonic naming conventions for variables. Additionally, the internal workings of the algorithm are explained in comments to aid in understanding. Note that the `List` type must be imported from the `typing` module if not already done. You should include this import at the beginning of your code.
36
37```python
38from typing import List
39
1class Solution {
2 // Method to find the minimum path sum in a grid.
3 public int minPathSum(int[][] grid) {
4 // m and n store the dimensions of the grid.
5 int m = grid.length, n = grid[0].length;
6
7 // dp array stores the minimum path sums.
8 int[][] dp = new int[m][n];
9
10 // Initialize top-left cell with its own value as this is the starting point.
11 dp[0][0] = grid[0][0];
12
13 // Fill in the first column (vertical path) by accumulating values.
14 for (int i = 1; i < m; ++i) {
15 dp[i][0] = dp[i - 1][0] + grid[i][0];
16 }
17
18 // Fill in the first row (horizontal path) by accumulating values.
19 for (int j = 1; j < n; ++j) {
20 dp[0][j] = dp[0][j - 1] + grid[0][j];
21 }
22
23 // Fill in the rest of the grid.
24 for (int i = 1; i < m; ++i) {
25 for (int j = 1; j < n; ++j) {
26 // The cell dp[i][j] is the minimum of the cell above or to the left of it,
27 // plus the value in the current cell of the grid.
28 dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
29 }
30 }
31
32 // Return the bottom-right cell which contains the min path sum from top-left to bottom-right.
33 return dp[m - 1][n - 1];
34 }
35}
36
1#include <vector>
2#include <algorithm> // for std::min
3
4using std::vector;
5using std::min;
6
7class Solution {
8public:
9 int minPathSum(vector<vector<int>>& grid) {
10 int rowCount = grid.size(); // number of rows in the grid
11 int colCount = grid[0].size(); // number of columns in the grid
12 vector<vector<int>> dp(rowCount, vector<int>(colCount, 0)); // create a 2D vector for dynamic programming
13
14 // Initialize the value for the first cell
15 dp[0][0] = grid[0][0];
16
17 // Fill the first column of the dp vector
18 for (int row = 1; row < rowCount; ++row) {
19 dp[row][0] = dp[row - 1][0] + grid[row][0];
20 }
21
22 // Fill the first row of the dp vector
23 for (int col = 1; col < colCount; ++col) {
24 dp[0][col] = dp[0][col - 1] + grid[0][col];
25 }
26
27 // Fill the rest of the dp vector
28 for (int row = 1; row < rowCount; ++row) {
29 for (int col = 1; col < colCount; ++col) {
30 // Calculate the minimum path sum for the current cell by adding the current grid value to the minimum of the top and left cells in the dp vector
31 dp[row][col] = min(dp[row - 1][col], dp[row][col - 1]) + grid[row][col];
32 }
33 }
34
35 // Return the minimum path sum for the bottom-right corner of the grid
36 return dp[rowCount - 1][colCount - 1];
37 }
38};
39
1function minPathSum(grid: number[][]): number {
2 // Get the number of rows in the grid
3 const numRows = grid.length;
4 // Get the number of columns in the grid
5 const numCols = grid[0].length;
6 // Initialize a 2D array to store the minimum path sums
7 const minPathSums: number[][] = Array(numRows)
8 .fill(0)
9 .map(() => Array(numCols).fill(0));
10
11 // Set the first cell's minimum path sum to the first cell's value in the grid
12 minPathSums[0][0] = grid[0][0];
13
14 // Calculate minimum path sums for the first column
15 for (let row = 1; row < numRows; ++row) {
16 minPathSums[row][0] = minPathSums[row - 1][0] + grid[row][0];
17 }
18
19 // Calculate minimum path sums for the first row
20 for (let col = 1; col < numCols; ++col) {
21 minPathSums[0][col] = minPathSums[0][col - 1] + grid[0][col];
22 }
23
24 // Calculate minimum path sums for the rest of the cells
25 for (let row = 1; row < numRows; ++row) {
26 for (let col = 1; col < numCols; ++col) {
27 // The min path sum of a cell is the smaller of the min paths to the cell
28 // from the left or above, plus the cell's value
29 minPathSums[row][col] = Math.min(minPathSums[row - 1][col], minPathSums[row][col - 1]) + grid[row][col];
30 }
31 }
32
33 // Return the minimum path sum of the bottom-right corner of the grid
34 return minPathSums[numRows - 1][numCols - 1];
35}
36
Time and Space Complexity
Time Complexity
The time complexity of the code is determined by the nested loops that iterate over the entire grid
matrix. Since we iterate through all rows m
and all columns n
of the grid, the time complexity is O(m * n)
.
Space Complexity
The space complexity of the code is given by the additional 2D array f
that we use to store the minimum path sums. This array is of the same size as the input grid
, so the space complexity is also O(m * n)
. We do not use any other significant space that grows with the size of the input.
Learn more about how to find time and space complexity quickly using problem constraints.
Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https algomonster s3 us east 2 amazonaws com recursion jpg You first
Want a Structured Path to Master System Design Too? Don’t Miss This!