给定一个包含非负整数的 *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
解析:
我们想计算从起点
D
到达B
的最小路径和,那你说怎么才能到达B
呢?题目说了只能向右或者向下走,所以只有从
A
或者C
走到B
。从
D
走到A
的最小路径和是 6,而从D
走到C
的最小路径和是 8,6 小于 8,所以一定要从A
走到B
才能使路径和最小。换句话说,我们把「从
D
走到B
的最小路径和」这个问题转化成了「从D
走到A
的最小路径和」和 「从D
走到C
的最小路径和」这两个问题。
dp函数的定义:从左上角位置
(0, 0)
走到位置(i, j)
的最小路径和为dp(grid, i, j)
。状态转移:
dp(grid, i, j)
的值取决于dp(grid, i - 1, j)
和dp(grid, i, j - 1)
返回的值。备忘录优化:
前文多次说过判断重叠子问题的技巧,首先抽象出上述代码的递归框架:
int dp(int i, int j) { dp(i - 1, j); // #1 dp(i, j - 1); // #2 }
如果我想从
dp(i, j)
递归到dp(i-1, j-1)
,有几种不同的递归调用路径?可以是
dp(i, j) -> #1 -> #2
或者dp(i, j) -> #2 -> #1
,不止一种,说明dp(i-1, j-1)
会被多次计算,所以一定存在重叠子问题。那么我们可以使用备忘录技巧进行优化
代码:
class Solution {
int[][] memo;
public int minPathSum(int[][] grid) {
int m=grid.length;
int n=grid[0].length;
memo=new int[m][n];
for(int[] row:memo){
Arrays.fill(row,-1);
}
return dp(grid,m-1,n-1);
}
public int dp(int[][] grid,int i,int j){
if(i==0 && j==0) return grid[0][0];
if(i<0 || j<0) return Integer.MAX_VALUE;
if(memo[i][j]!=-1) return memo[i][j];
memo[i][j]=Math.min(dp(grid,i,j-1),dp(grid,i-1,j))+grid[i][j];
return memo[i][j];
}
}