动态规划算法

目录

1、什么是动态规划算法?

2、什么时候使用动态规划?

3、动态规划步骤

4、示例:

1、最大子数组和

2、最小三角形路径和

3、最小路径和

4、不同路径||

5、打家劫舍


1、什么是动态规划算法?

动态规划算法和分治算法类似,都是通过组合子问题的方式来解决新问题。区别在于,分治算法将问题分解为互不相交的子问题,递归的求解子问题,再组合解,求出原问题动态规划算法应用于子问题重叠的情况,不同的子问题有共同的子子问题

2、什么时候使用动态规划?

动态规划算法通常用来求解最优化问题。这类问题有很多可行解,每个解都有一个值,需要寻找最优值(最大值或者最小值)的解。

3、动态规划步骤

  1. 刻画最优解特征(如何求得子问题的最优解);
  2. 递归定义最优解的值;
  3. 计算最优解的值,通常采用自底向上的方法。

4、示例:

1、最大子数组和

最大子数组和就是一个典型的求取最大值的问题,可以分解为求取 每个位置下的最大值f(i),如何求取f(i),可以考虑nums[i]单独成为一段还是加入f(i-1)那一段,于是动态规划转移方程为:

f(i)=max{(f(i-1)+nums[i],nums[i])}

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的结点。

如果用f[i][j]表示走到第i行列的最小路径和,那么f[i][j]=min(f[i-1][j],f[i-1][j-1])+triangle[i][j]

以上公式是有受限情况的:

  1. 每一行的第一个元素,即f[i][0]位置,其状态转移方程为:f[i][0]=f[i-1][0]+triangle[i][0]
  2. 第i行有i个元素,f[i][i]的状态转移方程为:f[i][i]=f[i-1][i-1]+triangle[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列的状态转移方程为:dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j]

以上公式是有受限的,当i-1或者j-1超出数组的索引范围

  1. 当i>0,j=0时,dp[i][0]=dp[i-1][0]+grid[i][0]
  2. 当i=0,j>0时,dp[0][j]=dp[0][j-1]+gird[0][j]
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)这道题是求能到达终点处一共有多少路径。

有几种特殊情况需要考虑:

  1. 要是start的位置既是障碍物,那么立即结束,到达终点处的路径数为0;
  2. 边界位置处的初始值设置问题;

本题的状态转移方程和上道题一样,dp[i][j]=min(dp[i-1][j],dp[i][j-1])+grid[i][j]

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、打家劫舍

本题的条件是:不能偷相邻的房屋

如果房间大于两间的话,状态转移方程为:dp[i]=max(dp[i-1],dp[i-2]+nums[i])

边界条件为:

  • 只有一间房,dp[0]=nums[0];
  • 有两间房,dp[1]=max(dp[0],dp[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];
        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]);
    }
};

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值