从斐波那契数列开始学动态规划| 豆包MarsCode AI刷题

177 阅读4分钟

提要

在豆包MarsCode AI刷题中,有一道题目是有限制的楼梯攀登 - MarsCode。这个名称很容易让人联想到一道经典的动态规划题——爬楼梯(70. 爬楼梯 - 力扣(LeetCode))。对于没接触过动态规划的小白来说,先去理解题目的原型,会对之后解决变形题目提供很大的思路帮助。

爬楼梯题解

一、题目

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

二、思路

爬到第一层楼梯只有一种方法:走一步。

爬到第二层有两种方法:走一步再走一步 ,或者走两步。

爬到第三层有三种方法:一步一步一步,一步两步,或者两步一步。

这是我们很容易推断出来的,但是随着楼梯的层数增加,惯式思维很难计算出接下来的方法数。

那么我们不妨转换一下思路,重新去推导爬到第三层的方法:

  1. 从第一层楼梯走两步。
  2. 从第二层楼梯走一步。

所以,爬到第三层的方法数实际上是爬到第一层的方法数+爬到第二层的方法数:一共1+2=3种。

接着推导爬到第四层:

  1. 从第二层楼梯走两步。
  2. 从第三层楼梯走一步。

据此推断,爬到第n层:

  1. 从第n-2层楼梯走两步。
  2. 从第n-1层楼梯走一步。

爬到第n层的方法数=爬到第n-2层的方法数+爬到第n-1层的方法数

我们发现题目中的每一个当前状态,取决于之前的状态。同时,当前状态又会决定之后的状态。这其实就是动态规划题型的最大特点

假设爬到第0层的方法数为1,那么我们可以试着列举爬到每一层楼梯的方法数:

1、1、2、3、5、8、13……

这样一段数列其实就是斐波那契数列

三、代码部分

  1. 我们先定义一个一维数组dp[i],其含义为爬到第i层楼梯,共有dp[i]种方法。
  2. 写出递推公式。由于我们之前推断出:

爬到第n层的方法数=爬到第n-2层的方法数+爬到第n-1层的方法数

据此很容易写出:dp[i]=dp[i-2]+dp[i-1]

3. 进行dp数组的初始化

我们可以让dp[0]=1并且dp[1]=1,从i=2时开始遍历。当然,由于本题中i=0其实没有意义,从便于理解的角度出发,我们也可以让dp[1]=1并且dp[2]=2,从i=3时开始遍历。

完整代码展示:

public int solution {
         int[] dp = new int[n + 1];
         dp[0] = 1;
         dp[1] = 1;
         for (int i = 2; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }

兔子数列与兔群繁殖之谜

豆包MarsCode AI刷题中,还有一道题目是兔群繁殖之谜 - MarsCode

如果仔细研究一下题目就会发现,每个月末兔子的总对数也是一个斐波那契数列。

其实当年提出兔子繁殖问题的就是斐波纳契,而后人为了纪念,就将这个兔子数列称为斐波那契数列。

而这道题的解题思路和爬楼梯几乎一模一样,只是需要注意处理一下当A=1时,dp数组越界的情况,故这里不过多赘述。

有限制的楼梯攀登

最后回到开头提到的这道题,与“爬楼梯”相比,这道题多了一个“不能连续走两步”的限制条件。那么我们想推断爬到下一层的方法数时,不仅要知道之前层数的方法数,还要知道到达之前层数是走了1步还是2步。所以我们需要引入二维数组dp[i][j],单独储存到达第i层的时候是走了1步还是2步的这个状态。

写递推公式时其实有点绕,需要一点反向思考,直接看代码应该能够理解。

    public static int solution(int n) {
        //特殊情况的边界处理
        if(n==1) return 1;
        if(n==2) return 2;
        
        //行号:表示到达第i层
        //列号:表示最后一步是走1步还是2步
        int[][] dp=new int [n+1][2];
        
        //设置初始状态
        dp[1][0]=1;
        //dp[1][1]=0;表示不可以走2步到第一层,可以不写,java中int数组初始化的值默认为0
        dp[2][0]=1;
        dp[2][1]=1;

        for(int i=3;i<=n;i++){
            //最后一步是走1步
            dp[i][0]=dp[i-1][0]+dp[i-1][1];
            //最后一步是走2步
            dp[i][1]=dp[i-2][0];
        }
        
        return dp[n][0]+dp[n][1];
    }