提要
在豆包MarsCode AI刷题中,有一道题目是有限制的楼梯攀登 - MarsCode。这个名称很容易让人联想到一道经典的动态规划题——爬楼梯(70. 爬楼梯 - 力扣(LeetCode))。对于没接触过动态规划的小白来说,先去理解题目的原型,会对之后解决变形题目提供很大的思路帮助。
爬楼梯题解
一、题目
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
二、思路
爬到第一层楼梯只有一种方法:走一步。
爬到第二层有两种方法:走一步再走一步 ,或者走两步。
爬到第三层有三种方法:一步一步一步,一步两步,或者两步一步。
这是我们很容易推断出来的,但是随着楼梯的层数增加,惯式思维很难计算出接下来的方法数。
那么我们不妨转换一下思路,重新去推导爬到第三层的方法:
- 从第一层楼梯走两步。
- 从第二层楼梯走一步。
所以,爬到第三层的方法数实际上是爬到第一层的方法数+爬到第二层的方法数:一共1+2=3种。
接着推导爬到第四层:
- 从第二层楼梯走两步。
- 从第三层楼梯走一步。
据此推断,爬到第n层:
- 从第n-2层楼梯走两步。
- 从第n-1层楼梯走一步。
爬到第n层的方法数=爬到第n-2层的方法数+爬到第n-1层的方法数
我们发现题目中的每一个当前状态,取决于之前的状态。同时,当前状态又会决定之后的状态。这其实就是动态规划题型的最大特点。
假设爬到第0层的方法数为1,那么我们可以试着列举爬到每一层楼梯的方法数:
1、1、2、3、5、8、13……
这样一段数列其实就是斐波那契数列。
三、代码部分
- 我们先定义一个一维数组dp[i],其含义为爬到第i层楼梯,共有dp[i]种方法。
- 写出递推公式。由于我们之前推断出:
爬到第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];
}