力扣Day38(2.16)| 第八章 动态规划 ( 509. 斐波那契数 70. 爬楼梯 746. 使用最小花费爬楼梯)

理论基础:

在这里插入图片描述

题一:509. 斐波那契数

链接

题目链接:
视频链接:
文章链接:

视频总结

关键点

编程思路

Me:
卡尔:

1. 确定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数组打印出来看看和我们推导的数列是不是一致的。

力扣实战

思路一:暴力数学法

 class Solution:
    def fib(self, n: int) -> int:
        # easy
        res = [0,1]
        for i in range(2,n+1):
            res.append(res[i-1]+res[i-2])
        return res[n]
        
# 反思1:

思路二:递归

 class Solution:
    def fib(self, n: int) -> int:
        # 递归写法,注意开头剪枝,参数返回值;退出条件;单词递归逻辑的书写!
        res = [0,1]
        if n<2: #开头剪枝
            return res[n]
        def func(n):    #参数为n,全局变量res不需要返回值
            if len(res)==n+1:   #退出条件
                return 
            sum1 = res[-1]+res[-2]  #单词递归逻辑
            res.append(sum1)
            func(n)
        func(n)
        return res[n]

思路三:dp法 和 拉满递归法

# 动态规划 (注释版。无修饰)
class Solution:
    def fib(self, n: int) -> int:
       
        # 排除 Corner Case
        if n == 1:
            return 1

        if n == 0:
            return 0
        
        # 创建 dp table 
        dp = [0] * (n + 1)

        # 初始化 dp 数组
        dp[0] = 0
        dp[1] = 1

        # 遍历顺序: 由前向后。因为后面要用到前面的状态
        for i in range(2, n + 1):

            # 确定递归公式/状态转移公式
            dp[i] = dp[i - 1] + dp[i - 2]
        
        # 返回答案
        return dp[n]

# 递归实现
class Solution:
    def fib(self, n: int) -> int:
        if n < 2:
            return n
        return self.fib(n - 1) + self.fib(n - 2)

文档总结

1. 简单题目是用来加深对解题方法论的理解的。
2. 

题二:70. 爬楼梯ez

链接

题目链接:
视频链接:
文章链接:

视频总结

编程思路

Me:
  1. 按照回溯五部曲的思路,考虑每个dp元素的意思,然后再考虑每个dp元素直接的递推关系,这样就会把思路引导到,到n阶的爬法和到n-i阶楼梯的爬法之间的关系,然后就显然了!
卡尔:

力扣实战

思路一:

# 空间复杂度为O(n)版本
class Solution:
    def climbStairs(self, n: int) -> int:
        # dp[i] 为第 i 阶楼梯有多少种方法爬到楼顶
        dp = [0]*(n+1)
        dp[0] = 1
        dp[1] = 1
        for i in range(2, n+1):
            dp[i] = dp[i-1] + dp[i-2]
        return dp[n]

 class Solution:
    def climbStairs(self, n: int) -> int:
            #可以分解成先走n-2再走2和先走n-1再走1
            dp = [1,2]
            for i in range(2,n):
                dp.append(dp[-1]+dp[-2])
            return dp[n-1]
        
# 反思1:

思路二:

 # 空间复杂度为O(1)版本,相当于只维护两个需要计算的空间,不断的计算和覆盖。这也是优化空间复杂度的思路
class Solution:
    def climbStairs(self, n: int) -> int:
        dp = [0]*(n+1)
        dp[0] = 1
        dp[1] = 1
        for i in range(2,n+1):
            tmp = dp[0] + dp[1]
            dp[0] = dp[1]
            dp[1] = tmp
        return dp[1]

思路三:

这道题目还可以继续深化,就是一步一个台阶,两个台阶,三个台阶,直到 m个台阶,有多少种方法爬到n阶楼顶。
这又有难度了,这其实是一个完全背包问题,但力扣上没有这种题目,所以后续我在讲解背包问题的时候,今天这道题还会从背包问题的角度上来再讲一遍。 如果想提前看一下,可以看这篇:70.爬楼梯完全背包版本(opens new window)

代码:

