动态规划篇(2021-7-27)

本文深入讲解动态规划的基本概念与步骤,通过四个典型实例演示如何定义状态、建立递推关系及确定初始条件,帮助读者掌握动态规划解决问题的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

动态规划

1.动态规划的三大步骤定义
动态规划的作用
1.计数:a.有多少种方式走到右下角
       b.有多少种选出k个数使得和是Sum
2.求最大最小值
       a.从左上角走到右下角路径的最大数字和
       b.最长上升子序列长度
3.求存在性
       a.取石子游戏,先手是否必胜
       b.能不能选出k个数使得和是Sum
(1)定义数组元素的含义,假设用一维数组dp[]保存历史数组,设置好dp[i]是代表什么意思。
(2)找出数组元素之间的关系式:就是和归纳法相似,计算dp[n],利用dp[n-1],...dp[1],来推出dp[n],找出元素之间的关系。dp[n]=dp[n-1]+dp[n-2],
(3)找出初始值。要知道初始值,dp[3]=dp[2]+dp[1]。要能直接获得dp[2]和dp[1]的初始值。

2.案例

案例1.一只青蛙一次可以跳上1级台阶,也可以跳上2级。求青蛙跳上一个n级台阶总共有多少种跳法。
(1)定义数组元素的含义:青蛙跳上n级台阶总共有多少种跳法,跳上一个i级的台阶总共有dp[i]种跳法。
(2)找出数组元素间的关系式:目的是求dp[n],规模为n,比它小的规模是n-1,n-2,n-3,就是找出他们之间的关系。dp[n]=dp[n-1]+dp[n-2].
(3)找出初始条件:n=1时,dp[1]=dp[0]+dp[-1],下标不允许为负,就相当于dp[1]=1.dp[0]=0
class Solution{
   public int climbStairs(int n){
      if(n==1) return 1;
      //创建一个数组来保存历史数据
      int[] dp=new int[n+1];
      //给出初始值
      dp[1]=1;
      dp[2]=2;
      //通过关系式来计算出dp[n]
      for(int i=3;i<=n;i++){
        dp[i]=dp[i-1]+dp[i-2];
      }
      //把最终结果返回
      return dp[n];
   }
}
案例2:一个机器人位于一个m×n网络的左上角,机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角
(1)定义数组元素的含义:dp[i][j]:当机器人从左上角走到(i,j)这个位置,一共有dp[i][j]种路径。dp[m-1][n-1]就是要的答案。
(网格相当于一个二维数组,数组是从下标0开始算起的,所以右下角的位置是(m-1,n-1,所以dp[m-1][n-1]就是要的答案)
(2)找出关系数组元素间的关系式:机器人怎么才能达到(i,j)这个位置。可以向下和向右走。
一种是(i-1,j)这个位置走一步到达
一种是(i,j-1)这个位置走到下一步
因为计算所有可能的步骤,所以把所有可能走的路径都加起来,所以关系式是dp[i][j]=dp[i-1][j]+dp[i][j-1].
(3)找出初始值:假如dp[i][j]中,如果i或者j有一个为0,就不能使用关系式了。这样就把i-1和j-1变成负数了。所以计算出初始值为dp[0][0....n-1]和dp[0...m-1][0],相当于图中的最上面一行和左边一列。
dp[0][0....n-1]=1;//相当于最上面一行,机器人只能一直往左走
dp[0...m-1][0]=1;//相当于最左面一列,机器人只能一直往下走
class Solution{
   public static int uniquePaths(int m,int n){
   if(m<=0||n<=0){
   return 0;
   }
   //创建一个二维数组保存历史数据
   int[][] dp=new int[m][n];
   //初始化
   for(int i=0;i<m;i++){
   dp[i][0]=1;
   }
   for(int j=0;j<n;j++){
   dp[0][j]=1;
   }
   //推导出dp[m-1][n-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-1][n-1];
   }
}
案例3:给定一个包含非负整数的m×n网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。
说明:每次只能向下或者向右移动一步。(算最优路径和)
(1)定义数组元素的含义:定义dp[i][j]的含义:当机器人从左上角走到(i,j)这个位置时,最下的路径和是dp[i][j]。dp[m-1][n-1]是我们要的答案。数组从下标为0开始算起,所以下角位置是(m-1,n-1).
(2)找出关系数组元素间的关系式
 机器人可以向下或者向右走,所以有两种方式达到
 一种是从(i-1,j)这个位置走一步到达
 一种是从(i,j-1)这个位置走一步到达
 计算哪一个路径和是最小的。
dp[i][j]=min(dp[i-1][j],dp[i][j-1])+arr[i][j];//arr[i][j]表示网格中的值
(3)找出初始值
dp[i][j]中,如果i或者j有一个为0,也是不能使用关系式。这个时候把i-1或者j-1,就变成负数了。初始值是计算出所有的dp[0][0...n-1]和所有的dp[0...m-1][0]。
dp[0][j]=arr[0][j]+dp[0][j-1];//相当于最上面一行,机器人只能一直往左走
dp[i][0]=arr[i][0]+dp[i][0];//相当于最左面一列,机器人只能一直往下走
class Solution{
   public static int uniquePaths(int[][] arr){
     int m=arr.length;
     int n=arr[0].length;
     if(m<=0||n<=0){
     return 0;
     }
     //创建一个二维数组
     int[][] dp=new int[m][n];
     //初始化
     dp[0][0]=arr[0][0];
     //初始化最左边的列
     for(int i=1;i<m;i++){
       dp[i][0]=dp[i-1][0]+arr[i][0];
     }
     //初始化最上边的行
     for(int j=1;j<n;j++){
       dp[0][j]=dp[0][j-1]+arr[0][j];
     }
     //推导出dp[m-1][n-1]
     for(int i=1;i<m;i++){
        for(int j=1;j<n;j++){
           dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+arr[i][j];
        }
     }
     return dp[m-1][n-1];
   }
}
案例4:给定两个单词word1和word2,计算将word1转换成word2所使用的最少操作数
(1)定义数组元素的含义:所使用的的最少操作数,定义dp[i][j]的含义:当字符串word1的长度为i,word2的长度为j,将word1转化为word2所使用的最少操作次数为dp[i][j].
(2)找出关系数组元素间的关系式:找出dp[i][j]元素之间的关系,dp[i-1][j]、dp[i][j-1]、dp[i-1][j-1]存在着某种关系,通过规模小的推导出规模大的。
a.word1[i]与word2[j]相等,这个时候不需要进行任何操作,显然有dp[i][j]=dp[i-1][j-1].
b.word1[i]与word2[j]不相等,就必须进行调整,调整的操作有3种,三种操作关系如下(注意字符串与字符的区别):
   1)如果把字符word1[i]替换成与word2[j]相等,则有dp[i][j]=dp[i-1][j-1]+1;
   2)如果在字符串word1末尾插入一个与word2[j]相等的字符,则有dp[i][j]=dp[i][j-1]+1;
   3)如果把字符word1[i]删除,则有dp[i][j]=dp[i-1][j]+1;
   选择使得dp[i][j]的值最小,dp[i][j]=min(dp[i-1][j-1],dp[i][j-1],dp[i-1][j])+1
