【算法】动态规划 斐波那契类型:746. 使用最小花费爬楼梯

  1. 使用最小花费爬楼梯

给你一个整数数组 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

在这里插入图片描述

分析

我们同样可以用动态规划来解决这道「最小花费爬楼梯」问题。设 n = cost.size(),我们要到达的“楼顶”可以看作下标 n(此处没有实际的 cost,需要付费即可到达终点)。


思路

  • 定义 dp[i] 为到达位置 i 时的最小累计花费。

  • 因为每次可以爬 1 或 2 阶:

    • i-1 跨一步到 i,需付费 cost[i-1]
    • i-2 跨两步到 i,需付费 cost[i-2]
  • 状态转移:

    dp[i] = min(dp[i-1] + cost[i-1],  dp[i-2] + cost[i-2])
    
  • 边界(起始):

    • dp[0] = 0:还没开始爬,花费 0;
    • dp[1] = 0:可以从下标 1 开始,也不需要先付费就到达 1。

最终答案为 dp[n]

注意:这样定义的好处是最后一步到达 n 时,并不需要为下标 n 支付任何 cost,因为 cost 只有 0…n−1。


方法一:O(n) 时间 + O(n) 空间

// C++ 实现
class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n = cost.size();
        vector<int> dp(n+1, 0);
        // dp[0]=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];
    }
};
# Python 实现
class Solution:
    def minCostClimbingStairs(self, cost: List[int]) -> int:
        n = len(cost)
        dp = [0] * (n + 1)
        # dp[0]=0, dp[1]=0
        for i in range(2, n + 1):
            dp[i] = min(dp[i-1] + cost[i-1],
                        dp[i-2] + cost[i-2])
        return dp[n]
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

方法二:O(n) 时间 + O(1) 空间(滚动变量)

我们注意到 dp[i] 只依赖于 dp[i-1]dp[i-2],因此可以只用两个变量滚动维护:

// C++ 实现(O(1) 空间)
class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n = cost.size();
        int prev2 = 0;  // dp[i-2]
        int prev1 = 0;  // dp[i-1]
        int cur;
        for (int i = 2; i <= n; ++i) {
            cur = min(prev1 + cost[i-1],
                      prev2 + cost[i-2]);
            prev2 = prev1;
            prev1 = cur;
        }
        return prev1;
    }
};
# Python 实现(O(1) 空间)
class Solution:
    def minCostClimbingStairs(self, cost: List[int]) -> int:
        n = len(cost)
        prev2, prev1 = 0, 0  # dp[i-2], dp[i-1]
        for i in range(2, n + 1):
            cur = min(prev1 + cost[i-1],
                      prev2 + cost[i-2])
            prev2, prev1 = prev1, cur
        return prev1
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

示例验证

  • 示例 1:cost = [10,15,20]

    dp[2] = min(dp[1]+cost[1], dp[0]+cost[0]) = min(0+15, 0+10) = 10  
    dp[3] = min(dp[2]+cost[2], dp[1]+cost[1]) = min(10+20, 0+15) = 15  
    返回 dp[3]=15
    
  • 示例 2:cost = [1,100,1,1,1,100,1,1,100,1] 亦可按上述方法逐步计算,最终得到 6。

以上两种方法都能在线性时间内求解,其中滚动变量的方法在空间上更优。

花费

这两个 cost 数组只是用来举例说明「最小花费爬楼梯」的输入格式——数组下标代表台阶编号,数组值代表踏上该台阶需要支付的费用。下面我逐一拆解这两个例子,帮助你看懂它们的设定和最优策略是如何得出的。


例子一:cost = [10, 15, 20]

  • 共有 3 个台阶,编号 0、1、2,对应的费用分别是

    cost[0] = 10,   cost[1] = 15,   cost[2] = 20
    
  • “楼顶”可以看作是下标 3(超出数组末尾,不需要支付费用)。

  • 可以从台阶 0 或 台阶 1 开始,不用先付 0 号或 1 号之前的费用。

所有可能的起点和路径

  1. 从 0 开始

    • 选一步到 1(付 15),再一步到 2(付 20),再一步到顶(不付费)→ 总花费 15+20 = 35
    • 或一步到 1(15),两步到顶(不用再付 20)→ 总花费 15
    • 或两步到 2(一次性跨过 0→2,付费 cost[0] = 10),再一步到顶→ 总花费 10

    在这三条路径里,最少是 10(先跨两步付10,再跨一步不付费)。

  2. 从 1 开始

    • 一步到 2(付 20),再一步到顶 → 20
    • 两步直接到顶(从 1→3,不需要付 cost[2])→ 付费 cost[1] = 15

    最少是 15

比较从 0 开始最优(10)和从 1 开始最优(15),取更小,答案就是 10

注意:上面从 0 出发跨两步是付 cost[0],而不是 cost[2],这一点是容易混淆的。
不过官方示例给出的答案是 15,这是因为他们在「从 0 开始」时,默认的最优策略是:

  • 付 10(0→2),再 0→3(不用费 cost[2])其实也是可以的,花费 10。
    但 LeetCode 示例里他们没列出这条路径,而是直接说「从 1 开始,两步到顶,花费 15」。
    实际上,对于 [10,15,20],最小花费应该是 10

例子二:

cost = [1,100,1,1,1,100,1,1,100,1]

长度是 10,对应台阶下标 0…9;楼顶是下标 10。

下标 i0123456789
cost[i]1100111100111001

最优路径解析

  • 最开始我们可以选从 0 或 1 开始;显然 cost[0]=1cost[1]=100 小,所以从 0 出发更优。
  • 从 0→2:付费 cost[0] = 1
  • 从 2→4:付费 cost[2] = 1
  • 从 4→6:付费 cost[4] = 1
  • 从 6→7:付费 cost[6] = 1
  • 从 7→9:付费 cost[7] = 1
  • 从 9→10(楼顶):付费 cost[9] = 1

总花费 = 1 + 1 + 1 + 1 + 1 + 1 = 6

你也可以验证:

  • 每次都尽量跳过 100 的台阶(下标 1、5、8),
  • 在 1、2、4、6、7、9 等低费台阶付款。

小结

  • cost[i] 就是「踏上台阶 i 要付的钱」。

  • 最后到达的“楼顶”下标是 n = cost.length,不需要付费。

  • 你可以从下标 0 或 1 开始;第一次踏上就是付对应的 cost[起点]

  • 动态规划的状态转移:

    dp[i] = min(dp[i-1] + cost[i-1],
                dp[i-2] + cost[i-2])
    

    最后返回 dp[n]

希望这样分步拆解,能让你看懂这两个示例中 cost 数组的具体含义和最优付费路径!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

等风来不如迎风去

你的鼓励是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值