0
点赞
收藏
分享

微信扫一扫

动态规划分析(粗)

guanguans 2022-02-05 阅读 54

1.动态规划

1.1 递归

1.1.1 动态规划经典题目 - 斐波那契数列

题目:斐波那契数列的前两个为1,后面的为当前数字的前面两个只和(fib[i] = fib[i-1] + fib[i-2]),求斐波那契数列的第i个数字。
题解1 - 递归
public int recur_fib(int i){
   if (i == 1 || i == 2){
      return 1;
   }

   return recur_fib(i-1) + recur_fib(i-2);
}
问题:

小结:

  • 因此我们可以得出,对于一些问题,我们需要通过某种方式来得到数据,并且我们将这些已经求得数据保存起来,当我们需要的时候直接使用,那么我们就可以减少不必要的运算执行。
  • 并且我们可以看到斐波那契数列中除了开头的两个,后面的每一个数字的值只和它前面的两个数字有关。(fib(i) = fib(i-1) + fib(i-2) )
  • 我们可以得出,需要求得的答案是在前面已有的基础上得到的。
  • 递归其实可以看出来是一种第顶向下穷尽的方式,但是对于这道题,我们其实从底向上来做会更加方便(前一条观点)。

1.2 动态规划

  1. 创建一个数组来保存数据
  2. 已知条件,初始赋值
  3. 自底向上,求得结果
  4. 找寻动态规划转移方程
public int dp_fib(int i){
   //1.创建一个数组来保存已经求得的数据,自底向上,最后到达i,所以数组的大小为i
   int[] dp = new int[i];
   //2.已知条件,初始赋值
   dp[0] = 1;
   dp[1] = 1;
   //3.自底向上
   for (int j = 2; j < i ; j++) {
      //4.根据已有的条件求得新的数据并保存,以便下次使用,动态转移方程
      dp[j] = dp[j-1] + dp[j-2];
   }
   //5.返回结果
   return dp[i-1];
}

1.3 题目练习

剑指 Offer 10- II. 青蛙跳台阶问题

分析:
public int numWays(int n) {
   //优化:减少代码运行次数。
   if (n == 0 || n == 1){
      return 1;
   }

   //1.创建数组,保存数据
   int[] dp = new int[n+1];
   //2.已知条件,默认赋值
   dp[1] = 1;
   dp[2] = 2;

   for (int i = 3; i <= n; i++) {
      dp[i] = dp[i-1] + dp[i-2];
   }

   return dp[n];
}

LeetCode 746. 使用最小花费爬楼梯

分析:
public int minCostClimbingStairs(int[] cost) {
   //优化:创建数组需要依靠传入的数据时,一般将长度保存起来便于后面使用。
   int len = cost.length;
   //1.创建数组,保存数据
   int[] dp = new int[len];
   //2.已知条件,默认赋值
   /*
        * 根据我们的分析和题目,我们知道到达最后一个台阶时是不需要支付费用的
        * 这里我们默认赋值也是进行再到达前的赋值,从第一个台阶开始跳或从第二个台阶开始跳
        * */
   dp[0] = cost[0];
   dp[1] = cost[1];
   //3.从底向上
   for (int i = 2; i < len; i++) {
      //4.转移方程
      dp[i] = Math.min(dp[i-1],dp[i-2]) + cost[i];
   }
   //5.返回结果
   return Math.min(dp[len-1],dp[len-2]);
}  

LeetCode 62. 不同路径

分析:
  1. 创建一个数组来保存数据
  2. 已知条件初始赋值
  3. 自低向上,求得结果
  4. 设置动态转移方程
public int uniquePaths(int m, int n) {
   //1.创建一个数组,来保存每一个点的路径和
   int[][] dp = new int[m][n];
	//2.已知条件,初始赋值,对于第一行或第一列来说只有一直向右或一直向下才可能到达,所以只有一种情况
   for(int i=0; i<n; i++){
      dp[0][i] = 1;
   }    

   for(int i=0; i<m; i++){
      dp[i][0] = 1;
   }

	//3.自底向上,求得结果
   for(int i=1; i<m; i++){
      for(int j=1; j<n; j++){
         //4.设置动态转移方程
         dp [i][j] = dp[i][j-1] + dp[i-1][j];
      }
   }
	//5.返回结果
   return dp[m-1][n-1];
}

LeetCode 343. 整数拆分

