(力扣—动态规划)使用最小花费爬楼梯
说明
数组的每个索引做为一个阶梯,第 i个阶梯对应着一个非负数的体力花费值 cost[i] (索引从0开始)。
每当你爬上一个阶梯你都要花费对应的体力花费值,然后你可以选择继续爬一个阶梯或者爬两个阶梯。
您需要找到达到楼层顶部的最低花费。在开始时,你可以选择从索引为 0 或 1 的元素作为初始阶梯。
示例 1:
输入: cost = [10, 15, 20]
输出: 15
解释: 最低花费是从cost[1]开始,然后走两步即可到阶梯顶,一共花费15。
示例 2:
输入: cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1] 输出: 6
解释: 最低花费方式是从cost[0]开始,逐个经过那些1,跳过cost[3],一共花费6。
注意:
cost 的长度将会在 [2, 1000]。
每一个 cost[i] 将会是一个Integer类型,范围为 [0, 999]。
错误思想
这道题不禁让人联想到斐波那契数列,估计很多人自然而然想到了一个dp方程:
f[i] = min(f[i - 1], f[i - 2] + cost[i])
意思是 当前阶最小值是处于上一阶时迈两阶(不经过当前这一阶)与前一阶加上这一阶和 这两者的最小值为到当前阶耗费的最小体力。
这种想法真的看着非常合理,如果处于上一阶那肯定不经过此阶最小,如果处于前一阶想到此阶肯定只能迈两阶到当前阶最小,然后两者比较一下取最小值。
但是这是错误的,原因有两点:
1. 如果此阶台阶不是最后一阶且最小值恰好取上一阶的情况,就造成了在此阶台阶的最小值不会加上此阶的体力。如果紧接着在下一阶上楼时用到了这一阶的最小值(把这一阶台阶命名为a 方便叙述),就会出现一个荒谬的错误点—从a台阶上楼但不经过a台阶再跨越一阶台阶,相当于飞了两阶台阶上去,这里解释的可能有些抽象,下面举个例子
假如体力是0 0 1 1
四阶台阶命名为a b c d
如果要求d台阶的最小体力值,那么根据方程就知道是c台阶的值与b台阶的值加上d台阶的值两者取最小
按照这种方程思想,到达c台阶的最小体力值是0,然后看d台阶,最小体力值此时肯定是从c上来的,也就是0.
但是再看看体力值,无论怎么上台阶都不可能体力是0,最小值明显是1,所以说,就像飞了过去一样。
2. 大家仔细观察这个方程可以发现,这个方程根本没有只上1阶台阶的情况,所以肯定是错误的。
正确思想
什么是算法?算法是为了能让人更好、更快的解决生活中遇到的各类问题的一种运算。而我们不应该为了套用某种算法而故意向某类算法贴合。我们学算法只能去学一种算法思想,而真正的内容只能靠我们去填,填的不是模板 而是数学。
说多了点,其实这道题根本没那么复杂,做法就是 算出经过每一阶台阶的最小值,然后返回最后两阶台阶中更小的那个就行了。
怎么算经过每一阶的最小值呢?题目说了,只能迈一阶或者两阶,那么dp方程就是:dp[i] = min(dp[i - 2], dp[i - 1]) + cost[i]。
注意,我们算出的是经过了每一阶台阶的最小值,并非上到此阶台阶时所消耗的最小体力值。
下面贴代码:
python code
class Solution:
def minCostClimbingStairs(self, cost: List[int]) -> int:
f1, f2 = cost[0], cost[1]
for i in range(2, len(cost)):
f1, f2 = f2, min(f1, f2) + cost[i]
return min(f1, f2)
c++ code
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
int res1 = cost[0];
int res2 = cost[1];
int tmp;
for(int i = 2; i < cost.size(); i++){
tmp = res2;
res2 = min(res1, res2) + cost[i];
res1 = tmp;
}
return min(res1, res2);
}
};