代码随想录 day35 动态规划part01

来自代码随想录
什么是动态规划
动态规划,英文:Dynamic Programming,简称DP,如果某一问题有很多重叠子问题,使用动态规划是最有效的。

DP, 某一个问题有很多重叠的子问题。下一步的最优值依赖上一步, 且下一步的逻辑与上一步一致。

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

在关于贪心算法,你该了解这些! (opens new window)中我举了一个背包问题的例子。

例如:有N件物品和一个最多能背重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。


DP动规划下五步曲
1. 确定dp数组(dp table)以及下标的含义
2. 确定递推公式
3. dp数组如何初始化 (根据公式确定)
4. 确定遍历顺序
5. 举例推导dp数组

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

写动规题目,代码出问题很正常!

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

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

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

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

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

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

这一篇是动态规划的整体概述,讲解了什么是动态规划,动态规划的解题步骤,以及如何debug。

509. 斐波那契数

斐波那契数,通常用 F(n) 表示,形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: F(0) = 0,F(1) = 1 F(n) = F(n - 1) + F(n - 2),其中 n > 1 给你n ,请计算 F(n) 。

示例 1:

输入:2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1
示例 2:

输入:3
输出:2
解释:F(3) = F(2) + F(1) = 1 + 1 = 2
示例 3:

输入:4
输出:3
解释:F(4) = F(3) + F(2) = 2 + 1 = 3
提示:

0 <= n <= 30

思路

子问题: dp[i]: 第i个斐波那契数值
dp[i] = dp[i-1] + dp[i-2], i >=2;
dp[0] = 0; dp[1] = 1
dp = [0] * (n + 1)
dp[1] = 1
for i in range(2,n + 1):
    dp[i] = dp[i-1] + dp[i-2]
return dp[-1]

--->
dp = [3] * 2
dp[1] = 1
for i in range(2, n + 1):
    dp[2] = dp[0] + dp[1]
    dp[0] = dp[1]
    dp[1] = dp[2]
return dp[2]

f0 = 0
f1 = 1
for i in range(2, n + 1):
    f2 = f0 + f1
    f0 = f1
    f1 = f2
return f2

完整的动态规划

class Solution:
    def fib(self, n: int) -> int:
        if n <= 1:return n
        dp = [0] * (n+1)
        dp[1] = 1
        for i in range(2, n + 1):
            dp[i] = dp[i-1] + dp[i-2]
        return dp[n]

动态规划 优化code

class Solution:
    def fib(self, n: int) -> int:
        if n <= 1:return n
        dp = [0] * 3
        dp[1] = 1
        for i in range(2, n + 1):
            dp[2] = dp[0] + dp[1]
            dp[0] = dp[1]
            dp[1] = dp[2]
        return dp[1]

70. 爬楼梯

力扣题目链接(opens new window)

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 12 个台阶。你有多少种不同的方法可以爬到楼顶呢?

注意:给定 n 是一个正整数。

示例 1:

输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1+ 12 阶
示例 2:

输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1+ 1+ 11+ 22+ 1

思考

这个题和上一题很类似。 题目问有多少种爬到楼顶的方法? dp[i]: 爬到i阶有dp[i]种方法。
dp[i] 从哪里来, dp[i-1] dp[i-2]--一次只能爬1 or 2个台阶。
dp[i] = dp[i-1] + dp[i-2]
i >= 2
dp[0] = 1
dp[1] = 1
dp[2] = 2

i ---i-1, i-2 前到后
dp[2] = dp[0] + dp[1] = 1 + 1 = 2
dp[3] =dp[1] + dp[2] = 1 + 2 = 3
dp[4] = dp[2] + dp[3] = 2 + 3 = 5

dp = [0] * 3
for i in range(2, n + 1):
    dp[2] = dp[0] + dp[1]
    dp[0] = dp[1]
    dp[1] = dp[2]
return dp[2]

code python

class Solution:
    def climbStairs(self, n: int) -> int:
        if n <= 1:return 1
        dp = [0] * 3
        dp[0] = 1
        dp[1] = 1
        for i in range(2, n+1):
            dp[2] = dp[0] + dp[1]
            dp[0] = dp[1] 
            dp[1] = dp[2]
        return dp[2]

746. 使用最小花费爬楼梯

力扣题目链接(opens new window)
旧题目描述:
数组的每个下标作为一个阶梯,第 i 个阶梯对应着一个非负数的体力花费值 cost[i](下标从 0 开始)。每当你爬上一个阶梯你都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯。请你找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 01 的元素作为初始阶梯。

示例 1:
输入:cost = [10, 15, 20]
输出:15
解释:最低花费是从 cost[1] 开始,然后走两步即可到阶梯顶,一共花费 15 。
示例 2:

输入:cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
输出:6
解释:最低花费方式是从 cost[0] 开始,逐个经过那些 1 ,跳过 cost[3] ,一共花费 6 。
提示:

cost 的长度范围是 [2, 1000]。
cost[i] 将会是一个整型数据,范围为 [0, 999]

思考

最小的花费爬楼梯。

i -- cost[i] 0
一次只能爬1 or 201开始
dp[i]: 爬到第i个阶梯的最小花费为dp[i]
如何表示爬过第i过阶梯,一次只能爬1 or 2个阶梯
dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])
dp[0] = 0
dp[1] = 0
dp[2] = min(dp[1] + cost[1], dp[0] + cost[2])
dp[3] =  min(dp[2] + cost[2], dp[1] + cost[1])
dp[4] =  min(dp[3] + cost[3], dp[2] + cost[4])
dp[n] = (dp[n-1] + cost[n-1], dp[n-2] + cost[n-2])

dp[i] 爬过第i个台阶的最小花费。
dp[i] = min(dp[i-1], dp[i-2] + cost[i])
dp[0] = cost[0]
dp[1] = cost[1]
dp[2] =  min(dp[1], dp[0] + cost[2])
dp[3] =  min(dp[2], dp[1] + cost[3])

code python

class Solution:
    def minCostClimbingStairs(self, cost: List[int]) -> int:
        dp0 = 0  # 初始值,表示从起点开始不需要花费体力
        dp1 = 0  # 初始值,表示经过第一步不需要花费体力
        
        for i in range(2, len(cost) + 1):
            # 在第i步,可以选择从前一步(i-1)花费体力到达当前步,或者从前两步(i-2)花费体力到达当前步
            # 选择其中花费体力较小的路径,加上当前步的花费,得到当前步的最小花费
            dpi = min(dp1 + cost[i - 1], dp0 + cost[i - 2])
            
            dp0 = dp1  # 更新dp0为前一步的值,即上一次循环中的dp1
            dp1 = dpi  # 更新dp1为当前步的最小花费
        
        return dp1  # 返回到达楼顶的最小花费
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值