_33LeetCode代码随想录算法训练营第三十三天-动态规划| 509.斐波那契数、70.爬楼梯、746.使用最小花费爬楼梯
题目列表
- 理论基础
- 509.斐波那契数
- 70.爬楼梯
- 746.使用最小花费爬楼梯
理论基础
代码随想录地址:https://programmercarl.com/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html#%E4%BB%80%E4%B9%88%E6%98%AF%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92
动态规划大纲
什么是动态规划
动态规划,英文:Dynamic Programming,简称DP,如果某一问题有很多重叠子问题,使用动态规划是最有效的。
所以动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的,
动态规划五部曲——超级重要
每一步都要想清楚。
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
509.斐波那契数
代码随想录地址:
https://programmercarl.com/0509.%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0.html
题目
斐波那契数 (通常用 F(n)
表示)形成的序列称为 斐波那契数列 。该数列由 0
和 1
开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定 n
,请计算 F(n)
。
示例 1:
输入:n = 2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1
示例 2:
输入:n = 3
输出:2
解释:F(3) = F(2) + F(1) = 1 + 1 = 2
示例 3:
输入:n = 4
输出:3
解释:F(4) = F(3) + F(2) = 2 + 1 = 3
提示:
0 <= n <= 30
思路
有三种思路:
- 使用动态规划
- 常识思想
- 递归
代码
动态规划代码
- 时间复杂度:O(n)
- 空间复杂度:O(n)
/*
* @lc app=leetcode.cn id=509 lang=cpp
*
* [509] 斐波那契数
*/
// @lc code=start
class Solution {
public:
int fib(int n) {
if(n <= 1) return n;
//理解dp的含义
vector<int> dp(n + 1, 0);
//初始化dp
dp[0] = 0;
dp[1] = 1;
//从左到右遍历
for(int i = 2; i <= n; i++)
{
dp[i] = dp[i - 1] + dp[i - 2];
}
//返回需要的值
return dp[n];
}
};
// @lc code=end
常规思路代码(动态规划的优化)
- 时间复杂度:O(n)
- 空间复杂度:O(1)
/*
* @lc app=leetcode.cn id=509 lang=cpp
*
* [509] 斐波那契数
*/
// @lc code=start
class Solution {
public:
int fib(int n) {
if(n <= 1) return n;
//理解dp的含义
vector<int> dp(3, 0);
//初始化dp
dp[0] = 0;
dp[1] = 1;
//从左到右遍历
for(int i = 2; i <= n; i++)
{
dp[2] = dp[1] + dp[0];
dp[0] = dp[1];
dp[1] = dp[2];
}
//返回需要的值
return dp[2];
}
};
// @lc code=end
递归思路代码
- 时间复杂度:O(2^n)(此处很迷糊)
- 空间复杂度:O(n),算上了编程语言中实现递归的系统栈所占空间
/*
* @lc app=leetcode.cn id=509 lang=cpp
*
* [509] 斐波那契数
*/
// @lc code=start
class Solution {
public:
int fib(int n) {
if(n < 2) return n;
return fib(n - 1) + fib(n - 2);
}
};
// @lc code=end
70.爬楼梯
代码随想录地址:https://programmercarl.com/0070.%E7%88%AC%E6%A5%BC%E6%A2%AF.html
题目
假设你正在爬楼梯。需要 n
阶你才能到达楼顶。
每次你可以爬 1
或 2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例 2:
输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
提示:
1 <= n <= 45
思路
不考虑0层为1种方法还是0种方法。
爬到第一层楼梯有一种方法,爬到二层楼梯有两种方法。
那么第一层楼梯再跨两步就到第三层 ,第二层楼梯再跨一步就到第三层。
所以到第三层楼梯的状态可以由第二层楼梯 和 到第一层楼梯状态推导出来。
其实就是斐波拉契数列。
代码
动态规划思路代码
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( n ) O(n) O(n)
/*
* @lc app=leetcode.cn id=70 lang=cpp
*
* [70] 爬楼梯
*/
// @lc code=start
class Solution {
public:
int climbStairs(int n) {
if(n <= 2)
return n;
//dp[i]的含义为爬到第i层有多少种方式
vector<int> dp(n + 1, 0);
dp[1] = 1;
dp[2] = 2;
//从1层两步爬到3层有dp[1]种方法,从2层一步爬到3层有dp[2]种方法
for(int i = 3; i <= n; i++)
dp[i] = dp[i - 1] + dp[i - 2];
return dp[n];
}
};
// @lc code=end
常规思路代码(动态规划的优化)
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( 1 ) O(1) O(1)
/*
* @lc app=leetcode.cn id=70 lang=cpp
*
* [70] 爬楼梯
*/
// @lc code=start
class Solution {
public:
int climbStairs(int n) {
if(n <= 2)
return n;
//dp[i]的含义为爬到第i层有多少种方式
vector<int> dp(3, 0);
dp[0] = 1;
dp[1] = 2;
//从1层两步爬到3层有dp[1]种方法,从2层一步爬到3层有dp[2]种方法
for(int i = 3; i <= n; i++)
{
dp[2] = dp[1] + dp[0];
dp[0] = dp[1];
dp[1] = dp[2];
}
return dp[2];
}
};
// @lc code=end
746.使用最小花费爬楼梯
代码随想录地址:https://programmercarl.com/0746.%E4%BD%BF%E7%94%A8%E6%9C%80%E5%B0%8F%E8%8A%B1%E8%B4%B9%E7%88%AC%E6%A5%BC%E6%A2%AF.html
题目
给你一个整数数组 cost
,其中 cost[i]
是从楼梯第 i
个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
你可以选择从下标为 0
或下标为 1
的台阶开始爬楼梯。
请你计算并返回达到楼梯顶部的最低花费。
示例 1:
输入:cost = [10,15,20]
输出:15
解释:你将从下标为 1 的台阶开始。
- 支付 15 ,向上爬两个台阶,到达楼梯顶部。
总花费为 15 。
示例 2:
输入:cost = [1,100,1,1,1,100,1,1,100,1]
输出:6
解释:你将从下标为 0 的台阶开始。
- 支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
- 支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
- 支付 1 ,向上爬一个台阶,到达楼梯顶部。
总花费为 6 。
提示:
2 <= cost.length <= 1000
0 <= cost[i] <= 999
思路
dp数组的含义:dp[i]表示到达下标为i的台阶的最小花费为dp[i]
递推公式:dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])
代码
- 时间复杂度:O(n)
- 空间复杂度:O(n)
/*
* @lc app=leetcode.cn id=746 lang=cpp
*
* [746] 使用最小花费爬楼梯
*/
// @lc code=start
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
//dp数组的含义:dp[i]表示到达下标为i的台阶的最小花费为dp[i]
//将dp[0]和dp[1]初始化为0,根据题意来的
vector<int> dp(cost.size() + 1, 0);
//递推公式:dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])
//题意说了只需要两步
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()];
}
};
// @lc code=end
优化:
- 时间复杂度:O(n)
- 空间复杂度:O(1)
/*
* @lc app=leetcode.cn id=746 lang=cpp
*
* [746] 使用最小花费爬楼梯
*/
// @lc code=start
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
//dp数组的含义:dp[i]表示到达下标为i的台阶的最小花费为dp[i]
//将dp[0]和dp[1]初始化为0,根据题意来的
vector<int> dp(3, 0);
//递推公式:dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])
//题意说了只需要两步
for(int i = 2; i <= cost.size(); i++)
{
//一步消费小就选一步,两步消费小就选两步
dp[2] = min(dp[1] + cost[i - 1], dp[0] + cost[i - 2]);
dp[0] = dp[1];
dp[1] = dp[2];
}
return dp[2];
}
};
// @lc code=end