您现在的位置是:首页 >技术杂谈 >60题学会动态规划系列:动态规划算法第二讲网站首页技术杂谈
60题学会动态规划系列:动态规划算法第二讲
都是路径问题~
文章目录
- 1.不同路径
- 2.不同路径II
- 3.礼物的最大价值
- 4.下降路径最小和
- 5.最小路径和
1.不同路径
力扣链接:力扣
一个机器人位于一个 m x n
网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
本题中的重点在于:机器人每次只能向下或向右走,下面我们画图分析一下:
如上图,我们要求终点的位置的路径总和其实只要计算终点位置的上一个位置和终点位置的左边一个位置的方法总和,因为只有这两个位置可以一步到终点,而计算这两个位置的方法数也同理,只需要求上面那个位置和左边那个位置的和,所以我们的状态可以表示成到达这个位置的路径总和
1.状态表示
dp[i][j]表示到达i,j位置的路径总和.
2.状态转移方程
dp[i][j] = dp[i-1][j] + dp[i][j-1]
3.初始化
还记得我们上一篇最后讲的初始化的技巧吗?只需要多开虚拟位置就可以解决越界的问题。首先第一行的值求dp[i-1][j]的时候会越界,第一列的值求dp[i][j-1]的时候会越界,所以我们只需要多开一行一列就能完成初始化:
那么这个多开的虚拟位置如何初始化呢?我们的虚拟位置的值一定不可以影响最终结果,首先第一行不管是哪一个位置他们的方法数都为1,因为这个机器人只能向右或者向下。然后第一列也同理所有的方法数都为1,这个时候我们看第一行最右边的位置,这个位置需要上面位置的方法数和左边位置的方法数之和,首先起点位置是一种方法,所以我们的虚拟位置一定为0,但是为了保证起点为1,所以只需要让起点的上一个位置或者左边的位置为1即可,如下图:
4.填表顺序
整体从上往下,每一行从左往右依次填表
5.返回值
返回dp表最后一个位置
class Solution {
public:
int uniquePaths(int m, int n) {
//创建dp表
vector<vector<int>> dp(m+1,vector<int>(n+1,0));
//初始化
dp[1][0] = 1;
//状态转移方程
for (int i = 1;i<=m;i++)
{
for (int j = 1;j<=n;j++)
{
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
//返回值
return dp[m][n];
}
};
要注意:我们的虚拟位置是不进行动态计算的,所以实际上的位置是从1,1开始的,从上面的图我们也可以发现。刚开始二维数组初始化为0会方便很多。
2.不同路径II
力扣链接:力扣
一个机器人位于一个 m x n
网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。
现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?
网格中的障碍物和空位置分别用 1
和 0
来表示。
此题与上一题一样,只不过障碍物的位置方法数为0.
1.状态表示
dp[i][j]表示到达i,j位置的方法数。
2.状态转移方程
当dp[i][j]!=1时,dp[i][j] = dp[i-1][j] + dp[i][j-1]
当dp[i][j]==1时,dp[i][j]==0
3.初始化
与上一题一样
4.填表顺序
与上一题一样
5.返回值
与上一题一样
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
//创建dp表
int row = obstacleGrid.size();
int col = obstacleGrid[0].size();
vector<vector<int>> dp(row+1,vector<int>(col+1,0));
//初始化
dp[1][0] = 1;
//状态转移方程
for (int i = 1;i<=row;i++)
{
for (int j = 1;j<=col;j++)
{
if (obstacleGrid[i-1][j-1]==1)
{
dp[i][j] = 0;
}
else
{
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
}
return dp[row][col];
}
};
注意:dp表由于多开了虚拟位置所以与原数组映射整体向右下角移动了一位。
3.礼物的最大价值
力扣链接:力扣
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
此题实际上还是路径问题,下面我们画个图演示:
其实做了这么多道动态规划题我们也有些经验了,本题的条件还是每次只能向下或者向右移动,那么到达右下角拿到的最大价值的礼物其实就是右下角上面位置和右下角左边位置中拿到的礼物的价值的最大值再加上右下角的礼物价值,下面我们直接按步骤解题:
1.状态表示
dp[i][j]表示到达i,j位置的最大礼物价值
2.状态转移方程
dp[i][j] = max(dp[i-1][j],dp[i][j-1]) + 原数组[i][j]
3.初始化
我们还是用多开虚拟位置的方式初始化,下面我们画图演示:
题目中有一个小细节:所有礼物的价值都是大于0的,而我们的虚拟位置是不能影响结果的,起点位置的价值就是1不能被影响那么虚拟位置只能是0了,当我们填0后发现所有的结果都是正确的。
4.填表
整体从上往下,每一行从左向右依次填表
5.返回值
返回dp表最后一个位置
class Solution {
public:
int maxValue(vector<vector<int>>& grid) {
//创建dp表
int row = grid.size();
int col = grid[0].size();
//初始化
vector<vector<int>> dp(row+1,vector<int>(col+1,0));
//状态转移方程
for (int i = 1;i<=row;i++)
{
for (int j = 1;j<=col;j++)
{
dp[i][j] = max(dp[i-1][j],dp[i][j-1]) + grid[i-1][j-1];
}
}
return dp[row][col];
}
};
4.下降路径最小和
力扣链接:力扣
给你一个 n x n
的 方形 整数数组 matrix
,请你找出并返回通过 matrix
的下降路径 的 最小和 。
下降路径 可以从第一行中的任何元素开始,并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列(即位于正下方或者沿对角线向左或者向右的第一个元素)。具体来说,位置 (row, col)
的下一个元素应当是 (row + 1, col - 1)
、(row + 1, col)
或者 (row + 1, col + 1)
。
此题很简单,从第一行任选一个位置开始向下加和,向下的规则是下面相邻的三个位置,直到最后一行的最小路径和,理解了这个条件我们的状态表示就很简单了。
1.状态表示
dp[i][j]表示到达i,j位置的最小下降和
2.状态转移方程
我们的dp[i][j]既然是到达i,j位置的最小下降和,那么状态转移方程一定是看从哪几个位置可以到达i,j位置并且求最小值。
dp[i][j] = min(min(dp[i-1][j-1],dp[i-1][j]),dp[i-1][j+1]) + 原数组[i][j]
3.初始化
我们依次用开虚拟位置的方式,下面我们画个图:
越界的位置就是我画圈的位置,那么我们如何初始化才不会影响结果呢?这里一定不能将虚拟位置初始化为0,因为题目中是有负数的!:
所以我们要求最小值不影响原来的位置的值,只能将虚拟位置初始化为无穷大,这样虚拟位置就一定不会是最小值了。如下图:
首先第一行是不需要计算的,所以第一行的值我们要保持原始结果,那么上面虚拟位置就不能影响我们第一行的值,所以第一行的虚拟位置初始化为0.第一列和最后一列在计算的时候不能影响我们求最小值,所以我们将这两列虚拟位置初始化为正无穷。
4.填表顺序
从上到下,每一行从左向右
5.返回值
我们的状态表示是到达i,j位置的最小和,那么只要找到dp表最后一行的最小值即可
class Solution {
public:
int minFallingPathSum(vector<vector<int>>& matrix) {
//创建dp表
int row = matrix.size();
int col = matrix[0].size();
vector<vector<int>> dp(row+1,vector<int>(col+2,INT_MAX));
//初始化
for (int i = 0;i<col+2;i++)
{
dp[0][i] = 0;
}
//状态转移方程
for (int i = 1;i<=row;i++)
{
for (int j = 1;j<=col;j++)
{
dp[i][j] = min(min(dp[i-1][j-1],dp[i-1][j]),dp[i-1][j+1])+matrix[i-1][j-1];
}
}
int min = dp[row][0];
for (int i = 1;i<=col;i++)
{
if (dp[row][i]<min)
{
min = dp[row][i];
}
}
return min;
}
};
我们初始化vector的时候全部初始化为正无穷会比初始化0方便很多,初始化0我们要修改两列的值,初始化正无穷只需要修改第一行的值,将第一行初始化为0,由于我们的min函数只能是两个参数,所以我们先求前两个的最小值再和最后一个值求一下最小值即可,最后我们将dp表最后一行的最小元素拿出来即可。注意:行列的判断条件要画图去看,因为我们实际上是不算虚拟位置的所以一定要控制好判断条件。
5.最小路径和
力扣链接:力扣
给定一个包含非负整数的 m x n
网格 grid
,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。
这道题的条件和之前一样,都是只能往下或者往右走,在这种条件下到达右下角,那么右下角的最小值一定是右下角的上面位置或者右下角的左边位置的最小值。
1.状态表示
dp[i][j]表示从起点到达i,j位置的最小值。
2.状态转移方程
能到达i,j位置一定是[i-1][j]和[i][j-1]这两个位置
dp[i][j] = min(dp[i-1][j],dp[i][j-1])
3.初始化
我们继续用开虚拟位置的思想,根据状态转移方程我们知道能越界的位置只有第一行和第一列,所以我们只需要多开一行和多开一列即可:
我们的起始位置的最小值只能是1,所以起始位置的上面的位置和左边的位置不能影响起始位置的值,所以将1的上面和左边初始化为0,其他位置为了不影响计算最小值所以要初始化为无穷大。
4.填表
从上往下,每行从左向右依次填表
5.返回值
dp表最后一个位置的值。
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
//创建dp表
int row = grid.size();
int col = grid[0].size();
vector<vector<int>> dp(row+1,vector<int>(col+1,INT_MAX));
dp[0][1] = dp[1][0] = 0;
//状态转移方程
for (int i = 1;i<=row;i++)
{
for (int j = 1;j<=col;j++)
{
dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + grid[i-1][j-1];
}
}
return dp[row][col];
}
};
以上就是我们动态规划第二篇的5道题,本次的习题都是路径问题,对于动态规划中的路径问题,我们只要掌握了规律就会非常简单。