LeetCode509.斐波那契数列
1.思路
动态规划
斐波那契数列这道题算是最简单的动态规划的题了。接下来通过这道题来掌握
动态规划五部曲:
1.确定dp数组及下标的含义:dp[i] -> 第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],所以遍历顺序一定是从前向后的;
5.举例推导dp数组:
当N为10的时候,dp数组应该是这样的数列: 0 1 1 2 3 5 8 13 21 34 55 ,如果代码写出来,发现结果不对,就把dp数组打印出来看看和我们推导的数列是不是一致的。
cpp
class Solution {
public:
int fib(int n) {
if(n<=1) return n;
vector<int>dp(n+1);
dp[0]=0;
dp[1]=1;
for(int i=2;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[n];
}
};
优化
这里其实只需要维护两个数值,所以不需要记录整个序列,就只需要定义两个变量并记录最后的值即可。
cpp
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];
}
};
递归
递归的方法是最常见的。
cpp
class Solution {
public:
int fib(int n) {
if(n<=1) return n;
return fib(n-1)+fib(n-2);
}
};
2.复杂度分析
时间复杂度:O(n) - 动规、优化,O(2^n) - 递归
空间复杂度:O(n) - 动规、递归,O(1) - 优化
3.思考
这道题虽然很简单,但确是掌握动态规划的入门第一步,我们要从中熟悉并掌握动态规划题目的解题方法思想。
4.Reference:509. 斐波那契数 | 代码随想录
LeetCode70.爬楼梯
1.思路
这道题需要多举几个例子,例如爬到第一层楼梯有一种方法,爬到第二层有两种方法,爬到第三次有三种方法,依次类推。可以得知,爬到第三层的方法由爬到第二层和爬到第一层的方法推导出来。
动态规划五部曲:
-
dp[i]:爬到第 i 层楼梯有 dp[i] 种方法;
-
要到达 dp[i] ,可以从 dp[i-1] 处跳一步,也可以从 dp[i-2] 处跳两步,由此可知,dp[i] = dp[i-1] + dp[i-2];
-
爬到第一层有 1 种方法,爬到第二层有 2 种方法,所以 dp[1] = 1,dp[2] = 2 。这里不需要从 0 开始初始化,因为爬到第 0 层的方法为 0 ,而且 dp[i] 表示的是爬到第 i 层的方法数,如果从 0 开始初始化的话意义就不是那么明显,并且题目说了 n 是一个正整数,所以从 0 开始初始化压根就没意义;
-
由递归公式可知,dp[i] 依赖于 dp[i-1]、dp[i-2],所以从前往后遍历;
-
依旧举例比较,当 n 为 5 的时候,dp数组应该是这样的数列:1 2 3 5 8 ;
cpp
// 版本一
class Solution {
public:
int climbStairs(int n) {
if (n <= 1) return n;
vector<int> dp(n + 1);
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= n; i++) { // 注意i是从3开始的
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
};
cpp
// 版本二
class Solution {
public:
int climbStairs(int n) {
if (n <= 1) return n;
int dp[3];
dp[1] = 1;
dp[2] = 2;
for (int i = 3; i <= n; i++) {
int sum = dp[1] + dp[2];
dp[1] = dp[2];
dp[2] = sum;
}
return dp[2];
}
};
2.复杂度分析
时间复杂度:O(n)
空间复杂度:O(n) - 方法一,O(1) - 方法二
3.思考
这道题和斐波那契数列那道题的递推公式是一样的,但是如果是第一次做还是有难度的,因为这里的递推公式得自己去推,dp数组的初始化也得思考一下是否有意义。
4.Reference:70. 爬楼梯 | 代码随想录
LeetCode746.使用最小花费爬楼梯
1.思路
题目说的可以自行选择下标 0 或下标 1 的台阶开始爬楼梯,也就相当于跳到下标 0 或 1 是不消耗体力的。
动态规划五部曲:
-
dp[i]:到达第 i 个台阶所需要的最小体力;
-
dp[i] 可由 dp[i-1] 跳到 dp[i],此时花费 dp[i-1]+cost[i-1];也可以由 dp[i-2] 跳到 dp[i],此时花费 dp[i-2] + cost[i-2],由于 dp[i] 是到第 i 个台阶的最小体力,所以 dp[i] = min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]) ;
-
由题目可知,跳到下标 0 或下标 1 是不消化体力的,所以可以初始化 dp[0]=0,dp[1]=0;
-
由递推公式可知是从前往后遍历的;
-
拿示例2:cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1] ,dp = [0,0,1,2,2,3,3,4,4,5,6] ;
cpp
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
vector<int>dp(cost.size()+1);
dp[0]=0;
dp[1]=0;
for(int i=2;i<=cost.size();i++){
dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
}
return dp[cost.size()];
}
};
优化
cpp
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
int dp0=0;
int dp1=0;
for(int i=2;i<=cost.size();i++){
int sum=min(dp0+cost[i-2],dp1+cost[i-1]);
dp0=dp1;
dp1=sum;
}
return dp1;
}
};
2.复杂度分析
时间复杂度:O(n)
空间复杂度:O(n),O(1) - 优化
3.思考
这道题较前两到题就上难度了,因为不是单纯的由前两个 dp 数组的状态相加得到的,这里还需要加上从当前位置起跳的花费 cost[i],最终还要存最小值,所以状态转移方程就很难想到;其次是 dp 数组的初始化,这里必须要读懂题目,可以任意选择从 0 或 1 位置开始走,就说明跳到 0 或 1 是不消耗体力的。