目录
1、什么是动态规划算法?
动态规划算法和分治算法类似,都是通过组合子问题的方式来解决新问题。区别在于,分治算法将问题分解为互不相交的子问题,递归的求解子问题,再组合解,求出原问题。动态规划算法应用于子问题重叠的情况,不同的子问题有共同的子子问题。
2、什么时候使用动态规划?
动态规划算法通常用来求解最优化问题。这类问题有很多可行解,每个解都有一个值,需要寻找最优值(最大值或者最小值)的解。
3、动态规划步骤
- 刻画最优解特征(如何求得子问题的最优解);
- 递归定义最优解的值;
- 计算最优解的值,通常采用自底向上的方法。
4、示例:
1、最大子数组和
最大子数组和就是一个典型的求取最大值的问题,可以分解为求取 每个位置下的最大值,如何求取,可以考虑单独成为一段还是加入那一段,于是动态规划转移方程为:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int length=nums.size();
int dp[length];
dp[0]=nums[0];
for(int i=1;i<length;i++)
{
dp[i]=max(dp[i-1]+nums[i],nums[i]);
}
int maxValue=dp[0];
for(int j=0;j<length;j++)
{
maxValue=max(maxValue,dp[j]);
}
return maxValue;
}
};
2、最小三角形路径和
此题中的条件是:每一步只能移动到下一行中的相邻结点上,如何定义相邻?
- 下标与上一层结点下标相同
- 等于上一层下标+1的结点。
如果用表示走到第i行列的最小路径和,那么
以上公式是有受限情况的:
- 每一行的第一个元素,即位置,其状态转移方程为:
- 第i行有i个元素,的状态转移方程为:
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
//动态规划的思想是找到每一个元素对应的最小路径和
int row=triangle.size();
//动态规划数组
vector<vector<int>> f(row,vector<int>(row));
//边界情况
f[0][0]=triangle[0][0];
for(int i=1;i<row;i++)
{
f[i][0]=f[i-1][0]+triangle[i][0];
for(int j=1;j<i;j++)
{
f[i][j]=min(f[i-1][j-1],f[i-1][j])+triangle[i][j];
}
f[i][i]=f[i-1][i-1]+triangle[i][i];
}
return *min_element(f[row - 1].begin(), f[row- 1].end());
}
};
3、最小路径和
这道题中的条件是每次只能向下走或者向右走,因此走到第i行j列的状态转移方程为:
以上公式是有受限的,当i-1或者j-1超出数组的索引范围
- 当i>0,j=0时,
- 当i=0,j>0时,
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m=grid.size()-1,n=grid[0].size()-1;
auto dp = vector < vector <int> > (m+1, vector <int> (n+1));
dp[0][0]=grid[0][0];
for(int i=1;i<=m;i++)
dp[i][0]=dp[i-1][0]+grid[i][0];
for(int j=1;j<=n;j++)
dp[0][j]=dp[0][j-1]+grid[0][j];
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j];
}
}
return dp[m][n];
}
};
4、不同路径||
这一题和上一题有两点不同之处,(1)这道题添加了障碍物,就是值为1的地方;(2)这道题是求能到达终点处一共有多少路径。
有几种特殊情况需要考虑:
- 要是start的位置既是障碍物,那么立即结束,到达终点处的路径数为0;
- 边界位置处的初始值设置问题;
本题的状态转移方程和上道题一样,
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
//与最小路径和不一样的是 这题有障碍物
//找不同的路径
int row=obstacleGrid.size(),colum=obstacleGrid[0].size();
int maxvalue=max(row,colum);
vector<vector<int>>dp(maxvalue,vector<int>(maxvalue));
//遇到的第一种特殊情况:start位置就是障碍物
if(obstacleGrid[0][0]==1)
return 0;
else
dp[0][0]=1;
//边界初始值设置--左边
for(int i=1;i<row;i++)
{
//对于上下相邻的行,如果上面有障碍物,则下一行不能到达
if(obstacleGrid[i][0]==1 ||dp[i-1][0]==0)
dp[i][0]=0;
else
dp[i][0]=1;
}
//边界初始值设置--上边
for(int j=1;j<colum;j++)
{
//对于左右相邻的列,如果左边有障碍物,则下一列不能到达
if(obstacleGrid[0][j]==1 ||dp[0][j-1]==0)
dp[0][j]=0;
else
dp[0][j]=1;
}
//i>0,j>0的情况
for(int i=1;i<row;i++)
{
for(int j=1;j<colum;j++)
{
if(obstacleGrid[i][j]==1)
{
dp[i][j]=0;
}
else
{
if(dp[i-1][j]!=0)
dp[i][j]+=dp[i-1][j];
if(dp[i][j-1]!=0)
dp[i][j]+=dp[i][j-1];
}
}
}
return dp[row-1][colum-1];
}
};
5、打家劫舍
本题的条件是:不能偷相邻的房屋
如果房间大于两间的话,状态转移方程为:
边界条件为:
- 只有一间房,
- 有两间房,
class Solution {
public:
int rob(vector<int>& nums) {
//状态转移方程
int length=nums.size();
//不能偷相邻的
//dp[i]=max(dp[i-1],dp[i-2]+nums[i])
int dp[length];
if(length==1)
return nums[0];
dp[0]=nums[0];
dp[1]=max(nums[0],nums[1]);
for(int i=2;i<length;i++)
{
dp[i]=max(dp[i-1],dp[i-2]+nums[i]);
}
return dp[length-1];
}
};
class Solution {
public:
int rob(vector<int>& nums) {
//状态转移方程
int length=nums.size();
//不能偷相邻的
//dp[i]=max(dp[i-1],dp[i-2]+nums[i])
//有两种选择,偷或者不偷
int dp[length][2];
dp[0][0]=0,dp[0][1]=nums[0];
for(int i=1;i<length;i++)
{
dp[i][0]=max(dp[i-1][1],dp[i-1][0]);
dp[i][1]=dp[i-1][0]+nums[i];
}
return max(dp[length-1][0],dp[length-1][1]);
}
};