分析:
public int integerBreak(int n) {
   //优化:减少运行次数
   if(n <= 2){
      return 1;
   }
	//1.创建数组,保存数据,这里我们最后是要达到n的,所以数组的长度为n+1
   int[] dp = new int[n+1];
   //2.已知条件,默认赋值
   dp[2] = 1;
	//3.第一个for循环是自底向上
   for(int i=3; i<=n;i++){
      //第二个for循环就是将数字不断划分部分来找到最大值
      for(int j=2;j<i; j++){
         //4.设置动态转移方程
         //注意:这里的需要用两个max,因为在数字划分的时候,是在当前for循环的,但是i是不变的,那么如果在循环中求得了最大值,就需要通过一个max嵌套来保存
         //方便理解可以写成这样:int temp = Math.max(j*(i-j),j*dp[i-j])); 求得当前划分的最大值
         // 						 dp[i] = Math.max(dp[i],temp); 跟已经得到最大值进行比较,保存最大的
         dp[i] = Math.max(dp[i],Math.max(j*(i-j),j*dp[i-j]));
      }
   }
	//5.返回结果
   return dp[n];
}

1.4 动态规划(背包问题)

1.4.1 经典题目:背包问题

分析:
  1. 每一个物品都有自己的价值和重量
  2. 背包的容量有限
  3. 每一个物品能装多次
public int findPackeMax(int[] value,int[] wight,int capacity){
   int len = capacity;
   //1.创建数组,保存数据,最后是要到达背包的容量,数组大小容量+1
   int[] dp = new int[len+1];
   //2.初始赋值,容量为0,那么价值也为0
   dp[0] = 0;

   //3.自底向上
   //先遍历物品,不断加入新的物品
   for (int i = 1; i < wight.length; i++) {
      //再遍历背包,固定容量capacity 
      for (int j = 0; j <= capacity; j++) {
         //只有背包的容量大于当前需要尝试加入的新物品的需要的容量才能进行装入
         if (j >= i)
            dp[j] = Math.max(dp[j],value[i] + dp[j-wight[i]]);
      }
   }

   //3.返回结果
   return dp[len];
}
总结:

1.5 题目练习

LeetCode 518. 零钱兑换 II

分析:
public int change(int amount, int[] coins) {      
   int len = amount;
   //1.创建数组,保存数据,最后要到达amount因此长度为amount+1
   int[] dp = new int[len+1];
	//2.已知条件,默认赋值
   //这里我们认为如果和为0,是有一种情况的。事实:题目也是这么规定的
   dp[0] = 1;
	//3.自底向上
   for(int i=0; i<coins.length; i++){
      for(int j=1; j<=len; j++){
         //跟背包问题一样,首先需要装的下才行,这里就是硬币的值不能超过和,如果超过那么就没有必要算
         //后面并且是优化,也可以不写。只有新放入的硬币,减去以后存在组合的可能性(即不为0)才有可能组成新的组合
         if(j >= coins[i] && dp[j - coins[i]] != 0){
            //因为问的是所有可能性,即在不添加新硬币求得可能加上加入新硬币求得的可能的和
            dp[j] = dp[j] + dp[j-coins[i]];
         }
      }
   }
	//4.返回结果
   return dp[len];
}

LeetCode 322. 零钱兑换

分析:
class Solution {   public int coinChange(int[] coins, int amount) {      int len = amount;      //1.创建数组,保存数据,最后会到达amount因此数组容量+1      int[] dp = new int[len+1];		//2.已知条件,初始赋值      for (int i = 0; i<=len; i++) {         dp[i] = 65535;      }      //价值为的时候没有组成的可能性,题目要求      dp[0] = 0;		//3.自底向上,扩展数组      for(int i=0; i<coins.length; i++){         for(int j=1; j<=len; j++){            if(j >= coins[i]){               //只有大于当前硬币的值才进行               dp[j] = Math.min(dp[j],1+dp[j-coins[i]]);            }         }      }		//4.返回结果,如果值为65535说明没有组合的情况返回-1      return dp[len]==65535?-1:dp[len];   }}

总结:

  • 一定要动态的扩展物品和背包的容量
  • 可以用二维数组
  • 一些可以动态加入物品和动态扩展背包容量的题都可以用动态规划尝试解答
  • 自底向上,获取有用数据,不断保存,需要时使用
  • 和原有、没有加入新物品时的背包进行比较,得到最优解
举报

相关推荐

0 条评论