一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第2天,点击查看活动详情。
给定一个包含非负整数的
m x n网格grid,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。说明:每次只能向下或者向右移动一步。
输入: grid = [[1,3,1],[1,5,1],[4,2,1]]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。
一、分析
给定一个二维数组matrix,一个人必须从左上角出发,最后到达右下角,沿途只可以向下或者向右走,沿途的数字都累加就是距离累加和,返回最小距离累加和。
因为只能向下或向右移动,所以格子第一行和第一列很好填。
方法一:准备一张二维dp表,dp[i][j]含义:当前来到(i,j)位置的格子,最小累加和是多少
第一行和第一列很好填,剩下的格子就是 min(向下走,向右走) + 当前格子的值
方法二:优化空间:原数组matrix,需要建立一张相等规模的dp表,经过分析,每一行的值只依赖于上一行的值和本行左边的值,当这行填完后,下一行又依赖于上一行的值,依次传递下来,所以不需要建立同等规模的dp表
只需要建立一张一维的数组dp即可
二、实现
1、二维dp
public static int minPathSum(int[][] m) {
if (m == null || m.length == 0 || m[0] == null || m[0].length == 0) {
return 0;
}
int row = m.length;
int col = m[0].length;
int[][] dp = new int[row][col];
dp[0][0] = m[0][0];
for (int i = 1; i < row; i++) { // 0列
dp[i][0] = dp[i - 1][0] + m[i][0];
}
for (int j = 1; j < col; j++) { // 0行
dp[0][j] = dp[0][j - 1] + m[0][j];
}
for (int i = 1; i < row; i++) {
for (int j = 1; j < col; j++) {
dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + m[i][j];
}
}
return dp[row - 1][col - 1];
}
2、一维dp(数组压缩技巧)
// dp优化:数组空间压缩,一维数组搞定
public static int minPathSum(int[][] m) {
if (m == null || m.length == 0 || m[0] == null || m[0].length == 0) {
return 0;
}
int row = m.length;
int col = m[0].length;
int[] dp = new int[col];
dp[0] = m[0][0];
for (int j = 1; j < col; j++) { // 0行
dp[j] = dp[j - 1] + m[0][j];
}
for (int i = 1; i < row; i++) {
dp[0] += m[i][0];
for (int j = 1; j < col; j++) {
// arr[j-1] -> dp[这一行][j-1] 左侧的值
// arr[j] -> dp[上一行][j] 上侧的值
dp[j] = Math.min(dp[j - 1], dp[j]) + m[i][j];
}
}
return dp[col - 1];
}
三、总结
记忆化搜索进一步优化:严格位置的空间压缩技巧
常见的数组空间压缩技巧:在数组上整体从上往下(一行一行的)滚动更新 或 在数组上整体从左往右(一列一列的)滚动更新
-
当前位置依赖左边和上边 ,例如本题就是,整体是从上往下更新
-
当前位置依赖于左上和上,第0行直接得到,既不依赖左上,又不依赖上,相当于base case,第1上从右往左依次求,整体是从上往下(一行一行的)更新
-
当前位置依赖于左边、左上边、上边,从左往右更新,但是需要有个变量t记录,整体是从上往下(一行一行的)更新
-
当前位置依赖于左边、左上边、上边,从左往右更新,这种情况是行比较小,列比较大,空间省,整体是从左往右(一列一列的)更新
如果发现列比较短,就一行一行的更新,如果发现列比较长,行比较短,就从一列一列的更新