中文题目
给定一个包含非负整数的 m x n
网格 grid
,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:一个机器人每次只能向下或者向右移动一步。
示例 1:
输入:grid = [[1,3,1],[1,5,1],[4,2,1]] 输出:7 解释:因为路径 1→3→1→1→1 的总和最小。
示例 2:
输入:grid = [[1,2,3],[4,5,6]] 输出:12
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 200
0 <= grid[i][j] <= 100
注意:本题与主站 64 题相同: https://leetcode-cn.com/problems/minimum-path-sum/
通过代码
高赞题解
这是一道动态规划算法题,对于动态规划如何入门的话,请看:动态规划入门题解
一般动态规划的问题都可以通过穷举的方式得到答案,既然可以穷举,我们就可以将这个问题抽象成树形结构问题,然后通过回溯来解决
对于什么是回溯算法的话,请参考:回溯算法入门题解
所以,我们先从回溯解法看起,然后一步一步的优化,从而得到最终最优的动态规划解法
1. 回溯解法
代码如下:
private int minPathSum = Integer.MAX_VALUE;
private int[][] dirs = {{1, 0}, {0, 1}};
public int minPathSum(int[][] grid) {
dfs(grid, 0, 0, grid[0][0]);
return minPathSum;
}
private void dfs(int[][] grid, int row, int col, int sum) {
if (row == grid.length - 1 && col == grid[0].length - 1) {
minPathSum = Math.min(minPathSum, sum);
return;
}
for (int[] dir : dirs) {
int nextRow = row + dir[0];
int nextCol = col + dir[1];
if (nextRow < 0 || nextCol < 0
|| nextRow >= grid.length
|| nextCol >= grid[0].length) continue;
sum += grid[nextRow][nextCol];
dfs(grid, nextRow, nextCol, sum);
sum -= grid[nextRow][nextCol];
}
}
2. 记忆化搜索
代码如下:
private int[][] dirs = {{1, 0}, {0, 1}};
public int minPathSum(int[][] grid) {
int[][] memo = new int[grid.length][grid[0].length];
for (int i = 0; i < grid.length; i++) {
Arrays.fill(memo[i], Integer.MAX_VALUE);
}
return dfs(grid, 0, 0, memo);
}
private int dfs(int[][] grid, int row, int col, int[][] memo) {
if (row == grid.length - 1 && col == grid[0].length - 1) {
return grid[row][col];
}
if (memo[row][col] != Integer.MAX_VALUE) return memo[row][col];
int minPathSum = Integer.MAX_VALUE;
for (int[] dir : dirs) {
int nextRow = row + dir[0];
int nextCol = col + dir[1];
if (nextRow < 0 || nextCol < 0
|| nextRow >= grid.length
|| nextCol >= grid[0].length) continue;
int childMinPathSum = dfs(grid, nextRow, nextCol, memo);
minPathSum = Math.min(minPathSum, childMinPathSum);
}
memo[row][col] = minPathSum + grid[row][col];
return memo[row][col];
}
class Solution {
private:
vector<vector<int>> dirs = {{1, 0}, {0, 1}};
public:
// 3. 记忆化搜索
int minPathSum1(vector<vector<int>>& grid) {
vector<vector<int>> memo(grid.size(), vector<int>(grid[0].size(), INT_MAX));
return dfs(grid, 0, 0, memo);
}
int dfs(vector<vector<int>>& grid, int row, int col, vector<vector<int>>& memo) {
if (row == grid.size() - 1 && col == grid[0].size() - 1) {
return grid[row][col];
}
if (memo[row][col] != INT_MAX) return memo[row][col];
int minPathSum = INT_MAX;
for (vector<int> dir : dirs) {
int nextRow = row + dir[0];
int nextCol = col + dir[1];
if (nextRow < 0 || nextCol < 0
|| nextRow >= grid.size()
|| nextCol >= grid[0].size()) continue;
int childMinPathSum = dfs(grid, nextRow, nextCol, memo);
minPathSum = min(minPathSum, childMinPathSum);
}
memo[row][col] = minPathSum + grid[row][col];
return memo[row][col];
}
}
const dirs = [[1, 0], [0, 1]]
// 3. 记忆化搜索
var minPathSum1 = function(grid) {
const MAX_INT = Math.pow(2, 31) - 1
const m = grid.length, n = grid[0].length
const memo = new Array(m).fill(0).map(() => new Array(n).fill(MAX_INT))
const dfs = (row, col) => {
if (row == m - 1 && col == n - 1) return grid[row][col]
if (memo[row][col] != MAX_INT) return memo[row][col]
let minPathSum = MAX_INT
for (const dir of dirs) {
const nextRow = row + dir[0]
const nextCol = col + dir[1]
if (nextRow < 0 || nextRow >= m || nextCol < 0 || nextCol >= n) continue
const childMinPathSum = dfs(nextRow, nextCol)
minPathSum = Math.min(minPathSum, childMinPathSum)
}
memo[row][col] = minPathSum + grid[row][col]
return memo[row][col]
}
return dfs(0, 0)
};
class Solution:
def __init__(self):
self.dirs = [[1, 0], [0, 1]]
# 3. 记忆化搜索
def minPathSum1(self, grid: List[List[int]]) -> int:
MAX_INT = 2**31 - 1
m, n = len(grid), len(grid[0])
memo = [[MAX_INT] * n for _ in range(m)]
def dfs(row, col) -> int:
if row == m - 1 and col == n - 1:
return grid[row][col]
if memo[row][col] != MAX_INT:
return memo[row][col]
min_path_sum = MAX_INT
for dir in self.dirs:
next_row, next_col = row + dir[0], col + dir[1]
if next_row < 0 or next_row >= m or next_col < 0 or next_col >= n:
continue
child_min_path_sum = dfs(next_row, next_col)
min_path_sum = min(min_path_sum, child_min_path_sum)
memo[row][col] = min_path_sum + grid[row][col]
return memo[row][col]
return dfs(0, 0)
3. 动态规划 - 从终点到起始点
代码如下:
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
// 状态定义:dp[i][j] 表示从坐标 (i, j) 到右下角的最小路径和
int[][] dp = new int[m][n];
// 状态初始化
dp[m - 1][n - 1] = grid[m - 1][n - 1];
// 状态转移
for (int i = m - 1; i >= 0 ; i--) {
for (int j = n - 1; j >= 0 ; j--) {
if (i == m - 1 && j != n - 1) { // 最后一行
dp[i][j] = grid[i][j] + dp[i][j + 1];
} else if (i != m - 1 && j == n - 1) {
dp[i][j] = grid[i][j] + dp[i + 1][j];
} else if (i != m - 1 && j != n - 1) {
dp[i][j] = grid[i][j] + Math.min(dp[i + 1][j], dp[i][j + 1]);
}
}
}
// 返回结果
return dp[0][0];
}
public:
// 4. 动态规划:从终点到起始点
int minPathSum2(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
// 状态定义:dp[i][j] 表示从坐标 (i, j) 到右下角的最小路径和
vector<vector<int>> dp(m, vector<int>(n));
// 状态初始化
dp[m - 1][n - 1] = grid[m - 1][n - 1];
// 状态转移
for (int i = m - 1; i >= 0; i--) {
for (int j = n - 1; j >= 0; j--) {
if (i == m - 1 && j != n - 1) { // 最后一行
dp[i][j] = grid[i][j] + dp[i][j + 1];
} else if (i != m - 1 && j == n - 1) { // 最后一列
dp[i][j] = grid[i][j] + dp[i + 1][j];
} else if (i != m - 1 && j != n - 1) {
dp[i][j] = grid[i][j] + min(dp[i + 1][j], dp[i][j + 1]);
}
}
}
// 返回结果
return dp[0][0];
}
}
// 4. 动态规划:从终点到起始点
var minPathSum2 = function(grid) {
const m = grid.length, n = grid[0].length
// 状态定义:dp[i][j] 表示从坐标 (i, j) 到右下角的最小路径和
const dp = new Array(m).fill(0).map(() => new Array(n).fill(0))
// 状态初始化
dp[m - 1][n - 1] = grid[m - 1][n - 1]
// 状态转移
for (let i = m - 1; i >= 0 ; i--) {
for (let j = n - 1; j >= 0 ; j--) {
if (i == m - 1 && j != n - 1) { // 最后一行
dp[i][j] = grid[i][j] + dp[i][j + 1]
} else if (i != m - 1 && j == n - 1) { // 最后一列
dp[i][j] = grid[i][j] + dp[i + 1][j]
} else if (i != m - 1 && j != n - 1) {
dp[i][j] = grid[i][j] + Math.min(dp[i + 1][j], dp[i][j + 1])
}
}
}
// 返回结果
return dp[0][0]
}
class Solution:
# 4. 动态规划:从终点到起始点
def minPathSum2(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
# 状态定义:dp[i][j] 表示从坐标 (i, j) 到右下角的最小路径和
dp = [[0] * n for _ in range(m)]
# 状态初始化
dp[m - 1][n - 1] = grid[m - 1][n - 1]
# 状态转移
for i in range(m - 1, -1, -1):
for j in range(n - 1, -1, -1):
if i == m - 1 and j != n - 1:
dp[i][j] = grid[i][j] + dp[i][j + 1]
elif i != m - 1 and j == n - 1:
dp[i][j] = grid[i][j] + dp[i + 1][j]
elif i != m - 1 and j != n - 1:
dp[i][j] = grid[i][j] + min(dp[i + 1][j], dp[i][j + 1])
# 返回结果
return dp[0][0]
4. 动态规划 - 从起始点到终点
代码如下:
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
// 状态定义:dp[i][j] 表示从 [0,0] 到 [i,j] 的最小路径和
int[][] dp = new int[m][n];
// 状态初始化
dp[0][0] = grid[0][0];
// 状态转移
for (int i = 0; i < m ; i++) {
for (int j = 0; j < n ; j++) {
if (i == 0 && j != 0) {
dp[i][j] = grid[i][j] + dp[i][j - 1];
} else if (i != 0 && j == 0) {
dp[i][j] = grid[i][j] + dp[i - 1][j];
} else if (i != 0 && j != 0) {
dp[i][j] = grid[i][j] + Math.min(dp[i - 1][j], dp[i][j - 1]);
}
}
}
// 返回结果
return dp[m - 1][n - 1];
}
public:
// 5. 动态规划:从起始点到终点
int minPathSum3(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
// 状态定义:dp[i][j] 表示从 [0,0] 到 [i,j] 的最小路径和
vector<vector<int>> dp(m, vector<int>(n));
// 状态初始化
dp[0][0] = grid[0][0];
// 状态转移
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (i == 0 && j != 0) { //第一行
dp[i][j] = grid[i][j] + dp[i][j - 1];
} else if (i != 0 && j == 0) { // 第一列
dp[i][j] = grid[i][j] + dp[i - 1][j];
} else if (i != 0 && j != 0) {
dp[i][j] = grid[i][j] + min(dp[i - 1][j], dp[i][j - 1]);
}
}
}
// 返回结果
return dp[m - 1][n - 1];
}
}
// 5. 动态规划:从起始点到终点
var minPathSum3 = function(grid) {
const m = grid.length, n = grid[0].length
// 状态定义:dp[i][j] 表示从 [0,0] 到 [i,j] 的最小路径和
const dp = new Array(m).fill(0).map(() => new Array(n).fill(0))
// 状态初始化
dp[0][0] = grid[0][0]
// 状态转移
for (let i = 0; i < m ; i++) {
for (let j = 0; j < n ; j++) {
if (i == 0 && j != 0) {
dp[i][j] = grid[i][j] + dp[i][j - 1]
} else if (i != 0 && j == 0) {
dp[i][j] = grid[i][j] + dp[i - 1][j]
} else if (i != 0 && j != 0) {
dp[i][j] = grid[i][j] + Math.min(dp[i - 1][j], dp[i][j - 1])
}
}
}
// 返回结果
return dp[m - 1][n - 1]
}
# 5. 动态规划:从起始点到终点
def minPathSum3(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
# 状态定义:dp[i][j] 表示从 [0,0] 到 [i,j] 的最小路径和
dp = [[0] * n for _ in range(m)]
# 状态初始化
dp[0][0] = grid[0][0]
# 状态转移
for i in range(m):
for j in range(n):
if i == 0 and j != 0:
dp[i][j] = grid[i][j] + dp[i][j - 1]
elif i != 0 and j == 0:
dp[i][j] = grid[i][j] + dp[i - 1][j]
elif i != 0 and j != 0:
dp[i][j] = grid[i][j] + min(dp[i - 1][j], dp[i][j - 1])
# 返回结果
return dp[m - 1][n - 1]
5. 空间状态压缩和优化
状态空间压缩代码:
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
// 状态定义:dp[i] 表示从 (0, 0) 到达第 i - 1 行的最短路径值
int[] dp = new int[n];
// 状态初始化
dp[0] = grid[0][0];
// 状态转移
for (int i = 0; i < m ; i++) {
for (int j = 0; j < n ; j++) {
if (i == 0 && j != 0) {
dp[j] = grid[i][j] + dp[j - 1];
} else if (i != 0 && j == 0) {
dp[j] = grid[i][j] + dp[j];
} else if (i != 0 && j != 0) {
dp[j] = grid[i][j] + Math.min(dp[j], dp[j - 1]);
}
}
}
// 返回结果
return dp[n - 1];
}
// 6. 动态规划:从起始点到终点 + 状态压缩
public:
int minPathSum4(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
// 状态定义:dp[i] 表示从 (0, 0) 到达第 i - 1 行的最短路径值
vector<int> dp(n);
// 状态初始化
dp[0] = grid[0][0];
// 状态转移
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (i == 0 && j != 0) { //第一行
dp[j] = grid[i][j] + dp[j - 1];
} else if (i != 0 && j == 0) { // 第一列
dp[j] = grid[i][j] + dp[j];
} else if (i != 0 && j != 0) {
dp[j] = grid[i][j] + min(dp[j], dp[j - 1]);
}
}
}
// 返回结果
return dp[n - 1];
}
// 6. 动态规划:从起始点到终点 + 状态压缩
var minPathSum4 = function(grid) {
const m = grid.length, n = grid[0].length
// 状态定义:dp[i] 表示从 (0, 0) 到达第 i - 1 行的最短路径值
const dp = new Array(n).fill(0)
// 状态初始化
dp[0] = grid[0][0]
// 状态转移
for (let i = 0; i < m ; i++) {
for (let j = 0; j < n ; j++) {
if (i == 0 && j != 0) {
dp[j] = grid[i][j] + dp[j - 1]
} else if (i != 0 && j == 0) {
dp[j] = grid[i][j] + dp[j]
} else if (i != 0 && j != 0) {
dp[j] = grid[i][j] + Math.min(dp[j], dp[j - 1])
}
}
}
// 返回结果
return dp[n - 1]
}
# 6. 动态规划:从起始点到终点 + 状态压缩
def minPathSum4(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
# 状态定义:dp[i] 表示从 (0, 0) 到达第 i - 1 行的最短路径值
dp = [0] * n
# 状态初始化
dp[0] = grid[0][0]
# 状态转移
for i in range(m):
for j in range(n):
if i == 0 and j != 0:
dp[j] = grid[i][j] + dp[j - 1]
elif i != 0 and j == 0:
dp[j] = grid[i][j] + dp[j]
elif i != 0 and j != 0:
dp[j] = grid[i][j] + min(dp[j], dp[j - 1])
# 返回结果
return dp[n - 1]
空间优化代码如下:
// 动态规划:从起始点到终点 + 使用输入数组作为状态数组
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
for (int i = 0; i < m ; i++) {
for (int j = 0; j < n ; j++) {
if (i == 0 && j != 0) {
grid[i][j] = grid[i][j] + grid[i][j - 1];
} else if (i != 0 && j == 0) {
grid[i][j] = grid[i][j] + grid[i - 1][j];
} else if (i != 0 && j != 0) {
grid[i][j] = grid[i][j] + Math.min(grid[i - 1][j], grid[i][j - 1]);
}
}
}
return grid[m - 1][n - 1];
}
public:
// 7. 动态规划:从起始点到终点 + 使用输入数组作为状态数组
int minPathSum(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size();
// 状态转移
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (i == 0 && j != 0) { //第一行
grid[i][j] = grid[i][j] + grid[i][j - 1];
} else if (i != 0 && j == 0) { // 第一列
grid[i][j] = grid[i][j] + grid[i - 1][j];
} else if (i != 0 && j != 0) {
grid[i][j] = grid[i][j] + min(grid[i - 1][j], grid[i][j - 1]);
}
}
}
// 返回结果
return grid[m - 1][n - 1];
}
// 7. 动态规划:从起始点到终点 + 使用输入数组作为状态数组
var minPathSum = function(grid) {
const m = grid.length, n = grid[0].length
// 状态转移
for (let i = 0; i < m ; i++) {
for (let j = 0; j < n ; j++) {
if (i == 0 && j != 0) {
grid[i][j] = grid[i][j] + grid[i][j - 1]
} else if (i != 0 && j == 0) {
grid[i][j] = grid[i][j] + grid[i - 1][j]
} else if (i != 0 && j != 0) {
grid[i][j] = grid[i][j] + Math.min(grid[i - 1][j], grid[i][j - 1])
}
}
}
// 返回结果
return grid[m - 1][n - 1]
}
# 7. 动态规划:从起始点到终点 + 使用输入数组作为状态数组
def minPathSum(self, grid: List[List[int]]) -> int:
m, n = len(grid), len(grid[0])
# 状态转移
for i in range(m):
for j in range(n):
if i == 0 and j != 0:
grid[i][j] = grid[i][j] + grid[i][j - 1]
elif i != 0 and j == 0:
grid[i][j] = grid[i][j] + grid[i - 1][j]
elif i != 0 and j != 0:
grid[i][j] = grid[i][j] + min(grid[i - 1][j], grid[i][j - 1])
# 返回结果
return grid[m - 1][n - 1]
在刷题的时候:
如果你觉得自己数据结构与算法基础不够扎实,那么请点这里,这里包含了一个程序员 5 年内需要的所有算法知识
如果你感觉刷题太慢,或者感觉很困难,或者赶时间,那么请点这里。这里用 365 道高频算法题,带你融会贯通算法知识,做到以不变应万变
回溯、贪心和动态规划,是算法面试中的三大难点内容,如果你只是想搞懂这三大难点内容 请点这里
以上三个链接中的内容,都支持 Java/C++/Python/js 四种语言
统计信息
通过次数 | 提交次数 | AC比率 |
---|---|---|
3914 | 5515 | 71.0% |
提交历史
提交时间 | 提交结果 | 执行时间 | 内存消耗 | 语言 |
---|