代码随想录算法训练营第三十八天 | 509. 斐波那契数、70. 爬楼梯、746. 使用最小花费爬楼梯
前期基础:
(一)关键五步
509. 斐波那契数
视频讲解
主要思路:
1.确定dp数组以及下标的含义
dp[i]的定义为:第i个数的斐波那契数值是dp[i]
2.确定递推公式
dp[i] = dp[i - 1] + dp[i - 2];
3.dp数组如何初始化
dp[0] = 0;
dp[1] = 1;
4.确定遍历顺序
从递归公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,dp[i]是依赖 dp[i - 1] 和 dp[i - 2],那么遍历的顺序一定是从前到后遍历的
5.举例推导dp数组
按照这个递推公式dp[i] = dp[i - 1] + dp[i - 2],我们来推导一下,当N为10的时候,dp数组应该是如下的数列:
0 1 1 2 3 5 8 13 21 34 55
代码实现:
class Solution {
public:int fib(int n) {if(n <= 1) return n;int dp[2]; //只需要维护两个数就行,当前数与其前一个数dp[0] = 0;dp[1] = 1;for(int i = 2; i <= n; i++) {int sum = dp[0] + dp[1]; //根据递归公式求当前数dp[0] = dp[1]; //将原先的当前数前移为现在的前一位数dp[1] = sum; //将求和结果作为当前数}return dp[1];}
};
第一次写错误
还有可能出现只有一位的情况
70. 爬楼梯
视频讲解
主要思路:
与上一题菲波那切数列基本一致,关键在于发现递推公式
1.确定dp数组以及下标的含义
dp[i]: 爬到第i层楼梯,有dp[i]种方法
2.确定递推公式
如何可以推出dp[i]呢?
从dp[i]的定义可以看出,dp[i] 可以有两个方向推出来。
首先是dp[i - 1],上i-1层楼梯,有dp[i - 1]种方法,那么再一步跳一个台阶不就是dp[i]了么。
还有就是dp[i - 2],上i-2层楼梯,有dp[i - 2]种方法,那么再一步跳两个台阶不就是dp[i]了么。
那么dp[i]就是 dp[i - 1]与dp[i - 2]之和!
所以dp[i] = dp[i - 1] + dp[i - 2] 。
在推导dp[i]的时候,一定要时刻想着dp[i]的定义,否则容易跑偏。
这体现出确定dp数组以及下标的含义的重要性!
3.dp数组如何初始化
在回顾一下dp[i]的定义:爬到第i层楼梯,有dp[i]中方法。
那么i为0,dp[i]应该是多少呢,这个可以有很多解释,但基本都是直接奔着答案去解释的。
例如强行安慰自己爬到第0层,也有一种方法,什么都不做也就是一种方法即:dp[0] = 1,相当于直接站在楼顶。
但总有点牵强的成分。
那还这么理解呢:我就认为跑到第0层,方法就是0啊,一步只能走一个台阶或者两个台阶,然而楼层是0,直接站楼顶上了,就是不用方法,dp[0]就应该是0.
其实这么争论下去没有意义,大部分解释说dp[0]应该为1的理由其实是因为dp[0]=1的话在递推的过程中i从2开始遍历本题就能过,然后就往结果上靠去解释dp[0] = 1。
从dp数组定义的角度上来说,dp[0] = 0 也能说得通。
需要注意的是:题目中说了n是一个正整数,题目根本就没说n有为0的情况。
所以本题其实就不应该讨论dp[0]的初始化!
所以:不考虑dp[0]如何初始化,只初始化dp[1] = 1,dp[2] = 2,然后从i = 3开始递推,这样才符合dp[i]的定义。
4.确定遍历顺序
从递推公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,遍历顺序一定是从前向后遍历的
5.举例推导dp数组
代码实现:
class Solution {
public:int climbStairs(int n) {if(n <= 2) return n;int dp[2]; //dp[i]表示爬到第i阶台阶有几种方法,不过这里与斐波那契数列一样,只用维护两个就行dp[0] = 1; dp[1] = 2;for(int i = 3; i <= n; i++) {int sum = dp[0] + dp[1];dp[0] = dp[1];dp[1] = sum;}return dp[1];}
};
746. 使用最小花费爬楼梯
视频讲解
主要思路:
(1)dp数组及下标含义:跳到第i个楼梯所需最小体力值
(2)递推公式:因为一次能跳1或2个台阶,所以dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]
(3)初始化:可以从第0个或第1个台阶开始跳,所以初始化dp[0]与dp[1]为0
(4)遍历顺序:从前往后
易错点:
(1)i表示台阶序号,最后顶楼的序号是cost.size()
代码实现:
class Solution {
public:int minCostClimbingStairs(vector<int>& cost) {if(cost.size() <= 1) return 0;int dp[2]; //dp数组中记录的是跳到当前位置所需的最小体力值//空间上的优化,因为只用记录要跳到当前位置前两个位置即可dp[0] = 0; dp[1] = 0;for(int i = 2; i <= cost.size(); i++) { //i <= cost.size()是因为i表示跳到i这个位置,最后的顶楼位置下标也就是cost数组长度int next = min(dp[0] +cost[i - 2], dp[1] + cost[i - 1]);dp[0] = dp[1];dp[1] = next;}return dp[1];}
};
第一次写错误
(1) cost数组大小为1时才直接返回0