使用最小花费爬楼梯(easy)
给你一个整数数组 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
解题思路一
这道题比较容易踩坑的就是这个楼顶到底是在哪里❓❓❓
其实数组内的每⼀个下标 [0, n - 1] 标识的都是楼层,而 顶楼的位置其实是在 n 的位置,这点非常重要!!!
所以我们就必须 要把数组开辟到 dp[n] 的位置,这和后面的解题思路二是不太一样的!
接下来就是按我们的动态规划套路走了,解题思路一主要考虑的是以 i 为终点的状态表示!
首先就是确定 dp[i] 的状态表示什么,因为这道题说要求到达顶层花费的最小费用,那么很明显,dp[i] 就可以表示到达 i 层的时候花费的最小费用,注意是最小费用哦!
接着就是状态转移方程,思想如下:
- 用之前或者之后的状态,推导出 dp[i] 的值。
- 根据最近的一步来划分问题。
题目说每次可以跨越两步,也就是说到达第 i 层的只能是通过 i-1 或者 i-2 层来到达,所以我们求解 dp[i] 肯定是和 dp[i-1] 和 dp[i-2] 有关系!
对的,先说一下从 i-1 层到 i 层的关系,很明显,dp[i-1] 表示到达 i-1 层花费的最小费用,那么现在到达 i 层,只需要加上 i-1 花费的费用,也就是 cost[i-1] 即可!对于 i-2 层也是如此,接着既然要求最小花费,那么我们只需要在这两个到达 i 层费用中取那个最小费用的那个即可!
所以状态转移方程:dp[i] = min( dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])
然后就是初始化问题,因为填 dp 表过程中,我们是不需要知道第 0 层和第 1 层的,它们其实默认为 0,表示为起跳层,花费 0 元,所以我们将 dp[0] 和 dp[1] 赋值为 0 即可!
接着就是填表的顺序,因为我们是通过 i-1 层和 i-2 层来推出 i 层的,所以 填表顺序是从左往右的!
最后就是返回值,很明显就是最后推出来的 dp[n],这和等会要讲的解题思路二不太一样!
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
// 创建dp表,dp[i]表示到达该层花费最小的费用
int n = cost.size();
vector<int> dp(n + 1);
// 初始化,由题意可知0和1层开始爬楼梯,所以不需要支付费用
// 但其实vector已经帮我们默认初始化为0了,所以这里也可以不去初始化
dp[0] = dp[1] = 0;
// 填表
for(int i = 2; i <= n; ++i)
{
// 因为每次只能胯两步,所以要求前两步中花费最小的那步
dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]);
}
return dp[n];
}
};
解题思路二
题解二相当于是题解一的逆版本,也就是说我们要从后往前去推导出结果!
其实最关键的就是这个状态表示的改变,这次 dp[i] 表示的是从第 i 层到达楼顶所需要花的最小费用,这和题解一表示的达到第 i 层花费的最小费用是有区别的!因为状态这样子表示了,那么我们要求到达楼顶花费的最小费用,并且我们是从第 0 层和第 1 层出发的,那么意思就是我们 要求 dp[0] 和 dp[1] 中小的那个,对不对❗ 这其实也顺带的看出来了我们最后的返回值,就是 min(dp[0], dp[1])。
因为最后是通过 dp[0] 和 dp[1] 来获得最小费用,所以 没必要创建 n+1 个空间。
然后就是状态转移方程,既然我们最后要求的是 dp[0] 和 dp[1],那么势必要从 后往前推导,这里以 dp[i] 为例,它表示从第 i 层出发到楼顶最小费用,那么其实我们就能想到,第 i 层是可以向后跨越一步或者两步的,那么也就是说第 i+1 层和 i+2 层的费用是会影响到第 i 层的,因为我们要取的是最小费用,所以我们只需要取到 i+1 和 i+2 层中到达楼顶花的费用最小的那个,再加上当前第 i 层跳跃到 i+1 层或者 i+2 层需要花费的费用,就等于从第 i 层到达楼顶需要花的最小费用了!
也就是说状态转移方程是这样子的:dp[i] = min(dp[i+1], dp[i+2]) + cost[i]
其次填表的顺序是从后往前推导,这个我们是知道的,但是这个时候我们就要注意一点细节了,就是初始化的问题,因为要从后往前推,那么我们就得先将最后面的值进行初始化!
因为每次最多跨越两步,所以我们其实是可以先确定后两层到楼顶的最小费用的!为什么呢,因为 n-1 层到楼顶就是一步的距离,直接跳过去是最小费用的,而对于 n-2 层来说,没必要先跳到 n-1 层,因为可以一次跳两步,直接跳到楼顶即是最小费用!
所以这两层花费的最小费用其实就是它们这一跳的费用!即 dp[n-1] = cost[n-1],dp[n-2] = cost[n-2]
。
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
// 创建dp表,dp[i]表示从该层出发,到达楼顶需要花费的最小费用,注意和题解一区分开!!!
// 并且我们是从i-1层出发从右往左推导,所以不需要创建n+1个空间
int n = cost.size();
vector<int> dp(n);
// 初始化,因为我们是从右往左推导,所以要先初始化的是最后面两层
// 并且最后两层都有一个特点,它们到达楼顶的最小花费就是直接跳到楼顶,不需要计算其它的
// 尤其是要理解dp[n-2],它没必要说跳到n-1再去跳到楼顶,直接跳到楼顶即可!
dp[n-1] = cost[n-1];
dp[n-2] = cost[n-2];
// 填表,顺序是从右往左
for(int i = n - 3; i >= 0; --i)
{
// 每次取下一点中花费最小的一点
dp[i] = min(dp[i+1], dp[i+2]) + cost[i];
}
// 返回值要求的不是楼顶,而是前两层dp表中的最小值
// 因为dp[i]表示从该层出发到楼顶花费的最小费用,和题解一不一样!
return min(dp[0], dp[1]);
}
};