目录
LeetCode509.斐波那契数列
斐波那契数 (通常用
F(n)
表示)形成的序列称为 斐波那契数列 。该数列由0
和1
开始,后面的每一项数字都是前面两项数字的和。也就是:F(0) = 0,F(1) = 1 F(n) = F(n - 1) + F(n - 2),其中 n > 1给定
n
,请计算F(n)
。
思路:斐波那契数列这个问题比较简单,状态转换方程给出了,并且初始化的值也给出,所以比较简单。
1. 动态规划
首先设置一个dp数组,dp[i]表示第i个数的大小,根据转换方程,在循环里面计算,最后返回最后一个元素即可。
int fib(int n) {
if(n <= 1) return n;
vector<int> dp(n + 1);//构造dp数组,dp[i]表示下标为F(i)的和
dp[0] = 0;
dp[1] = 1;//初始化第一个和第二个元素
for(int i = 2; i < n + 1; i ++){
dp[i] = dp[i - 1] + dp[i - 2];//状态转换方程
}
return dp[n];//返回所求的F(n)
}
时间复杂度:O(n)
空间复杂度:O(n)
当然,因为需要管理的就是两个数,可以不用使用数组管理所有元素,因此可以简化一下,使得空间复杂度减小,只维护一个大小固定的数组。
int fib(int n) {
if(n <= 1) return n;
int dp[2];//这里只维护了大小为2的数组
dp[0] = 0;
dp[1] = 1;
int sum = 0;
for(int i = 2; i < n + 1; i ++){
sum = dp[0] + dp[1];//更新数组元素
dp[0] = dp[1];
dp[1] = sum;
}
return dp[1];
}
时间复杂度:O(n)
空间复杂度:O(1)
2. 递归法
斐波那契数列当然可以使用递归法来求解,虽然代码简单,但是时间复杂度比较大。
int fib(int n) {
if(n < 2) return n;
return fib(n - 1) + fib(n - 2);
}
时间复杂度:O(2^n)
空间复杂度:O(n)
LeetCode70.爬楼梯
假设你正在爬楼梯。需要
n
阶你才能到达楼顶。每次你可以爬
1
或2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
思路:爬楼梯和斐波那契数列很像。
举个例子,首先对于第一阶台阶,有一种方法,对于到达二层台阶,有两种方法(迈一步、迈一步以及一次性迈两步两种情况)。
那么对于三层台阶呢?
可以看作是在一层台阶的基础上需要走两层台阶或者两层台阶的基础上需要走一层台阶,所以三层台阶的方法就有3种,为前面一层,两层台阶的和,写成公式也就是dp[i]=dp[i-1]+dp[i-2]!
这和斐波那契数列几乎完全一样。
基于此,那么代码也好写了。
注意这里我的初始值是从dp[1]开始,而没有从dp[0]开始,因为本身要求的就是dp[n],所以这样比较好理解,dp[1]就是爬一层台阶时的不同方法数,dp[2]就是爬两层台阶时不同方法数。
int climbStairs(int n) {
if(n <= 2) return n;
vector<int> dp(n + 1, 0);//初始化dp数组,dp[i]表示到达第i层楼有多少种方法
dp[1] = 1;
dp[2] = 2;
for(int i = 3; i < n + 1; i ++){
dp[i] = dp[i - 1] + dp[i - 2];//状态转移方程
}
return dp[n];//返回第n层楼的不同方法数
}
时间复杂度:O(n)
空间复杂度:O(n)
当然,同样可以对上面代码进行优化,使得只维护固定大小的数组即可。
int climbStairs(int n) {
if(n <= 2) return n;
int dp[3];
dp[1] = 1;
dp[2] = 2;
int sum = 0;
for(int i = 3; i < n + 1; i ++){
sum = dp[1] + dp[2];
dp[1] = dp[2];
dp[2] = sum;
}
return dp[2];
时间复杂度:O(n)
空间复杂度:O(1)
LeetCode746.使用最小花费爬楼梯
给你一个整数数组
cost
,其中cost[i]
是从楼梯第i
个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。你可以选择从下标为
0
或下标为1
的台阶开始爬楼梯。请你计算并返回达到楼梯顶部的最低花费。
思路:这里其实相对于前面来说就困难一点了。
这里提供两种解决思路。
首先dp数组设置为一个二维数组,dp[i][0]表示前面一个元素跳一步到达第i个位置时的最小花费,dp[i][1]表示前两个元素跳两步到达第i个位置时的最小花费,同时这里的初始化是设置了dp[1][0]=cost[0],因为对于第1个台阶来说,只有下标为0的台阶可以跳1步到下标为1的第一个台阶处,花费为cost[0],其他的就默认为0了。
对于转换方程,dp[i][0]就应该取它前面的i-1这个元素由它的前面元素跳1步或两步到达i-1位置时的最小花费加上i-1这个元素跳1步到达i这里时,两者中的最小值;同样对于dp[i][1]也是同样的道理。
可能有的人会有点读不懂上面这段话的意思,建议参考下面代码中的for循环中的转换方程看,体会体会,或者手动模拟一下,就基本上没问题了。
最后返回的是最后一个元素的dp[n][0]和dp[n][1]两者中的最小值即为最小花费。
int minCostClimbingStairs(vector<int>& cost) {
//dp[i][0]表示走1步到达第i个台阶后的前i个楼梯最小开销
//dp[i][1]表示走2步到达第i个台阶走的前i个楼梯最小开销
vector<vector<int>> dp(cost.size() + 1, vector<int>(2,0));//设置dp数组
dp[1][0] = cost[0];//初始化第0层台阶楼到第1层台阶走一步时的花费为cost[0]
for(int i = 2; i < cost.size() + 1; i ++){
//这里更新dp[i][0],因为是走1步,所以取它前面一个元素的最小开销dp[i-1][0]或者dp[i-1][1],然后加上到达i所需花费cost[i-1]
dp[i][0] = min(dp[i - 1][0] + cost[i - 1], dp[i - 1][1] + cost[i - 1]);
//这里更新dp[i][1],因为是走2步,所以必然和它第前两个元素有关,也就是下标为i-2的元素
//同样,这里也是取它的最小开销dp[i-2][0]或者dp[i-2][1],然后加上到达i需要花费的cost[i-2]
dp[i][1] = min(dp[i -2][0] + cost[i - 2], dp[i -2][1] + cost[i - 2]);
}
return min(dp[cost.size()][0], dp[cost.size()][1]);//当循环遍历完成后,取到达第n阶楼梯,也就是楼梯顶部时的最小花费返回
}
时间复杂度:O(n)
空间复杂度:O(n)
上面维持的是二维数组,其实我们可以尝试使用一维数组来解决。
采用一维数组其实更好理解,也更好想,所以推荐使用一维数组的解法。
dp[i]就表示到达第i阶楼梯时所需要的最小花费,因为题目中说可以从0或1出发,那么也就是说到达0和1时花费是没有的,所以初始化为dp[0]=0,dp[1]=0。
状态转换方程呢也是取的前面i-1元素的最小花费加上i-1跳到i层这里的花费以及前面i-2元素的最小花费加上i-2跳到i这里的花费中,二者取最小。
于是最后返回dp[n]最后一个元素即可。
int minCostClimbingStairs(vector<int>& cost) {
vector<int> dp(cost.size() + 1);//设置dp数组,dp[i]表示到达第i层阶梯时需要最小花费
dp[0] = 0;
dp[1] = 0;
for(int i = 2; i < cost.size() + 1; i ++){
dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);//这里取两者中的最小花费
}
return dp[cost.size()];
}
时间复杂度:O(n)
空间复杂度:O(n)
当然还是可以进行优化,只维持两个数,简化空间复杂度。
int minCostClimbingStairs(vector<int>& cost) {
int dp0 = 0;
int dp1 = 0;
for(int i = 2; i < cost.size() + 1; i ++){
int dpi = min(dp1 + cost[i - 1], dp0 + cost[i - 2]);
dp0 = dp1;
dp1 = dpi;
}
return dp1;
}
时间复杂度:O(n)
空间复杂度:O(1)
感谢你的阅读,希望我的文章能够给你帮助,如果有帮助,麻烦点赞加收藏,或者点点关注,非常感谢。
如果有什么问题欢迎评论区讨论!