算法训练Day38 | 动态规划理论基础;LeetCode509. 斐波那契数;70. 爬楼梯;746. 使用最小花费爬楼梯

本文详细介绍了动态规划理论基础,并通过LeetCode的509. 斐波那契数、70. 爬楼梯、746. 使用最小花费爬楼梯三道题目,阐述了动态规划的五部曲方法,包括确定dp数组、递推公式、初始化、遍历顺序和举例推导。文章强调了理解和应用动态规划方法论的重要性,以及递归算法的时间复杂度分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

动态规划理论基础

 LeetCode509. 斐波那契数

1. 思路

2. 代码实现

3. 复杂度分析

4. 总结与收获

LeetCode70. 爬楼梯

1. 思路

2. 代码实现

3. 复杂度分析

4. 思考与收获

LeetCode746. 使用最小花费爬楼梯

1. 思路

2. 代码实现

3. 复杂度分析

4. 思考与收获


动态规划理论基础

 链接:代码随想录 (programmercarl.com)

 LeetCode509. 斐波那契数

链接: 509. 斐波那契数 - 力扣(LeetCode) 

1. 思路

斐波那契数列大家应该非常熟悉不过了,非常适合作为动规第一道题目来练练手;因为这道题目比较简单,可能一些同学并不需要做什么分析,直接顺手一写就过了;但「代码随想录」的风格是:简单题目是用来加深对解题方法论的理解的

通过这道题目让大家可以初步认识到,按照动规五部曲是如何解题的。对于动规,如果没有方法论的话,可能简单题目可以顺手一写就过,难一点就不知道如何下手了。

动态规划五部曲

  1. 确定dp数组及下标的含义

    这里我们要用一个一维dp数组来保存递归的结果,dp[i]的定义为:第i个数的斐波那契数值是dp[i];

  2. 确定递推公式

    为什么这是一道非常简单的入门题目呢?因为题目已经把递推公式直接给我们了:状态转移方程 dp[i] = dp[i - 1] + dp[i - 2];

  3. dp数组如何初始化

    题目中把如何初始化也直接给我们了,如下:dp[0] = 0;dp[1] = 1;

  4. 确定遍历顺序

    从递归公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,dp[i]是依赖 dp[i - 1] 和 dp[i - 2],那么遍历的顺序一定是从前到后遍历的;

  5. 举例推导dp数组

    按照这个递推公式dp[i] = dp[i - 1] + dp[i - 2],我们来推导一下,当N为10的时候,dp数组应该是如下的数列:0 1 1 2 3 5 8 13 21 34 55;

    如果代码写出来,发现结果不对,就把dp数组打印出来看看和我们推导的数列是不是一致的;

2. 代码实现

2.1 代码实现一: 动态规划

# 动态规划 
# time:O(N);space:O(N)
class Solution(object):
    def fib(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n<=1: return n
        dp = [0]*(n+1)
        dp[0] = 0
        dp[1] = 1
        for i in range(2,n+1):
            dp[i] = dp[i-1] + dp[i-2]
        return dp[n]

2.2 代码实现二:动态规划 状态压缩

写出以上的代码之后,我们当然可以发现,我们只需要维护两个数值就可以了,不需要记录整个序列;

# 状态压缩 动态规划
# 不用记录整个斐波那契数列
# 只用记录当前值的前两个元素的值
# time:O(N);space:O(1)
class Solution(object):
    def fib(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n<=1: return n 
        first,second = 0,1
        for _ in range(n-1):
            result = first + second
            first = second
            second = result
        return result

2.3 代码实现三:递归做法

斐波那契数的题目用递归也是经典解法;

# 递归做法
# time:O(2^N);space:O(N)
class Solution(object):
    def fib(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n == 0: return 0
        if n == 1: return 1
        return self.fib(n-1)+self.fib(n-2)

3. 复杂度分析

3.1 代码实现一: 动态规划

  • 时间复杂度:O(N)

    N为第N个斐波那契数,for循环在遍历时,遍历次数为O(N)的数量级;

  • 空间复杂度:O(N)

    需要一个大小为N的数列dp储存斐波那契数列;

3.2 代码实现二:动态规划 状态压缩

  • 时间复杂度:O(N)

    N为第N个斐波那契数,for循环在遍历时,遍历次数为O(N)的数量级;

  • 空间复杂度:O(1)

    只用了三个变量储存前中后连续三个斐波那契数;

3.3 代码实现三:递归做法

(容易错)透彻理解递归的复杂度:

相信很多同学对递归算法的时间复杂度都很模糊,那么这篇来给大家通透的讲一讲。同一道题目,同样使用递归算法,有的同学会写出了O(n)的代码,有的同学就写出了O(logn)的代码

这是为什么呢?

如果对递归的时间复杂度理解的不够深入的话,就会这样!

那么我通过一道简单的面试题,模拟面试的场景,来带大家逐步分析递归算法的时间复杂度,最后找出最优解,来看看同样是递归,怎么就写成了O(n)的代码。

💡 面试题:求x的n次方

方法一:for循环迭代解法;time complexity࿱

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值