题目
- m == grid.length
- n == grid[i].length
- 1 <= m, n <= 200
- 0 <= grid[i][j] <= 100
题解
方法一:递归解法(超时)
right 表示当前向已右走的步数,down 表示当前已向下走的步数,cur 表示当前走过的路径和。
此方法超时,应该使用 (方法二)动态规划解法。
public static int minPathSum1(int[][] grid) {
int right = 0, down = 0;
return getMinSum(0, right, down, grid);
}
public static int getMinSum(int cur, int right, int down, int[][] grid) {
int m = grid.length - 1; // 行边界
int n = grid[0].length - 1; // 列边界
cur += grid[down][right];
if (right == n && down == m) { // 到终点
return cur;
} else if (right == n) { // 右边界
return getMinSum(cur, right, down + 1, grid);
} else if (down == m) { // 下边界
return getMinSum(cur, right + 1, down, grid);
} else {
return Math.min(getMinSum(cur, right, down + 1, grid), getMinSum(cur, right + 1, down, grid));
}
}
方法二:动态规划
由于路径的方向只能是向下或向右,因此网格的第一行的每个元素只能从左上角元素开始向右移动到达,网格的第一列的每个元素只能从左上角元素开始向下移动到达,此时的路径是唯一的,因此每个元素对应的最小路径和即为对应的路径上的数字总和。
对于不在第一行和第一列的元素,可以从其上方相邻元素向下移动一步到达,或者从其左方相邻元素向右移动一步到达,元素对应的最小路径和等于其上方相邻元素与其左方相邻元素两者对应的最小路径和中的最小值加上当前元素的值。由于每个元素对应的最小路径和与其相邻元素对应的最小路径和有关,因此可以使用动态规划求解。
创建二维数组 dp,与原始网格的大小相同,dp[i][j] 表示从左上角出发到 (i,j) 位置的最小路径和。显然,dp[0][0]=grid[0][0]。对于 dp 中的其余元素,通过以下状态转移方程计算元素值。
状态转移方程:
-
当 i>0 且 j=0 时,
dp[i][0] = dp[i-1][0] + grid[i][0]
。 -
当 i=0 且 j>0 时,
dp[0][j] = dp[0][j-1] + grid[0][j]
。 -
当 i>0 且 j>0 时,
dp[i][j] = min(dp[i−1][j],dp[i][j−1]) + grid[i][j]
。
最后得到 dp[m-1][n-1]
的值,即为从网格左上角到网格右下角的最小路径和。
代码
public static int minPathSum2(int[][] grid) {
if (grid == null || grid.length == 0 || grid[0].length == 0) {
return 0;
}
int rows = grid.length, columns = grid[0].length;
int[][] dp = new int[rows][columns];
dp[0][0] = grid[0][0];
for (int i = 1; i < rows; i++) { // 计算最左列的值:此时左边是边界
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
for (int j = 1; j < columns; j++) { // 计算最上行的值:此时上边是边界
dp[0][j] = dp[0][j - 1] + grid[0][j];
}
for (int i = 1; i < rows; i++) { // 其余元素
for (int j = 1; j < columns; j++) {
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
return dp[rows - 1][columns - 1];
}
复杂度分析
-
时间复杂度:
O(m*n)
,其中 m 和 n 分别是网格的行数和列数。需要对整个网格遍历一次,计算 dp 的每个元素的值。 -
空间复杂度:
O(m*n)
,其中 m 和 n 分别是网格的行数和列数。创建一个二维数组 dpdp,和网格大小相同。
注:空间复杂度可以优化,例如每次只存储上一行的 dp 值,则可以将空间复杂度优化到 O(n)。
附:4*4矩阵的16步完整过程示例
附:完整代码(含测试用例)
class Solution {
public static void main(String[] args) {
// int[][] grid = {{1, 2, 3}, {4, 5, 6}};
int[][] grid = {{3, 8, 6, 0, 5, 9, 9, 6, 3, 4, 0, 5, 7, 3, 9, 3}, {0, 9, 2, 5, 5, 4, 9, 1, 4, 6, 9, 5, 6, 7, 3, 2}, {8, 2, 2, 3, 3, 3, 1, 6, 9, 1, 1, 6, 6, 2, 1, 9}, {1, 3, 6, 9, 9, 5, 0, 3, 4, 9, 1, 0, 9, 6, 2, 7}, {8, 6, 2, 2, 1, 3, 0, 0, 7, 2, 7, 5, 4, 8, 4, 8}, {4, 1, 9, 5, 8, 9, 9, 2, 0, 2, 5, 1, 8, 7, 0, 9}, {6, 2, 1, 7, 8, 1, 8, 5, 5, 7, 0, 2, 5, 7, 2, 1}, {8, 1, 7, 6, 2, 8, 1, 2, 2, 6, 4, 0, 5, 4, 1, 3}, {9, 2, 1, 7, 6, 1, 4, 3, 8, 6, 5, 5, 3, 9, 7, 3}, {0, 6, 0, 2, 4, 3, 7, 6, 1, 3, 8, 6, 9, 0, 0, 8}, {4, 3, 7, 2, 4, 3, 6, 4, 0, 3, 9, 5, 3, 6, 9, 3}, {2, 1, 8, 8, 4, 5, 6, 5, 8, 7, 3, 7, 7, 5, 8, 3}, {0, 7, 6, 6, 1, 2, 0, 3, 5, 0, 8, 0, 8, 7, 4, 3}, {0, 4, 3, 4, 9, 0, 1, 9, 7, 7, 8, 6, 4, 6, 9, 5}, {6, 5, 1, 9, 9, 2, 2, 7, 4, 2, 7, 2, 2, 3, 7, 2}, {7, 1, 9, 6, 1, 2, 7, 0, 9, 6, 6, 4, 4, 5, 1, 0}, {3, 4, 9, 2, 8, 3, 1, 2, 6, 9, 7, 0, 2, 4, 2, 0}, {5, 1, 8, 8, 4, 6, 8, 5, 2, 4, 1, 6, 2, 2, 9, 7}};
System.out.println(minPathSum1(grid));
System.out.println(minPathSum2(grid));
}
/**
* 递归解法(超时)
*/
public static int minPathSum1(int[][] grid) {
int right = 0, down = 0;
return getMinSum(0, right, down, grid);
}
public static int getMinSum(int cur, int right, int down, int[][] grid) {
int m = grid.length - 1; // 行边界
int n = grid[0].length - 1; // 列边界
cur += grid[down][right];
if (right == n && down == m) { // 到终点
return cur;
} else if (right == n) { // 右边界
return getMinSum(cur, right, down + 1, grid);
} else if (down == m) { // 下边界
return getMinSum(cur, right + 1, down, grid);
} else {
return Math.min(getMinSum(cur, right, down + 1, grid), getMinSum(cur, right + 1, down, grid));
}
}
/**
* 动态规划解法(推荐)
*/
public static int minPathSum2(int[][] grid) {
if (grid == null || grid.length == 0 || grid[0].length == 0) {
return 0;
}
int rows = grid.length, columns = grid[0].length;
int[][] dp = new int[rows][columns];
dp[0][0] = grid[0][0];
for (int i = 1; i < rows; i++) { // 计算最左列的值:此时左边是边界
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
for (int j = 1; j < columns; j++) { // 计算最上行的值:此时上边是边界
dp[0][j] = dp[0][j - 1] + grid[0][j];
}
for (int i = 1; i < rows; i++) { // 其余元素
for (int j = 1; j < columns; j++) {
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
return dp[rows - 1][columns - 1];
}
}