来自代码随想录
什么是动态规划
动态规划,英文: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 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例 1 :
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1 阶 + 1 阶
2 阶
示例 2 :
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1 阶 + 1 阶 + 1 阶
1 阶 + 2 阶
2 阶 + 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 开始)。每当你爬上一个阶梯你都要花费对应的体力值,一旦支付了相应的体力值,你就可以选择向上爬一个阶梯或者爬两个阶梯。请你找出达到楼层顶部的最低花费。在开始时,你可以选择从下标为 0 或 1 的元素作为初始阶梯。
示例 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 2
从0 , 1 开始
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 ) :
dpi = min ( dp1 + cost[ i - 1 ] , dp0 + cost[ i - 2 ] )
dp0 = dp1
dp1 = dpi
return dp1