前期基础:
(一)关键五步
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& 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