class Solution {
public:
    int climbStairs(int n) {
        vector<int> dp(n + 1, 0);
        dp[0] = 1;
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) { // j的上界是m,这是关键把m换成2,就可以AC爬楼梯这道题
                if (i - j >= 0) dp[i] += dp[i - j];	#因为是方法数量,所以磊加即可
            }
        }
        return dp[n];
    }
};

代码中m表示最多可以爬m个台阶。

以上代码不能运行哈,我主要是为了体现只要把m换成2,粘过去,就可以AC爬楼梯这道题,不信你就粘一下试试。

此时我就发现一个绝佳的大厂面试题,第一道题就是单纯的爬楼梯,然后看候选人的代码实现,如果把dp[0]的定义成1了,就可以发难了,为什么dp[0]一定要初始化为1,此时可能候选人就要强行给dp[0]应该是1找各种理由。那这就是一个考察点了,对dp[i]的定义理解的不深入。

然后可以继续发难,如果一步一个台阶,两个台阶,三个台阶,直到 m个台阶,有多少种方法爬到n阶楼顶。这道题目leetcode上并没有原题,绝对是考察候选人算法能力的绝佳好题。

这一连套问下来,候选人算法能力如何,面试官心里就有数了。

其实大厂面试最喜欢的问题就是这种简单题,然后慢慢变化,在小细节上考察候选人。

文档总结

1. 

题三:746. 使用最小花费爬楼梯

链接

题目链接:
视频链接:
文章链接:

视频总结

力扣实战

思路一:

class Solution:
    def minCostClimbingStairs(self, cost: List[int]) -> int:
        #dp[i]表示爬到第i个台阶后面的最小花费
        n=len(cost)
        if n==2:
            return min(cost[0],cost[1])
        dp = [999]*(n+1)
        dp[0]=0
        dp[1]=cost[0]
        dp[2]=min(cost[0],cost[1])  #[0,1,1,2,2,3,3,4,4,5,6]
        dp[3]=min(cost[1],dp[2]+cost[2])    #因为开始可以从索引0或者1跳一格或者两个,则最初最多能跳到索引3,所以需要初始化四个参数
        for i in range(4,n+1):  #注意取值上界!卡了好久,煞笔了
            dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])
        res=dp[-1]
        return res
 
        
# 反思1:

思路二:

 class Solution:
    def minCostClimbingStairs(self, cost: List[int]) -> int:
        #基于基础dp思路,把空间复杂度减少到欧一,由于状态方程在n≥4之后才起作用,所以需要维护后面三个空间
        n=len(cost)
        if n==2:
            return min(cost[0],cost[1])
        if n<5:
            dp = [999]*(n+1)
            dp[0]=0
            dp[1]=cost[0]
            dp[2]=min(cost[0],cost[1])  #[0,1,1,2,2,3,3,4,4,5,6]
            dp[3]=min(cost[1],dp[2]+cost[2])
            for i in range(4,n+1):
                dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])
        else:
            dp = [999]*6
            dp[0]=0
            dp[1]=cost[0]
            dp[2]=min(cost[0],cost[1])  #[0,1,1,2,2,3,3,4,4,5,6]
            dp[3]=min(cost[1],dp[2]+cost[2])    #因为开始可以从索引0或者1跳一格或者两个,则最初最多能跳到索引3,所以需要初始化四个参数
            for i in range(4,6):
                dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])
            for i in range(6,n+1):  #注意取值上界!卡了好久,煞笔了
                dp[-3]=dp[-2]
                dp[-2]=dp[-1]
                tem=min(dp[-2]+cost[i-1],dp[-3]+cost[i-2])                
                dp[-1]=tem        
        res=dp[-1]
        return res

思路三

class Solution:
    def minCostClimbingStairs(self, cost: List[int]) -> int:
        #若清晰了dp0和dp1的含义,代码可以极大简化.然后在此基础上优化空间,只需要欧一的空间复杂度
        n=len(cost)
        dp=[0]*(2)
        for i in range(2,n+1):
            tem=min(dp[-1]+cost[i-1],dp[-2]+cost[i-2])
            dp[0]=dp[1]
            dp[1]=tem
        return dp[-1]

文档总结

1. 题意确实有点模糊:①若位置为零到底是否花费体力②重点是数组最后一个元素的后面
2. 本题初始化:在0位置和1位置时,不需要跳,所以初始为零,其他的位置都需要跳,所以不需要跳
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值