当前位置:首页 > LeetCode > 正文内容

LeetCode #746 最小花费爬楼梯:动态规划经典

lher2025-09-05LeetCode4307

🧗‍♂️ LeetCode #746 最小花费爬楼梯:动态规划经典

给定一个整数数组 cost,其中 cost[i] 表示从第 i 个台阶向上爬需要支付的费用。支付费用后,你可以选择爬 1 或 2 个台阶。你可以从下标 0 或 1 开始,目标是到达楼梯顶部(超出数组的最后一步)。请计算到达楼梯顶部的最低花费。

本文将详细解析题目,提供三种 Go 语言实现方式,帮助你从递归到动态规划逐步掌握!


📜 题目描述

来源:LeetCode #746 - Min Cost Climbing Stairs

问题
给定整数数组 costcost[i] 是从第 i 个台阶向上爬的费用。支付费用后,可选择爬 1 或 2 个台阶。你可以从下标 0 或 1 开始,计算到达楼梯顶部(第 n 步)的最低花费。

示例 1

输入cost = [10,15,20]
输出15
解释
从下标 1 开始:

  • 支付 15,爬 2 个台阶到达顶部。
    总花费为 15。

示例 2

输入cost = [1,100,1,1,1,100,1,1,100,1]
输出6
解释
从下标 0 开始:

  • 支付 1,爬 2 阶到下标 2。

  • 支付 1,爬 2 阶到下标 4。

  • 支付 1,爬 2 阶到下标 6。

  • 支付 1,爬 1 阶到下标 7。

  • 支付 1,爬 2 阶到下标 9。

  • 支付 1,爬 1 阶到顶部。
    总花费为 6。

约束

  • 2 <= cost.length <= 1000

  • 0 <= cost[i] <= 999


💡 解题思路

核心观察

要到达楼梯顶部(第 n 步),你必须从第 n-1n-2 个台阶爬上来。因此,到达第 i 个台阶的最低花费可以表示为:

  • i-1 爬 1 步:需支付 cost[i-1] 加上到达 i-1 的最低花费。

  • i-2 爬 2 步:需支付 cost[i-2] 加上到达 i-2 的最低花费。

状态转移方程:

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

初始条件:

  • dp[0] = 0(从下标 0 开始,初始花费为 0)

  • dp[1] = 0(从下标 1 开始,初始花费为 0)

💡 小贴士:楼梯顶部是第 n 步,需计算 dp[n]。本题与爬楼梯问题类似,但增加了费用优化。


🚀 Go 语言实现

以下是三种 Go 语言实现方式,从递归到动态规划逐步优化,适合不同场景。代码块使用 go 语法高亮,建议在富文本编辑器中启用深色主题(如 monokaidracula)以增强可读性。

方法 1:递归 + 记忆化

通过递归解决,使用记忆化数组避免重复计算。

package main

import "fmt"

func minCostClimbingStairs(cost []int) int {
    n := len(cost)
    memo := make([]int, n+1)
    return dfs(n, cost, memo)
}

func dfs(i int, cost, memo []int) int {
    if i <= 1 {
        return 0
    }
    if memo[i] != 0 {
        return memo[i]
    }
    memo[i] = min(dfs(i-1, cost, memo)+cost[i-1], dfs(i-2, cost, memo)+cost[i-2])
    return memo[i]
}

func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

func main() {
    fmt.Println(minCostClimbingStairs([]int{10, 15, 20})) // 输出: 15
    fmt.Println(minCostClimbingStairs([]int{1, 100, 1, 1, 1, 100, 1, 1, 100, 1})) // 输出: 6
}
  • 时间复杂度:O(n)

  • 空间复杂度:O(n)

  • 优点:直观,体现递归思想

  • 缺点:需要额外内存存储记忆化数组


方法 2:动态规划(DP 数组)

使用数组存储每个台阶的最低花费,迭代计算。

package main

import "fmt"

func minCostClimbingStairs(cost []int) int {
    n := len(cost)
    dp := make([]int, n+1)
    dp[0], dp[1] = 0, 0
    for i := 2; i <= n; i++ {
        dp[i] = min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2])
    }
    return dp[n]
}

func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

func main() {
    fmt.Println(minCostClimbingStairs([]int{10, 15, 20})) // 输出: 15
    fmt.Println(minCostClimbingStairs([]int{1, 100, 1, 1, 1, 100, 1, 1, 100, 1})) // 输出: 6
}
  • 时间复杂度:O(n)

  • 空间复杂度:O(n)

  • 优点:逻辑清晰,适合初学者理解动态规划

  • 缺点:空间占用较多


方法 3:空间优化(滚动变量)🔥

仅用两个变量存储前两步的最低花费,优化空间复杂度。

package main

import "fmt"

func minCostClimbingStairs(cost []int) int {
    n := len(cost)
    prev, curr := 0, 0
    for i := 2; i <= n; i++ {
        next := min(curr+cost[i-1], prev+cost[i-2])
        prev = curr
        curr = next
    }
    return curr
}

func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

func main() {
    fmt.Println(minCostClimbingStairs([]int{10, 15, 20})) // 输出: 15
    fmt.Println(minCostClimbingStairs([]int{1, 100, 1, 1, 1, 100, 1, 1, 100, 1})) // 输出: 6
}
  • 时间复杂度:O(n)

  • 空间复杂度:O(1)

  • 优点:高效简洁,面试最佳写法

  • 推荐指数:⭐⭐⭐⭐⭐


📊 方法对比

方法时间复杂度空间复杂度推荐指数
递归 + 记忆化O(n)O(n)⭐⭐⭐☆
DP 数组O(n)O(n)⭐⭐⭐⭐
滚动变量(最优)O(n)O(1)⭐⭐⭐⭐⭐

注意:由于 cost[i] <= 999cost.length <= 1000,结果不会溢出 Go 的 int 类型。


🎯 进阶思考

  1. 扩展步长:如果可以爬 1、2 或 3 个台阶?
    状态转移方程需考虑更多前驱状态:dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2], dp[i-3] + cost[i-3])

  2. 动态费用:如果费用随时间变化?
    需引入时间维度,调整 DP 模型。

  3. 更高效率:能否用其他优化方法?
    对于特定输入,可能通过数学规律或预处理进一步优化。

💡 学习建议

  • 初学者:从递归和 DP 数组入手,理解状态转移。

  • 进阶者:掌握滚动变量优化,体会空间优化的精髓。

  • 高手:探索扩展问题,尝试更复杂的动态规划场景。


🔚 总结

“最小花费爬楼梯”问题是动态规划的经典变种,核心在于状态转移方程 dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])。通过三种 Go 实现(递归、DP 数组、滚动变量),我们可以从直观到高效逐步优化。

希望这篇文章帮你彻底掌握最小花费爬楼梯问题!如果觉得有用,欢迎点赞分享 💖,也欢迎留言讨论更多算法题!