动态规划分析(粗)

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);
}
问题:

当我们需要求得数列中的某个数的时候,都会进行两次递归,但是其实在递归过程中有很多数是已经求得过的。

比如:求数列中的第5个数,首先会进入方法,然后执行recur(4) + recur(3),在执行recur(4)的时候会执行recur(3) + recur(2)。这时我们就可以发现,其实recur(3),是前面已经求过一次的,但是我们没有对这个求得的数据进行保存,因此会再重新算一次(递归一次),这个执行是重复的。这就是重复子问题。

小结:
  • 因此我们可以得出,对于一些问题,我们需要通过某种方式来得到数据,并且我们将这些已经求得数据保存起来,当我们需要的时候直接使用,那么我们就可以减少不必要的运算执行。
  • 并且我们可以看到斐波那契数列中除了开头的两个,后面的每一个数字的值只和它前面的两个数字有关。(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开始的,但是我们的数组的下标是从0开始的(编程语言默认数组从0开始),所以我们创建的数组长度为i,并且数组i-1就是最后找的数组中的数值。 关于动态转移方程的设置,我认为的是在找寻动态转移方程的时候,不能局限于当前已有的数据,而是找到一个比较靠后的位置,来分析找寻规律,这个结果是通过什么得到的,如果这个结果可以通过前面已得到数据来求得,那么就可以使用动态规划解题。 对于斐波那契数列,我们就可以自己在纸上将数列的前几个数据写出来,然后分析比较靠后的数值,然后来发现规律找到最后的数值是通过前面两个的和得到(当然这道题,本身题目就已经给了转移方程,如果没有的时候,我们就可以站的高一点,来找寻规律),最后设置动态规划转移方程,这个还是需要多练,多看。 转移方程一定要在比较高的位置开始看,容易发现规律。

1.3 题目练习

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

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

分析:

根据题目,初始条件就是,1个台阶的时候,只有一种跳法;

2个台阶的时候,可以每次跳1个台阶,也可以一次跳2个台阶,共两种跳法;

对于后面的从第3级台阶开始,我们就可以看成,是在它的前一个台阶跳1个台阶或者在它前面两个台阶处一次性跳2个台阶。

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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值