20210916 动态规划

本文深入探讨了动态规划的概念,包括其特点、解题思路和常见类型。通过两个经典实例——硬币组合最少数量问题和网格行走路径计数,详细阐述了动态规划的求解过程,包括状态定义、转移方程、初始条件和计算顺序。同时提供了Java代码实现,帮助读者更好地理解和应用动态规划。

20210916 动态规划

动态规划题目特点:

  • 计数
    • 如:
      • 有多少种方式走到右下角
      • 有多少种方式选出k个数的和是给定的sum
  • 求最大最小值
    • 如:
      • 从左上角走到右下角路径的最大数字和
      • 最长上升子序列长度
  • 求存在性
    • 如:
      • 取石子判断是否能取胜
      • 能不能选出k个数使得他们的和为sum

动态规划题目的一般解题思路:

  • 确定状态
    • 研究最优策略的最后一步
    • 将问题转换为更小规模的子问题
  • 转移方程
    • 可以根据子问题直接得到
  • 初始条件和边界情况
    • 需要仔细判断
  • 判断计算的顺序
    • 总体思路是利用已经计算过的结果得到当前规模问题的结果

常见的动态规划类型

  • 坐标型动态规划
    • 一维二维矩阵中的问题
  • 序列型动态规划
    • 如从空序列开始,硬币序列能够凑出总值的问题
  • 划分型动态规划
    • 把n个东西分为几段每段又满足什么条件
  • 区间型动态规划
    • 矩阵乘法、气球爆炸等题目,存在左右端点、区间
  • 背包型动态规划
    • 如何求最大价值,或者最大重量之类的问题
  • 最长序列型动态规划
    • 最长上升子序列之类的题目
  • 博弈型动态规划
    • 下棋,判断是否先手必胜之类的题目
  • 综合类型

典中典的例题

1.你有三种硬币,分别面值2元、5元和7元每种硬币足够多;当前买一本书需要27元。问:如何使用最少的硬币数量组合刚好付清,且无需找零。

  • 确定状态

    • 最优策略的最后一步:
      • 当前最后一步是:最后一枚硬币A(k)面值刚好能够付清,并且不需要找零,同时组合的硬币数量最少
    • 子问题:
      • 需要判断A(k-1)能否被当前硬币面值组合付清,并且不需要找零,同时组合的硬币数量最少
      • 结果:当前问题规模从k 转换为 k-1
  • 转移方程

    • 分析:
      • 如果可以满足当前面值组合并且组合的硬币数最少,使用dp[k] 保存当前组合使用的最少硬币数
      • 那么,dp[k-1] 可以满足当前面值组合并且组合的硬币数最少
      • 于是得到状态转移方程:
        • 最后那枚硬币为A(k),且面值仅可能为 2或5或7
          • 如果硬币的面值为 2 :dp[27] = 1 + dp[25]
          • 如果硬币的面值为 5 :dp[27] = 1 + dp[22]
          • 如果硬币的面值为 7 :dp[27] = 1 + dp[20]
        • 除此之外,没有更多的情况
        • 因为要求组合的硬币数量最少:
          • dp[27] = min{dp[25] + 1, dp[22] + 1, dp[20] + 1}
  • 初始条件和边界情况

    • 分析:
      • 如果dp[x-2]或dp[x-5]或dp[x-7] 其中的 x-2、x-5、x-7 小于0怎么办,什么时候停下来
      • 事先定义,如果不能拼出结果,则定义该dp为正无穷
        • dp[负数] = INFINITY
        • dp[1] = min{dp[-1] , dp[-4], dp[-6]} —> INFINITY
        • dp[0] = 0 零元需要零块硬币解决当前问题
  • 计算顺序

    • 如果从大到小计算,需要计算子问题很多次
    • 如果从小到大计算,更大规模数量的问题可以通过较小规模问题的值得到
  • Java实现代码:

    输入:

    2 5 7
    27
    

    输出:

    5
    

    代码实现

    import java.util.*;
    
    public class Main{
    	public static void main(String[] args){
        //输入处理
        Scanner sc = new Scanner(System.in);
        
        String line = sc.nextLine();
        int target = Integer.parseInt(sc.nextLine());
        String[] str = line.split(" ");
        int[] coinValue = new int[str.length];
        for(int i = 0; i < str.length; i++){
          coinValue[i] = Integer.parseInt(str[i]);
        }
        
        System.out.println(findMinNum(coinValue,target));
        
    	}
      
      public static findMinNum(int[] group, int target){
        int n = group.length;
        int[] dp = new int[n + 1];
        //边界条件
        dp[0] = 0;
    		//初始化
        for(int i = 1; i <= target; i ++){ //从1开始,所以要取到target
          dp[i] = Integer.MAX_VALUE;
          for(int j = 0; j < n; j++){
            dp[i] = Math.min(dp[i],dp[i - group[j]] + 1);
          }
        }
        
        if(dp[target] == Integer.MAX_VALUE){
    			return -1;
    		}
    		else{
    			return dp[target];
    		}
      }
    		
    }
    

2.给定m*n的网格,有个机器人从左上角[0,0]出发,每一步可以向下或者向右走一步。问有多少种方式能走到最右下角

  • 确定状态

    • 最优策略的最后一步
      • 最后一步:当前最后到[m-1,n-1],可以从上面的格子[m-2,n-1],和左边的格子[m-1,n-2]两种方式
    • 子问题
      • 分析:
        • 如果当前有X种方式从上面格子[m-2,n-1]走下来,有Y种方式从左边格子[m-1,n-2]走到右边
        • 所以走到[m-1,n-1]存在X+Y种方式
        • 子问题:有多少种方式走到[m-2,n-1],以及有多少种方式走到[m-1,n-2]
  • 转移方程

    • f[i][j] = f[i - 1][j] + f[i][j - 1]
  • 初始条件和边界条件

    • 初始条件f[0][0] = 1 —> 机器人有一种方式走到f[0][0]
    • 边界情况:f[i][j] 当 i == 0 或 j == 0则只有一种方式,即从上往下或者从左往右走
  • 计算顺序

    • 从上到下,从左到右
    • 利用前面已计算过的结果
  • Java实现代码:

    输入

    1 1
    

    输出

    1
    

    代码实现:

    import java.util.*;
    
    public class Main{
      public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        
        int m = Integer.parseInt(sc.nextInt());
        int n = Integer.parseInt(sc.nextInt());
        
        System.out.println(findWays(m,n));
      }
      
      public static int findWays(int m, int n){
        int[][] dp = new int[m][n];
        
        // 从上到下
        for(int i = 0; i < m; i++){
          //从左到右
          for(int j = 0; j < n; j++){
            if(i == 0 || j == 0){
              dp[i][j] = 1;
              break;
            }
            
            dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
          }
        }
        return dp[m - 1][n - 1];
      }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值