leetcode刷题笔记-动态规划-使用最小花费爬楼梯

文章讲述了三种解决最小花费爬楼梯问题的方法:递归解法导致指数级时间复杂度,备忘录法改进至线性时间复杂度,而动态规划从子问题最优解出发,实现更高效。
摘要由CSDN通过智能技术生成

Problem使用最小花费爬楼梯

解题方法1

思路

最简单的办法就是用递归
问题定义,假设要爬n级台阶,即数组长度为n,那么这n级台阶要么是从第n级台阶跨越到顶端,要么是第n-1级
比较这两个大小即可

代码

Solution1:
    # 采用递归的形式
    # 思路是将n级台阶问题转化为n-1级台阶与n-2级台阶问题比较
    # 原始问题是,只有两级台阶要跨一步,只有一级台阶则不用跨步

    def minCostClimbingStairs(self, cost):
        """
        :type cost: List[int]
        :rtype: int
        """
        return self.climb(cost, len(cost))

    def climb(self, cost, idx):
        if idx == 1:
            return 0

        if idx == 2:
            return cost[0] if cost[0] < cost[1] else cost[1]

        return cost[idx - 1] + self.climb(cost, idx - 1) \
            if cost[idx - 1] + self.climb(cost, idx - 1) < cost[idx - 2] + self.climb(cost, idx - 2) \
            else cost[idx - 2] + self.climb(cost, idx - 2)


复杂度

对于每一级台阶 n,递归都会分为两个子问题,每个子问题又继续分裂成两个更小的子问题,类似于二叉树的增长。这种递归的时间复杂度是指数级的,具体来说是 O ( 2 n ) O(2^n) O(2n)。这意味着随着 n(台阶数)的增加,所需的计算量会非常快地增加。

空间复杂度就是栈的调用, O ( n ) O(n) O(n)

解题方法2

思路

思路1中的递归存在大量的重复递归调用,n级台阶问题需要调用n-1级台阶问题和n-2级台阶问题,这些子问题又要调用对应的子问题,子问题之间有重叠,因此加上一个备忘录即可

代码

class Solution2:

    # 思路是在暴力递归的基础上让这个问题带一个备忘录
    table = {}

    def minCostClimbingStairs(self, cost):
        """
        :type cost: List[int]
        :rtype: int
        """
        return self.climb(cost, len(cost))

    def climb(self, cost, idx):
        if idx == 1:
            return 0

        if idx == 2:
            return cost[0] if cost[0] < cost[1] else cost[1]

        if idx in self.table:
            return self.table[idx]
        else:
            res = cost[idx - 1] + self.climb(cost, idx - 1) \
            if cost[idx - 1] + self.climb(cost, idx - 1) < cost[idx - 2] + self.climb(cost, idx - 2) \
            else cost[idx - 2] + self.climb(cost, idx - 2)

            self.table[idx] = res

            return res

复杂度

备忘录法确保每个台阶只计算一次,之后直接使用缓存的结果。因此,对于每个台阶 idx,计算一次之后就将结果保存,后续的递归调用直接从 table 字典中取得结果,避免了重复计算。

由于每个台阶只被计算一次,且每次计算的时间都是常数时间(假设字典操作是常数时间),因此时间复杂度降低为 O(n),其中 n 是台阶数。

空间复杂度:主要由两部分组成:
递归栈:在最坏情况下,递归的深度为 n,因此递归栈的空间复杂度为 O(n)。
备忘录存储:备忘录 table 用于存储每级台阶的计算结果,最多存储 n 个结果,因此空间复杂度也为 O(n)。

解题方法3

思路

递归是自上而下的问题解决方式,一个问题的解决需要其子问题的支撑,所以想到如果能先计算它的子问题,就可以得到问题的解,也就是自下而上的解决方式

错误思路

首先想到要爬n个台阶时,要么从第n个台阶爬上去,此时的花费是爬到第n个台阶所用的最小花费和第n个台阶的花费;要么从第n-1个台阶爬上去,此时的花费是爬到第n-1个台阶所用的最小花费和第n-1个台阶的花费,所以问题规模为n可以通过问题规模为n-1以及问题规模为n-2解决;这里的问题规模n指的是数组的长度,即台阶的数量;

这个思路为什么错:

规模为n的问题,不是建立在规模为n-1和规模为n-2的基础之上,举个例子,[10,15,30];
这三台阶最小话费是15;
但是当问题规模为1时,[10]最小花费是10
当问题规模为2时,[10,15]最小花费还是10
当问题规模为3时,建立在问题规模为1和2时的最小花费就是10+30 = 40,这显然是不对的
因为问题规模为3时,从15走最好,这利用了问题规模为2的情况但是15并不是问题规模为2的最优解,换句话说,它的子问题最优解没有到15这个台阶的;

正确思路

按照上面的例子,当台阶数为3时,要么从第2个台阶爬上来,要么从第1个台阶爬上来,换句话说,要知道爬到第2个台阶的最小花费和爬到第一个第一个台阶的最小花费;

因此要得到n个台阶的最小花费,要先得到爬到第n-1个台阶的花费和爬到第n-2个台阶的最小花费

以[10,15,20,25]为例子:
只有一个台阶[10]时,最小花费就是它自己10;
两个台阶[10,15]时,最小花费就是15
三个台阶[10,15,20]时,最小花费明显是从第一个台阶爬过来10+20=30
四个台阶[10,15,20,25]时,此时前三个台阶的最小花费数组[10,15,30],要么从第二个台阶跨到第四个台阶,要么从第三个台阶跨到第四个台阶,显然从第二个跨花费小,代价就是15+25=40,那么前四个台阶的花费就是[10,15,30,40]接下来的到第五、第六个台阶的花费就很自然;

思路就是填充这个最小花费数组res,填充的规则,第一个和第二个数组都是可以直接填充进去,从第三个开始
res[i] = cost[i] + min{res[i-1],res[i-2]}

最后检查res数组中倒数两个比较小的值就可以了

代码

 def minCostClimbingStairs(self, cost):
     
        if len(cost) == 1:
            return 0

        if len(cost) == 2:
            return cost[0] if cost[0] < cost[1] else cost[1]

        res = [cost[0],cost[1]]
        for i in range(2, len(cost)):
            min = res[i-1] if res[i-1] < res[i-2] else res[i-2]
            res.append(min + cost[i])

        return res[-1] if res[-2] > res[-1] else res[-2]

复杂度

时间复杂度一看就是 O ( n ) O(n) O(n)
空间复杂度用了一个列表也是 O ( n ) O(n) O(n)

  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值