「这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战」
746. 使用最小花费爬楼梯
给你一个整数数组 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 。
解法一
动态规划。
思路
牢记动态规划三要素
1. 状态定义
2. 状态转移方程
3. 边界状态
这一题,根据题目的条件分析,到达楼梯顶部,并不是站在最后一节台阶,而是站在最后一节台阶的下一节台阶,虽然不怎么合理,但是题目这么出了,我们就这么来。
- 状态定义
- 那么我们定义dp数组,dp[i]就代表我站在下标为i的台阶上,所需要花费的费用,cost数组长度为n,那么最后一项的下标为n-1,那么站在楼顶的就是dp[n]所对应的花费。
- 状态转移方程
- dp[i],要么是从dp[i-1]爬上来的,花费为dp[i-1]+cost[i-1],即爬上i-1台阶的费用加上往上继续爬的费用。
- 要么是从dp[i-2]爬上来的,花费为dp[i-2]+cost[i-2]。
- 即dp[i] = Math.min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2])
- 边界状态
- 题目给出了,我们可以从第0 或者 第1级台阶开始爬,那么dp[0]和dp[1]就都应该等于0,继续分析dp[2]就是取这两个里更小的一个了,可以开始根据状态转移方程推了。
那么分析到这里,代码就可以水到渠成的写出来了。
代码
/**
* @param {number[]} cost
* @return {number}
*/
var minCostClimbingStairs = function(cost) {
let n = cost.length;
let dp = [];
dp[0] = dp[1] = 0;
for(let i=2;i<n+1;i++){
dp[i] = Math.min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])
}
// 这题,数组长度是n,那么最后一个台阶的下标为n-1,这题定义的楼顶为最后一节台阶的下一级,那么就是n
// 所以我们就是要求站在下标为n的台阶上的最小花费
return dp[n]
};
复杂度分析
时间复杂度:O(n), 遍历n-2次即可,故为O(n)。
空间复杂度:O(n), dp需要存在每个台阶的爬上来的花费值,故长度为n,所以为O(n)。
解法二
动态规划 + 滚动数组。
思路
这个解法其实是解法一的优化版本。解法一的空间复杂度是有优化空间的。
其实我们每次求dp[i],它只与dp[i-1]和dp[i-2]相关,那么我们其实只需要三个变量,pre保存上一节台阶的花费,cur保存当前台阶的花费,next保存下一节台阶的花费。
在遍历的过程中,对这三个值滚动赋值。
代码
/**
* @param {number[]} cost
* @return {number}
*/
var minCostClimbingStairs = function(cost) {
let n = cost.length;
let cur = 0;
let pre = 0;
for(let i=2;i<n+1;i++){
let next = Math.min(cur+cost[i-1],pre+cost[i-2]);
pre = cur;
cur = next;
}
// 这题,数组长度是n,那么最后一个台阶的下标为n-1,这题定义的楼顶为最后一节台阶的下一级,那么就是n
// 所以我们就是要求站在下标为n的台阶上的最小花费
return cur;
};
复杂度分析
时间复杂度:O(n),同上。
空间复杂度:O(1),只要3个变量保存三个状态对应的值,故为常量O(1)。