动态规划学习笔记-1
- 动态规划
简述
什么是动态规划
![](https://s1.ax1x.com/2020/05/02/JvhFFe.png)
A:动态规划(计数型/最优解)
B:递归
动态规划题目特点
- 计数
- 有多少种方式走到右下角
- 有多少种方法选出K个数使得和是SUM
- 求最大值/最小值
- 从左上角走到右下角路径的最大数字和
- 最长上升子序列长度
- 求存在性
- 取石子游戏,先手是否必胜
- 能不能选出k个数使得合适sum
Example 入门
Coin Cange(最值型)
题目描述
----------->求最大最小值动态规划
思路
- 直觉:最少的硬币组合—>尽量用面值大的硬币
---> 剩下1 不符合题意
---> 改进: 尽量用大的硬币,最后如果可以用一种硬币付清就行 7+7+7+2+2+2+2 合计6
---> 而正确结果是 7+5+5+5+5 合计5
---> 更改策略先小后大 或者 先中后小,但是总能找到反例,无法证明
---> 这种题无法用这种思路去解,算法证明需要证明,而这样的思路无法证明
-
动态规划解题步骤
-
确定状态
-
解动态规划的时候需要一个数组,数组的每个元素 f[i]或者 f[i][j]代表什么
-
确定状态需要两个意识:
- 最后一步
- 子问题
最后一步: - 虽然不知道最优策略是什么,但是最优策略肯定是 K 枚硬币 ai 的面值加起来是27 - -->所以存在最后一枚硬币ak - -->除去这枚硬币,前面的硬币面值加起来是27-ak - TKY1: 不关心前面的k-1枚硬币是怎么拼出来27-ak,而且甚至不知道ak合k,但是确定前面的硬币拼出了27-ak - KEY2: 因为是最优策略,所以拼出27-ak的硬币一定最少,否则就不是最优策略了
子问题: - 所以要求:最少用多少枚硬币可以拼出 27-ak - 将原问题转化成了一个子问题,而且规模更小:27-ak - 简化:设状态f(X)=最少用多少枚硬拼出X - ak,只可能是2,5,7 ak=2 f(27)=f(27-2)+1 ak=5 f(27)=f(27-5)+1 ... 需要的结果是最小的,所以f(27)=min{f(27-2)+1,f(27-5)+1,f(27-7)+1}
-
递归解法(非动态规划思想)
-
递归解法代码
int f(int x){ if(x==0) return 0; int res=MAX_VALUE; //最大值 if(x>=2){ res=Math.min((f(x-2)+1,res); } if(x>=5){ res=Math.min(f(x-5)+1,res); } if(x>=7){ res=Math.min(f(x-7)+1,res); } return res; }
-
递归解法的问题
从上面的图可以看出,存在重复计算的问题,算法性能不好,效率低下
- 如何避免?
---->动态规划:将计算结果保存下来,并改变计算顺序
- 如何避免?
-
-
-
转移方程
设状态 f[x]=最少用多少枚硬币拼出 X
对于任意 x,
f [ x ] = m i n { f [ x − 1 ] + 1 , f [ x − 5 ] + 1 , f [ x − 7 ] + 1 } f[x]=min\{f[x-1]+1,f[x-5]+1,f[x-7]+1\} f[x]=min{f[x−1]+1,f[x−5]+1,f[x−7]+1}
-
初始条件和边界情况
-
f [ x ] = m i n { f [ x − 1 ] + 1 , f [ x − 5 ] + 1 , f [ x − 7 ] + 1 } f[x]=min\{f[x-1]+1,f[x-5]+1,f[x-7]+1\} f[x]=min{f[x−1]+1,f[x−5]+1,f[x−7]+1}
-
有两个问题:
1. x-2,x-5,x-7 <0 怎么办? 2. 什么时候停下来 --- >如果不能拼出Y(包括Y=1,Y<0,此类情况),就定义f[x]= 无穷大
-
初始条件 f[0]=0
初始条件:用转移方程算不出,需要手动定义
边界条件,不要数组越界(向上越界,向下越界)
-
-
计算顺序
一般都是从小到大(一维),从上到下,从左到右(二维) [也有例外情况] 确定原则:当计算f[x]时,f[x-2],f[x-5],x[x-7]都已经得到结果
-
小结
时间复杂度为:m*n (m 块钱,n 种硬币)
代码
leetcode 练习题 https://leetcode-cn.com/problems/coin-change/)
Unique Paths(计数型)
题目描述
—> 计数型的动态规划
思路
-
确定状态
- 最后一步:
无论机器人用何种方式到达右下角,总有最后挪动的一步:向右 or 向下
右下角的坐标为(m-1,n-1) ---->那么前一步的坐标为(m-1,n-2) or (m-2,n-1)
- 子问题:
如果机器人有 X 种方式从左上角走到(m-2,n-1),有 Y 种方式从左右上角走到(m-1,n-2),则机器人有 X+Y 种方式走到(m-1,n-1)
为什么可以用X+Y? 加法原理:满足条件(无重复,无遗漏)
问题转化为机器人有多少种方式走到(m-1,n-2) 和 (m-2,n-1)
-
PS:如何判断状态数组是几维
—>看变量的数量
状态:设 f[i][j]为机器人有多少种方式从左上角走到(i,j)
- 转移方程
f [ i ] [ j ] = f [ i − 1 ] + f [ i ] [ j − 1 ] f[i][j]=f[i-1]+f[i][j-1] f[i][j]=f[i−1]+f[i][j−1]
-
初始条件和边界情况
初始条件:f[0][0]=1 机器人只有一种方式走到左上角
边界条件:i=0 或者 j=0 前一步只能有一个方向过来----> f[i][j]=1
-
计算顺序
从上到下,从左到右
时间复杂度 O(M*N)
代码
leetcode 练习题 https://leetcode-cn.com/problems/unique-paths/
Jump Game(存在型)
题目描述
----> 存在型动态规划问题
思路
-
确定思路
-
最后一步:
如果青蛙能跳到最后一块石头 n-1,我们考虑它跳的最后一步,这个石头试从石头 i 跳过来的,i< n-1
-
需要满足两个条件
- 青蛙可以跳到石头 i
- 最后一步不超过跳跃的最大距离
-
-
子问题:青蛙能不能跳到石头 i
-
状态:f[j]表示青蛙能不能跳到石头 j
- 状态转换方程
设 f[j]表示青蛙能不能跳到石头 j
f [ j ] = O R 0 < = i < j ( f [ i ] A N D i + a [ i ] > = j ) f[j]=OR_{0<=i<j}(f[i] \quad AND \quad i+a[i]>=j) f[j]=OR0<=i<j(f[i]ANDi+a[i]>=j)
-
初始条件和边界情况
- 初始条件:f[0]=true 青蛙一开始就在石头 0
-
计算顺序
一般顺序
时间复杂度 O(N^2),空间复杂度 O(N)
代码
leetcode 练习题 https://leetcode-cn.com/problems/jump-game/
ps:这道题还可以用贪心算法 O(N)
Example 提高
Unique Paths ||
题目描述
-------> 坐标型/计数型
思路
- 确定状态
- 转移方程
- 计算顺序
以上和 Unique Paths 相同
-
初始条件和边界情况
-
如果(0,0)或者(m-1,n-1)有障碍,直接输出 0;
-
如果(i,j)有障碍,f[0][0]=0,表示机器人不能到达此格
-
初始条件 f[0][0]=1
-
综上,由下图所示
-
代码
leetcode 练习题 https://leetcode-cn.com/problems/unique-paths-ii/
Paint House
题目描述
----->序列型动态规划
思路
-
确定状态
- 最后一步 最优策略种房子N-1一定染成了红、蓝、绿中的一种 但是相邻两栋房子不能是同一种颜色 所以如果最优策略中: 1. N-1红色,则N只能是蓝色、绿色 2. N-1蓝色,则N只能是红色、绿色 3. N-1绿色,则N只能是蓝色、红色 ````text 如果直接套用之前的套路,记录油漆前N栋房子的最小花费 自然也需要记录前N-1房子的最小花费 但是,前N-1栋房子的最优策略中不知道N-2的颜色,所以有可能和N-2撞色 ----->用数组记录 ------>分别记录油漆前N-1栋房子并且N-2是红色、蓝色、绿色的最小花费。 ```` - 子问题 求前N栋房子并且N-1是红色、蓝色、绿色的最小花费 ->需要知道N-1栋房子并且N-2是红色、蓝色、绿色的最小花费
状态:
-
f[i][0] i-1 是红色
-
f[i][1] i-1 是蓝色
-
f[i][2] i-1 是绿色
-
转移方程
f [ i ] [ 0 ] = m i n { f [ i − 1 ] [ 1 ] + c o s t [ i − 1 ] [ 0 ] , f [ i − 1 ] [ 2 ] + c o s t [ i − 1 ] [ [ 0 ] } f[i][0]=min\{f[i-1][1]+cost[i-1][0],f[i-1][2]+cost[i-1][[0]\} f[i][0]=min{f[i−1][1]+cost[i−1][0],f[i−1][2]+cost[i−1][[0]}
- 初始条件和边界情况
初始条件:f[0][0]=f[0][1]=f[0][2]=0
无边界情况
- 计算顺序
代码
lintcode 练习题 https://www.lintcode.com/problem/paint-house/description
Decode ways
题目描述
----->划分型
思路
-
确定状态
- 最后一步
一定有最后一个字母
- 子问题
设数字串长度为 N,要求前 N 个字符的解密方式
需要知道数字串前 N-1 和 N-2 的解密方式数
状态:设 s 前 i 个数组解密成字母串有 f[i]种方式
- 转移方程
f [ i ] = f [ i − 1 ] + f [ i − 2 ] f[i]=f[i-1]+f[i-2] f[i]=f[i−1]+f[i−2]
- 初始条件和边界情况
f[0]=1,s[0] !=‘0’
边界情况:
如果 i=1,只看最后一个数字
- 计算顺序
代码
lintcode 练习题 https://www.lintcode.com/problem/decode-ways/
Example 坐标型动态规划
-
给定一个序列或者网格
-
需要找到序列中某个/某些子序列或网格中的某条路径
- 某种性质最大/最小
- 计数
- 存在性
-
性质
-
动态规划方程 f[i]中的小标 i 表示以 ai 为结尾的满足条件的子序列的性质。
-
f[i][j]中的以(i,j)为结尾的满足条件的路径的性质
- 最值
- 个数
- 存在
-
Longest increasing continuous subsequnce
题目描述
思路
-
确定状态
- 最后一步 取数组的最后一个值,这个值可能大于前值也可能小于前值 - 子问题 数组共有N个数,到N的单调长度取决于N-1的情况
状态:
imax[i]=前 i 个值的最大单增长
imin[i]=前 i 个值的最大单减长
- 转移方程
imax[i]+=imax[i]
imin[i]+=imin[i]
max=Math.max(imax[i],imin[i]);
- 初始条件和边界情况
imax[0]=0;
imin[0]=0;
- 计算顺序
代码
lintcode 练习题 https://www.lintcode.com/problem/longest-continuous-increasing-subsequence/description
Minimum Path Sum
题目描述
思路
-
确定状态
- 最后一步
走到(m-1,n-1),而前一步可能是(m-2,n-1)或者(m-1,n-2)
- 子问题
走到(m-1,n-1)处的和由其上一步决定
状态:f[i][j] 为走到(i,j)的最小路径和
- 转移方程
f [ i ] [ j ] = m i n { f [ i − 1 ] [ j ] , f [ i ] [ j − 1 ] + A [ i ] [ j ] } f[i][j]=min\{f[i-1][j],f[i][j-1]+A[i][j]\} f[i][j]=min{f[i−1][j],f[i][j−1]+A[i][j]}
- 初始条件和边界情况
初始条件:f[0][0]=A[0][0]
边界情况:
f[0][j]=A[i][j]+f[0][j-1];f[i][0]=A[i][0]+f[i][0]
-
计算顺序
-
空间优化
f[i][j]=f[i-1][j]+f[i][j-1]
所以计算第 i 行时,只需要第 i 行和第 i-1 行的状态数组
用滚动数组来实现
计算f[2][]时,将值放入f[0][]
计算f[3][]时,将值放入f[1][]
之后同理
-
如果只依赖几行,就只开几行的数组
-
如果列数大于行数,那么就逐列计算,滚动数组方式同理
代码
lintcode 练习题 https://www.lintcode.com/problem/minimum-path-sum/description
Bomb Enemy
题目描述
思路
-
题目分析
-
每个炸弹可以往四个方向传播爆炸力
-
可以先分析一个防线,然后举一反三
如果在一个空地放一个炸弹,最多能向上炸死多少敌人 然后枚举,直到碰到墙为止 时间复杂度为O(MN*M)
-
-
用动态规划的方式优化
-
确定状态
-
假设有敌人或者有墙的格子也能放炸弹
-
有敌人的格子:格子里的敌人被炸死,并继续向上炸
-
有墙的格子:炸弹不能炸死任何敌人
-
-
最后一步
- 在(i,j)放一个炸弹,它向上能炸死的敌人是
- (i,j)是空地:(i-1,j)格向上能炸死的敌人数
- (i,j)是敌人:(i-1,j)格向上能炸死的敌人数+1
- (i,j)是墙:0
- 在(i,j)放一个炸弹,它向上能炸死的敌人是
-
子问题 - 需要知道(i-1,j)格放一个炸弹向上能炸死的敌人数
状态:up[i][j]表示(i,j)格放炸弹向上能炸死的地人数
-
-
转移方程
f [ i ] [ j ] = { u p [ i − 1 ] [ j ] 如果(i,j)是空格 u p [ i − 1 ] [ j ] + 1 如果(i,j)是敌人 0 如果(i,j)是墙 f[i][j]= \begin{cases} up[i-1][j]& \text{如果(i,j)是空格}\\ up[i-1][j]+1& \text{如果(i,j)是敌人}\\ 0& \text{如果(i,j)是墙} \end{cases} f[i][j]=⎩⎪⎨⎪⎧up[i−1][j]up[i−1][j]+10如果(i,j)是空格如果(i,j)是敌人如果(i,j)是墙
类似地计算 down,left,right
然后求和-
初始条件和边界情况
-
计算顺序
逐行计算
-
代码
lintcode 练习题 https://www.lintcode.com/problem/bomb-enemy/description
Example 位操作+序列
Counting Bits
题目描述
思路
-
题目分析
- 对于每个数 0<=i<=N,直接求 i 的二进制表示里面有多少个 1
- 二进制表示算法
- i mod 2 是最低位
- i= floor(1/2),如果 i=0 结束,否则回到 1
-
动态规划
-
确定状态
-
观察一个数的二进制 (170)10=(10101010)2
-
最后一步
观察这个数的最后一个二进制位,去掉它,还剩下多少 1
- (170)10=(10101010)2
- (85)10=(1010101)2
-
子问题 - N 的二进制去掉最后一位 N mod 2,设新的数是 Y=X>>1
状态:设 f[i]表示 i 的二进制表示中有多少个 1;
-
-
转移方程
f [ i ] = f [ i > > 1 ] + ( i m o d 2 ) f[i]=f[i>>1]+(i mod 2) f[i]=f[i>>1]+(imod2)
- 初始条件和边界情况
f[0]=0
- 计算顺序
-
小知识点:和位操作相关的动态规划一般用值作为状态
有状态的序列型动态规划
-
给定一个序列
-
动态规划方程 f[i]中的下表 i 表示前 i 个元素 a[0],a[1]…a[i-1]的某种性质
-
- 坐标型的 f[i]表示以 ai为结尾的某种性质
-
-
初始化中,f[0]表示空序列的性质
- 坐标型动态规划的初始条件 f[0]就是指以 ai为结尾的子序列的性质
Paint House II
题目描述
这里有 n 个房子在一列直线上,现在我们需要给房屋染色,共有 k 种颜色。每个房屋染不同的颜色费用也不同,你需要设计一种染色方案使得相邻的房屋颜色不同,并且费用最小。
费用通过一个 nxk 的矩阵给出,比如 cost[0][0]表示房屋 0 染颜色 0 的费用,cost[1][2]表示房屋 1 染颜色 2 的费用。
思路
在 Paint House 的思路上优化?
代码
lintcode 练习题 https://www.lintcode.com/problem/paint-house-ii/description
House Robber
题目描述
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。
代码
leetcode https://leetcode-cn.com/problems/house-robber/
Best Time to Buy and Sell Stock
题目描述
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
注意:你不能在买入股票前卖出股票。
思路
- 题目分析
显然需要找到最小的买入价格
f[i]截止第 i 天,最小的买入价格
代码
leetcode https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/
Best Time to Buy and Sell Stock II
题目描述
思路
[7,1,2,9,0]
-1 -1+2 -2+9 ---->-1+9
代码
leetcode https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-ii/
Best Time to Buy and Sell Stock III
题目描述
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)
思路
需要记录买卖次数
次数限制和股票持有状态作为加入状态数组
代码
leetcode https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iii/
Best Time to Buy and Sell Stock IV
题目描述
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。
注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
思路
代码
leetcode 练习 https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock-iv/submissions/
最长序列型动态规划(划分)
-
给定一个序列
-
要求找出符合条件的最长子序列
-
方法
- 记录以每个元素 i 结尾发最长子序列的长度
- 计算时,在 i 之前枚举子序列上一个人元素是哪个
Longest Increasing Subdequence
题目描述
给定一个无序的整数数组,找到其中最长上升子序列的长度。
//贪心+二分的算法思想性能更高
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
思路
枚举前面所以小于 ai 的数 aj 的 f[j]
代码
leetcode 练习 https://leetcode-cn.com/problems/longest-increasing-subsequence/
Russian Doll Envelopes
题目描述
给定一些标记了宽度和高度的信封,宽度和高度以整数对形式 (w, h) 出现。当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。
请计算最多能有多少个信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。
说明:
不允许旋转信封
思路
sort+ Longest Increasing Subdequence 的思想
代码
leetcode 练习题 https://leetcode-cn.com/problems/russian-doll-envelopes/
Perfect Squares(划分)
题目描述
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, …)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
思路
-
确定状态
- 最后一步
关注最优策略中最后一个完全平方数 j^2
最优策略中 n-j^2 也一定被划分成最少的完全平方数之和。
需要知道 n-j^2 最少被分成几个完全平方数之和
---->枚举 j
- 子问题
状态:设 f[i]表示 i 最少被分成几个完全平方数之和
//如果分成 j 个是定值,那么需要 f[i][j]来记录能不能分成
- 转移方程
f [ i ] = m i n { f [ i − j ∗ j + 1 ] i < = j ∗ j < = i } f[i]=min\{f[i-j*j+1] i<=j*j<=i\} f[i]=min{f[i−j∗j+1]i<=j∗j<=i}
- 初始条件和边界情况
f[0]=0;
- 计算顺序
代码
leetcode https://leetcode-cn.com/problems/perfect-squares/
Palindrome Parttitioning II
题目描述
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回符合要求的最少分割次数。
思路
-
确定状态
- 最后一步
关注最优策略中最后一段回文串,设为 s[j…N-1]
需要知道前 j 个字符串最少可以划分成几个字符串
- 子问题
状态:设 s 前 j 个字符串最少可以划分成 f[j]个字符串
- 转移方程
KaTeX parse error: Expected '}', got 'EOF' at end of input: … 枚举 j(0--->i-1)
- 初始条件和边界情况
初始条件 f[0]=0;
- 计算顺序
PS:
如何判断回文串?
回文串分两种:长度为奇数、长度为偶数
奇数:对称轴在一个元素上
偶数:对称轴在两个元素之间
定义中轴往两边扩展。
O(N^2)
代码
leetcode https://leetcode-cn.com/problems/palindrome-partitioning-ii/submissions/
小结
-
将一个序列或字符串分成若干满足要求的片段
-
方法: 最后一步—>最后一段,枚举最后一段的起点
-
如果题目不指定段数,用 f[i]表示前 i 个元素分段后的可行性/最值
-
如果指定段数,用 f[i][j]表示前 i 个元素分成 j 段后的可行性/方式数/最值
博弈型动态规划
博弈是两方游戏,一方线下,在一定规则下依次出招,满足一定条件,则一方胜
博弈的动态规划通常从第一步分析,而不是最后一步
- 因为局面越来越简单,石子数越来越少
Coins in a line (取石子)
题目描述
有 n 个硬币排成一条线。两个参赛者轮流从右边依次拿走 1 或 2 个硬币,直到没有硬币为止。拿到最后一枚硬币的人获胜。
请判定 先手玩家 必胜还是必败?
若必胜, 返回 true, 否则返回 false
思路
数学解法
if(i mod 3 ==0) false
动态规划解法
-
确定状态
- 最后一步
A 先手,选择让自己赢的一步
轮到 B 时,相当于 B 先手石子为 N-1 or N-2让自己赢的一步: 走了这步之后,对手面对剩下的石子,对手必输
- 子问题
状态:设 f[i]表示面对 i 个石子,是否先手必胜 f[i]=true/false
-
转移方程
f [ i ] = { t r u e , f[i-1]==false AND f[i-2]==false拿 1 or 2 都必胜 t r u e , f[i-1]==false AND f[i-2]==true 拿 1 必胜 t r u e , f[i-1]==true AND f[i-2]==false 拿 2 都必胜 f a l s e , f[i-1]==true AND f[i-1]==true 必败 f[i]=\begin{cases} true,& \text{f[i-1]==false AND f[i-2]==false} \text{拿 1 or 2 都必胜}\\ true,& \text{f[i-1]==false AND f[i-2]==true } \text{拿 1 必胜}\\ true,& \text{f[i-1]==true AND f[i-2]==false } \text{拿 2 都必胜}\\ false,& \text{f[i-1]==true AND f[i-1]==true } \text{必败}\\ \end{cases} f[i]=⎩⎪⎪⎪⎨⎪⎪⎪⎧true,true,true,false,f[i-1]==false AND f[i-2]==false拿 1 or 2 都必胜f[i-1]==false AND f[i-2]==true 拿 1 必胜f[i-1]==true AND f[i-2]==false 拿 2 都必胜f[i-1]==true AND f[i-1]==true 必败
-
初始条件和边界情况
-
计算顺序
代码
lintcode https://www.lintcode.com/problem/coins-in-a-line/description
背包问题
- 你有一个背包,背包有最大承重
- 商店里有若干物品
- 每个物品有价值和重量
- 目标:不撑爆背包的前提下
- 装下更多重量物品
- 装下最大总价值的物品
- 有多少种方式正好带走一背包
直觉
- 逐个放入物品,看是否能放入
关键点
- 还有几个物品
- 还剩多少承重
Backpack
题目描述
在 n 个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为 m,每个物品的大小为 A[i]
思路
对于每个总重量,有没有方案能解决
-
确定状态
- 最后一步
最后一个物品 An 是否进入背包
情况 1:如果前面 N-1 个物品能拼出 W
情况 2: 前 N-1 个物品能拼出 W-An-1,再加上最后的物品 An 拼出 W枚举 W
- 子问题
状态:f[i][w]=能否用前 i 个物品拼出 W boolean
常见误区:用 f[i]表示前 i 个物品拼出的最大重量
自己的想法:如果要用 f[i]的话先进行排序 但是这样复杂度有 O(m*nlogn)!!!
- 转移方程
$ f[i][w]=f[i-1][w] or f[i-1][w-ai]$
- 初始条件和边界情况
代码
lintcode https://www.lintcode.com/problem/backpack/
backpack V
题目描述
给出 n 个物品, 以及一个数组, nums[i] 代表第 i 个物品的大小, 保证大小均为正数, 正整数 target 表示背包的大小, 找到能填满背包的方案数。
每一个物品只能使用一次
思路
-
题目分析
要求和和是 Target----->背包问题
要求多少种组合
上一题记录能拼出哪些重量
本题记录拼出每种重量的方式数 -
动态规划解题
-
确定状态
- 最后一步
首先,需要知道 N 个物品有多少种方式拼出 w
第 N 个物品是否进入背包
情况 1:用前 N-1 个物品拼出 W
情况 2:前 N-1 个物品能拼出 W-an,前 N 的方式数=情况 1+情况 2
- 子问题
- 最后一步
状态:前 i 拼出 w 的方式数
- 转移方程
f [ i ] [ w ] = f [ i − 1 ] [ w ] + f [ i − 1 ] [ w − a [ i ] ] f[i][w]=f[i-1][w]+f[i-1][w-a[i]] f[i][w]=f[i−1][w]+f[i−1][w−a[i]]
- 初始条件和边界情况
f[0][0]=1;
f[0][1…m]=0;
- 计算顺序
空间优化
滚动数组
代码
lintcode https://www.lintcode.com/problem/backpack-v/description
Combination Sum IV
题目描述
给出一个都是正整数的数组 nums,其中没有重复的数。从中找出所有的和为 target 的组合个数。
一个数可以在组合中出现多次。
数的顺序不同则会被认为是不同的组合
思路
不能按前几个物品的思路
-
确定状态
- 最后一步
最后一个的重量是多少 类似于 coin changes 的思想
- 子问题
设 f[i]=有多少种组合
- 转移方程
f [ i ] = f [ i − a 0 ] + . . . . . f [ i − a n ] f[i]=f[i-a0]+.....f[i-an] f[i]=f[i−a0]+.....f[i−an]
-
初始条件和边界情况
-
计算顺序
代码
leetcode https://www.lintcode.com/problem/combination-sum-iv/description
背包型动态规划
每个物品的方案总重量都是 0-M
Backpack II
题目描述
有 n 个物品和一个大小为 m 的背包. 给定数组 A 表示每个物品的大小和数组 V 表示每个物品的价值.
问最多能装入背包的总价值是多大?
思路
对每一个总重量知道对应的最大价值是多少。
-
确定状态
- 最后一步
需要知道能否拼出重量
对于每一个能拼出的重量最大总价值是多少看最后一个物品是否进入背包
选择一:如果前面 N-1 物品能拼出 W,最大总价值是 V,前 N 个物品也能拼出 W 且总价值是 V
选择二:如果 N-1 能拼出 W-an,最大总价值是 V-vn…- 子问题
-
转移方程
d p [ i ] [ j ] = M a t h . m a x ( d p [ i − 1 ] [ j − A [ i − 1 ] ] + V [ i − 1 ] , d p [ i ] [ j ] ) dp[i][j]=Math.max(dp[i-1][j-A[i-1]]+V[i-1],dp[i][j]) dp[i][j]=Math.max(dp[i−1][j−A[i−1]]+V[i−1],dp[i][j])
代码
lintcode https://www.lintcode.com/problem/backpack-ii/description
BackPack III
题目描述
在 II 的基础下,每个物品有无穷多个
求最多能带走的物品
思路
- 容易错误: 以贪心的思想去解决
- 动态规划解
f[i][w]= 用前 i 种物品拼出重量 w 时的最大值。
f[i][w]=maxk>=0{f[i-1][w-kan]+kVn}
=> f[i][w]=max{f[i-1][w],f[i][w-an]+vi}
代码
区间动态规划
给定一个序列/字符串,进行一些操作
最后一步会将序列/字符串去头/去尾
剩下的会是一个区间[i,j]
状态自然定义为 f[i][j],表示面对子序列[i…j]的最优性质
题目描述
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
思路
-
确定状态
- 最后一步
最优策略,回文串为 T,长度为 M
情况 1:M=1
情况 2:M>1,那么必然存在 T[0]=T[M-1]设 T[0]是 S[i] ,T[m-1]是 S[j]
T 剩下的部分[1…M-2]仍然是一个回文串,而且是 S[i+1…j-1]的最长回文子串
- 子问题
要求 S[i…j]的最长回文子串
如果 S[i]=S[j],需要知道 S[i+1…j-1]的最长回文子串
否则答案是 S[i+1…j]的最长回文子串 -
转移方程
f [ i ] [ j ] = m a x { f [ i + 1 ] [ j ] , f [ i ] [ j − 1 ] , f [ i + 1 ] [ j − 1 ] + 2 ∣ s i = s j } f[i][j]=max\{f[i+1][j],f[i][j-1],f[i+1][j-1]+2|si=sj\} f[i][j]=max{f[i+1][j],f[i][j−1],f[i+1][j−1]+2∣si=sj}
- 初始条件和边界情况
f[0][0]=f[1][1]= …=1;
S[i]==S[i+1] f[i][i+1]=2; else =1;
- 计算顺序
按照 j-i 的大小去算
代码
leetcode https://leetcode-cn.com/problems/longest-palindrome/
习题
-
Maximum Product Subarray
https://leetcode-cn.com/problems/maximum-product-subarray/
容易在 ak 正负数的情况下纠结,并且返回值不应当是 f[size]
-
Decode ways
https://www.lintcode.com/problem/decode-ways/description
思路:
放入最后一个字母,
如果字母是 0-9,那么此字母是单独的一个,f[i]=f[i-1];
如果这个字母和前一个字母能凑成<"26"的字符串,那么 f[i]的值应当再加上 f[i-2].因为已经与前一个凑成了字符串,就相当于新增了 1*f[i-2]中方式需要注意 0 的处理