代码随想录算法训练营第四十一天|动态规划理论基础、509. 斐波那契数列、70. 爬楼梯、746. 使用最小花费爬楼梯

动态规划理论基础

什么是动态规划

动态规划,英文:Dynamic Programming,简称DP,如果某一问题有很多重叠子问题,使用动态规划是最有效的。

所以动态规划中每一个状态一定是由上一个状态推导出来的这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的,

常见类型

基础问题

背包问题

打家劫舍

股票问题

子序列问题

动规的误区

递推公式不是最主要的,仅仅是一部分

DP数组

DP数组究竟表示什么意思以及下标的含义

DP数组如何初始化

DP数组遍历顺序

打印DP数组(查错debug)

递归如何debug

做动规的题目,写代码之前一定要把状态转移在dp数组的上具体情况模拟一遍,心中有数,确定最后推出的是想要的结果

然后再写代码,如果代码没通过就打印dp数组,看看是不是和自己预先推导的哪里不一样。

如果打印出来和自己预先模拟推导是一样的,那么就是自己的递归公式、初始化或者遍历顺序有问题了。

如果和自己预先模拟推导的不一样,那么就是代码实现细节有问题。

这样才是一个完整的思考过程,而不是一旦代码出问题,就毫无头绪的东改改西改改,最后过不了,或者说是稀里糊涂的过了

这也是我为什么在动规五步曲里强调推导dp数组的重要性。

举个例子哈:在「代码随想录」刷题小分队微信群里,一些录友可能代码通过不了,会把代码抛到讨论群里问:我这里代码都已经和题解一模一样了,为什么通过不了呢?

发出这样的问题之前,其实可以自己先思考这三个问题:

  • 这道题目我举例推导状态转移公式了么?
  • 我打印dp数组的日志了么?
  • 打印出来了dp数组和我想的一样么?

如果这灵魂三问自己都做到了,基本上这道题目也就解决了,或者更清晰的知道自己究竟是哪一点不明白,是状态转移不明白,还是实现代码不知道该怎么写,还是不理解遍历dp数组的顺序。

然后在问问题,目的性就很强了,群里的小伙伴也可以快速知道提问者的疑惑了。

注意这里不是说不让大家问问题哈, 而是说问问题之前要有自己的思考,问题要问到点子上!

大家工作之后就会发现,特别是大厂,问问题是一个专业活,是的,问问题也要体现出专业!

如果问同事很不专业的问题,同事们会懒的回答,领导也会认为你缺乏思考能力,这对职场发展是很不利的。

所以大家在刷题的时候,就锻炼自己养成专业提问的好习惯。

递归五部曲总结

DP数组定义以及下标的含义

递推公式

DP数组如何初始化

DP数组遍历顺序

打印DP数组(查错debug)

509. 斐波那契数列

文档讲解:代码随想录

题目链接:. - 力扣(LeetCode)

dp数组

一维或者二维用来做状态转移的dp数组

dp[i]:第i个斐波那契的数组

递推公式

dp[i] = dp[i-1] +dp[i-2]

dp数组如何初始化

dp[0]=1,dp[1]=1

遍历顺序

从前向后遍历,从i=2开始遍历

debug

如果有问题就打印dp数组

class Solution:
    def fib(self, n: int) -> int:
        dp = [1]*(n+1) #因为有f(n),所以数组长度是n+1
        dp[0]= 0
        for i in range(2,n+1): 
            dp[i] = dp[i-1] + dp[i-2]
        print(dp)
        return dp[n]

这个题目如何简单:dp数组的初始化和递推关系都已经给我们了,遍历顺序也是很直观

70. 爬楼梯

文档讲解:代码随想录

题目链接:. - 力扣(LeetCode)

爬到第一层楼梯有一种方法,爬到二层楼梯有两种方法(走一步或者直接走两步)。

那么第一层楼梯再跨两步就到第三层 ,第二层楼梯再跨一步就到第三层。

所以到第三层楼梯的状态可以由第二层楼梯 和 到第一层楼梯状态推导出来,那么就可以想到动态规划了。(当要爬到第三层时,我们已经在一层或者二层了,假设在一层,我们就还需要迈两个台阶,假设在第二层,只需迈一个台阶就可以,因为我们最后一步只能迈一步或者两步

为什么三阶台阶只和一阶和二阶有关系?因为一步只能迈一步或者二步

同理4阶只能由2阶或者3阶迈上来,2阶再走两步,3阶再走1步

递推:当前这个阶有几种方法依赖于前两种状态

dp数组

dp[i]:达到第i阶有dp[i]种方法

递推公式

dp[i] = dp[i-1] +dp[i-2]

dp数组如何初始化

dp[0]=0,dp[1]=1,dp[2]=2

遍历顺序

从前向后遍历,从i=3开始遍历

debug

如果有问题就打印dp数组

class Solution:
    def climbStairs(self, n: int) -> int:
        dp = [0]*(n+1) #因为有f(n),所以数组长度是n+1
        dp[1] = 1
        if n > 1:
            dp[2] = 2
        for i in range(3,n+1): #当前状态只依赖于前两个状态,可以进行压缩
            dp[i] = dp[i-1] + dp[i-2]
        print(dp)
        return dp[n]

 最后这个题的代码与上一题差不多,但是从题意中看不出来呢?因为dp数组的定义与递推关系在题目中并没有明说

746. 使用最小花费爬楼梯

文档讲解:代码随想录

题目链接:. - 力扣(LeetCode)

dp数组的定义

如何定义dp数组呢?

我们的目标是求到达顶楼的最小消耗,上图中可以看出下标为3代表我们达到了楼顶,下标对应的值就表示为达到这楼的消耗

所以dp数组的含义就表示:到达i位置的最小花费是dp[i]

递推公式

一步可以跳1个台阶或者两个台阶

i可以由i-1跳了一步得到,花费就是cost[i-1]  一共就是dp[i] + cost[i-1]

也可以由i-2跳了两步得到,花费就是cost[i-2] 一共就是dp[i-2] + cost[i-2]

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

dp数组如何初始化

初始位置可以选在0或1

dp[0] = 0,dp[1] = 0 

遍历顺序

从前向后遍历,从i=2开始遍历

debug

如果有问题就打印dp数组

class Solution:
    def minCostClimbingStairs(self, cost: List[int]) -> int:
        dp = [0]*(len(cost)+1)
        for i in range(2,len(cost)+1):
            dp[i] = min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])
        return dp[len(cost)]

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值