(3)找出初始值
     dp[i][j]中,如果i或者j有一个为0,不能使用关系式。会使i-1或者j-1变成负数。我们的初始值计算出所有的dp[0][0....n]和所有的dp[0...m][0],这个还是非常容易计算的,因为当有一个字符串的长度为0时,转化为另外一个字符串,就只能一直进行插入或者删除
class Solution{
   public int minDistance(String word1,String word2){
       //定义两个数组的长度
       int n1=word1.length();
       int n2=word2.length();
       //创建二维数组存储历史数据
       int[][] dp=new int[n1+1][n2+1];
       //初始化dp[0][0...n2]的初始值
       for(int j=1;j<=n2;j++) dp[0][j]=dp[0][j-1]+1;
       //初始化dp[0...n1][0]的初始值
       for(int i=1;i<=n1;i++) dp[i][0]=dp[i-1][0]+1;
       //通过公式推出dp[n1][n2]
       for(int i=1;i<=n1;i++){
          for(int j=1;j<=n2;j++){
          //如果word1[1]与word2[j]相等,第i个字符对应下标是i-1
          if(word1.charAt(i-1)==word2.charAt(j-1)){
              dp[i][j]=dp[i-1][j-1];
          }else{
             dp[i][j]=Math.min(Math.min(dp[i-1][j-1],dp[i][j-1]),dp[i-1][j])+1;
          }
          }
       }
       return dp[n1][n2];
   }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值