最小路径和学习笔记
题目描述:
给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
示例:
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。
题目分析
递归方法
首先看到这道题的第一反映就是递归。同之前二叉树题目一样,这道题也可以用递归去分析。这道题的起始位置为grid[0][0],终点为grid[max][max]。他的前进方式为向下或者向右。首先我们分析最简单情况grid[1][1],显然目标及终点。复杂一点的情况grid[2][1],只有一步可走。更复杂的情况grid[2][2],这个就面临不同路径的选择情况,但我们可以通过递归将它拆分为上述两种情况,选择和最小的情况返回。代码如下:
```java
public int minPathSum(int[][] grid) {
if(grid.length == 0) return 0;
//当数组位空直接返回
return min(grid,0,0);
//传入0,0代表起始位置
}
public int min(int[][] gridl,int x,int y) {
int lx = gridl[0].length-1;
int ly = gridl.length-1;
//获取末尾位置
int down =0;
int right =0;
//设置左右变量
if(x==lx&&y==ly) return gridl[ly][lx];
//如果位置到达最后一个就返回
if(x==lx) {
//当x达到最大值,只能往下走,right值应当废弃
down =gridl[y][x] + min(gridl,x,y+1);
right = gridl[y][x] + down;
//这个数要废弃,加上down保证它永远比down大
}
if(y==ly) {
//当y达到最大值,只能往右走,down值应当废弃
right = gridl[y][x]+min(gridl,x+1,y);
down = gridl[y][x]+right;
//同上
}
if(y!=ly&&x!=lx) {
//当既可以往上又可以往下时,两种情况分别递归考虑
right = gridl[y][x]+min(gridl,x+1,y);
down = gridl[y][x]+min(gridl,x,y+1);
}
return (right>down)?down:right;
//选择题目要求数小的返回
}
经过测试,这种思路可行,但是时间复杂和空间复杂较高,运行较为复杂的数组时间过长,所以有了下面的新方法。
动态规划
由于路径的方向只能是向下或向右,因此网格的第一行的每个元素只能从左上角元素开始向右移动到达,网格的第一列的每个元素只能从左上角元素开始向下移动到达,此时的路径是唯一的,因此每个元素对应的最小路径和即为对应的路径上的数字总和。
对于不在第一行和第一列的元素,可以从其上方相邻元素向下移动一步到达,或者从其左方相邻元素向右移动一步到达,元素对应的最小路径和等于其上方相邻元素与其左方相邻元素两者对应的最小路径和中的最小值加上当前元素的值。由于每个元素对应的最小路径和与其相邻元素对应的最小路径和有关,因此可以使用动态规划求解。
创建二维数组 \textit{dp}dp,与原始网格的大小相同,\textit{dp}[i][j]dp[i][j] 表示从左上角出发到 (i,j)(i,j) 位置的最小路径和。显然,\textit{dp}[0][0]=\textit{grid}[0][0]dp[0][0]=grid[0][0]。对于 \textit{dp}dp 中的其余元素,通过以下状态转移方程计算元素值。
当 i>0i>0 且 j=0j=0 时,\textit{dp}[i][0]=\textit{dp}[i-1][0]+\textit{grid}[i][0]dp[i][0]=dp[i−1][0]+grid[i][0]。
当 i=0i=0 且 j>0j>0 时,\textit{dp}[0][j]=\textit{dp}[0][j-1]+\textit{grid}[0][j]dp[0][j]=dp[0][j−1]+grid[0][j]。
当 i>0i>0 且 j>0j>0 时,\textit{dp}[i][j]=\min(\textit{dp}[i-1][j],\textit{dp}[i][j-1])+\textit{grid}[i][j]dp[i][j]=min(dp[i−1][j],dp[i][j−1])+grid[i][j]。
最后得到 \textit{dp}[m-1][n-1]dp[m−1][n−1] 的值即为从网格左上角到网格右下角的最小路径和。代码如下:
public int min2(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]; 、
//直接返回最后一个数值
}
如何理解动态规划,我认为动态规划和递归的区别在于,递归把每一条路都走了,然后选择最优的路。而动态规划它只走一次,每一步都是选择最优的解,所以走到终点就是答案。
题目以及动态规划方法来源:力扣(LeetCode)