文章目录
1. 题目
数组的每个下标作为一个阶梯,第 i
个阶梯对应着一个非负数的体力花费值 cost[i]
(下标从 0 0 0 开始)。每当你爬上一个阶梯你都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯。请你找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 0 0 0 或 1 1 1 的元素作为初始阶梯。
1.1 示例
-
示例 1 1 1:
- 输入:
cost = [10, 15, 20]
- 输出: 15 15 15
- 解释: 最低花费是从
cost[1]
开始,然后走两步即可到阶梯顶,一共花费 15 15 15 。
- 输入:
-
示例 2 2 2:
- 输入:
cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
- 输出: 6 6 6
- 解释: 最低花费方式是从
cost[0]
开始,逐个经过那些 1 1 1 ,跳过cost[3]
,一共花费 6 6 6 。
- 输入:
1.2 说明
- 来源: 力扣(LeetCode)
- 链接: https://leetcode-cn.com/problems/min-cost-climbing-stairs
1.3 提示
cost
的长度范围是[2, 1000]
。cost[i]
将会是一个整型数据,范围为[0, 999]
。
1.4 进阶
你可以进一步输出花费最小时所爬阶梯的下标么?
2. 动态规划
2.1 分析
实际上,这道题目只要认真理解清楚题目的含义就很好解答,其中最重要的是要理解什么叫爬到楼顶,其实当你最后一次站上了下标为 i - 1
或下标为 i
的阶梯时,之后再跨一次就到达楼顶(这里只有比楼顶低一个及以上数量台阶高度的位置才被称为阶梯)了;另外跨台阶是不需要消耗体力的,只有站上台阶才会消耗体力,更重要的是站上楼顶也不会消耗体力。
2.1.1 定义状态
使用 dp[i]
表示为了站上下标为 i
的阶梯所耗费的最小体力。
2.1.2 基本状态
根据状态的含义,显然应该有 dp[0] = cost[0]
, dp[1] = cost[1]
。
2.1.3 状态转移
为了能站上第 i
级阶梯,先前可能站在第 i - 1
级阶梯或第 i - 2
级阶梯,因此状态转移方程为: min(dp[i - 1], dp[i - 2]) + cost[i]
。
2.2 解答
根据上述分析给出如下解答:
from typing import List
class Solution:
def min_cost_climb_stairs(self, cost: List[int]) -> int:
if len(cost) == 2:
return min(cost[0], cost[1])
dp = [0 for _ in range(len(cost))]
dp[0] = cost[0]
dp[1] = cost[1]
for i in range(2, len(cost)):
dp[i] = min(dp[i - 1] + cost[i], dp[i - 2] + cost[i])
return min(dp[-1], dp[-2])
def main():
cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
sln = Solution()
print(sln.min_cost_climb_stairs(cost)) # 6
if __name__ == '__main__':
main()
2.3 复杂度
- 时间复杂度: O ( n ) O(n) O(n) ;
- 空间复杂度: O ( n ) O(n) O(n) 。
实际上,可以继续优化上述代码,使得空间复杂度下降至 O ( 1 ) O(1) O(1) :
from typing import List
class Solution:
def space_efficient_min_cost_climb_stairs(self, cost: List[int]) -> int:
prev, cur = cost[0], cost[1]
for i in range(2, len(cost)):
prev, cur = cur, min(prev, cur) + cost[i]
return min(prev, cur)
def main():
cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
sln = Solution()
print(sln.space_efficient_min_cost_climb_stairs(cost)) # 6
if __name__ == '__main__':
main()