outline
滚动数组
- house robber I/II
- Maximal Square
记忆化搜索
- longest increasing subsequence
- coins in a line
动态规划四要素
- 状态
- 转移方程
- 初始化
- 答案
滚动数组优化
f[i] = max(f[i-1],f[i-2]+A[i]);
转化为:
f[i%2] = max(f[(i-1)%2],f[(i-2)%2])
滚动数组优化不会对时间复杂度进行优化,而只是对空间进行优化
例题1. House Robber
给定一个数组,代表抢劫商店可以获得的价值,不可以抢劫相邻的商店,计算能够获得的最大价值
思路:
序列型dp
f[i]
代表抢劫前i个商店能够获得的最大值
对于店铺i可以有抢和不抢两种情况:
- 如果抢,则不能抢f[i-1],则前i个商店的最大值为前i-2个商店的最大值加上第i个商店的价值
- 如果不抢,则前i个商店的最大值=前i-1个商店的最大值
取两种情况的最大值
转移方程:f[i] = max(f[i-1],f[i-2]+nums[i])
代码:
|
用滚动数组优化
转移方程:f[i] = max(f[i-1],f[i-2]+nums[i])
根据前面的分析,对于每一个商店i,我们只需要考虑商店i-1和i-2,也就是状态i只与它的前两个状态有关,所以我们只需要维护一个长度为2的数组来记录状态即可。
状态转移方程转化为f[i%2] = max(f[(i-1)%2],f[(i-2)%2]+nums[i])
代码:
|
这道题是状态只与前两个状态有关,如果推广到一般,如果状态i与前k个状态有关,则有:
f[i%k] = max(f[(i-1)%k],f[(i-2)%k]+nums[i])
例题2. House Robber II
首尾商店算相邻的商店,也就是商店是一个环。
思路:
对于成环的问题有两个小技巧:
- 拆分数组的方式,将计算一个数组的问题转化成计算两个数组:
- 去掉最后一个元素
- 去掉第一个元素
- 将原数组copy一份,首尾相接,本题不适用
代码:
|
滚动数组优化:
|
例题3. Maximal Square
Given a 2D binary matrix filled with 0’s and 1’s, find the largest square containing only 1’s and return its area.
For example, given the following matrix:
> 1 0 1 0 0> 1 0 1 1 1> 1 1 1 1 1> 1 0 0 1 0>>
>
Return 4.
找到全为1的正方形的最大面积
思路:
定位正方形,需要一个三维数组[x,y,a]
其中xy为正方形顶点坐标,a为边长
baseline:
x,y,a三层循环从0到n,然后x,y从1到a一共5层循环,复杂度
dp
对于一个点(i,j)
(nums[i,j] = 1),如要计算以其为右下角的最大正方形边长,需要考虑三个点:
- 以(i-1,j-1)为右下角的最大正方形边长
- 以(i,j-1)为最右点的矩形长度
- 以(i-1,j)为最下点的矩形长度
取三个值中最小的+1就是以(i,j)为右下角的矩形最大边长
因此:
状态:
f[i][j]
表示以以(i,j)为右下角的矩形最大边长转移方程:
if(matrix[i][j] == 1){f[i][j] = max(f[i-1],[j-1],up[i-1][j],left[i][j-1])+1}if(matrix[i][j] == 0){f[i][j] = 0;}改进:
上面的方法除了维护f之外,还需要维护up和left数组,其实可以直接用f来代替up和left。
if(matrix[i][j] == 1){f[i][j] = max(f[i-1],[j-1],f[i-1][j],f[i][j-1])+1}if(matrix[i][j] == 0){f[i][j] = 0;}初始化
f[i][0] = matrix[i][0]f[0][j] = matrix[0][j]答案
max(f[i][j])
代码:
|
滚动数组优化:
对于每一个元素(i,j),只与它前一行和前一列的元素有关,与其前两行的元素无关,因此可以对其行进行滚动数组优化
转移方程变为:
|
代码:
|
follow up:
01矩阵里面找一个,对角线全为1, 其他为0的正方形
转移方程:
|
二维动态规划空间优化(二维滚动数组)总结
这类题目特点:
f[i][j]
= 由f[i-1]行 来决定状态, 第i行跟 i-1行之前毫无关系, 所以状态转变为:
f[i%2][j]
= 由f[(i-1)%2]行来决定状态
还有一些题目可以用滚动数组进行优化:
习题1. Unique Paths
习题2. Minimum Path Sum
习题3. Edit Distance
记忆化搜索
- 本质上是动态规划
- 动态规划就是解决了重复计算的搜索
- 动态规划的实现方式:
- 循环(从小到大递推)
- 记忆化搜索(从大到小)
- 画搜索树
- 滚动数组优化,万金油
例题1. Longest Increasing Subsequence
例题2. Longest Increasing Path in a Matrix
Given an integer matrix, find the length of the longest increasing path.
From each cell, you can either move to four directions: left, right, up or down. You may NOT move diagonally or move outside of the boundary (i.e. wrap-around is not allowed).
Example 1:
> nums = [> [9,9,4],> [6,6,8],> [2,1,1]> ]>>
>
Return
4
The longest increasing path is[1, 2, 6, 9]
.Example 2:
> nums = [> [3,4,5],> [3,2,6],> [2,2,1]> ]>>
>
Return
4
The longest increasing path is[3, 4, 5, 6]
. Moving diagonally is not allowed.
分析:
用for循环解决这道题不知道起点在哪,即初始状态找不到
用搜索:
用搜索的方式,f[i][j]
为以点(i,j)为结尾的最长递增子序列长度,则需要看其上下左右四个点中没有被访问过的点,计算以这些点为结尾的最长递增子序列长度,这样就会有很多重复计算的点,因此可以用记忆化搜索,把计算过的点的信息存储下来,后面用到的时候直接查找即可。
记忆化搜索:
- 状态:
f[i][j]
为以点(i,j)为结尾的最长递增子序列长度 - 转移方程:
- a = +-1;b = 0;
- b = +-1;a = 0;
f[i][j] =f[x+a][x+b]+1,if(a[x][y]>a[x+a][x+b])
- 初始化
f[i][j] = 1
- 答案
max f[i][j]
代码:
|
总结:什么时候用记忆化搜索
- 状态转移特别麻烦,不是顺序性。
- 初始化状态不是很容易找到。
- 从大到小
博弈类DP
连两个人做游戏
解决博弈类DP通常用记忆化搜索的方法
例题1.coins in a line
There are n coins in a line. Two players take turns to take one or two coins from right side until there are no more coins left. The player who take the last coin wins.
Could you please decide the first play will win or lose?
Example
n =
1
, returntrue
.n =
2
, returntrue
.n =
3
, returnfalse
.n =
4
, returntrue
.n =
5
, returntrue
.
两个人轮流选取硬币,每次只能选1个或者2个,取到最后一个石子的人获胜,给定石子数量,返回第一个选的人(先手)能否获胜。
分析:
|
代码:
|
例题2. coins in a line II
There are n coins with different value in a line. Two players take turns to take one or two coins from left side until there are no more coins left. The player who take the coins with the most value wins.
Could you please decide the first player will win or lose?
Example
Given values array A =
[1,2,2]
, returntrue
.Given A =
[1,2,4]
, returnfalse
.
给定硬币序列,硬币具有价值,两个人轮流选取硬币,每次可以选1个或者2个获得相应的价值,最终获得价值多的人获胜,问先手能够获胜
分析:
|
代码:
|
例题3. Coins in a Line III
There are n coins in a line. Two players take turns to take a coin from one of the ends of the line until there are no more coins left. The player with the larger amount of money wins.
Could you please decide the first player will win or lose?
Example
Given array A = [3,2,2], return true.
Given array A = [1,2,4], return true.
Given array A = [1,20,4], return false.
和II一样coin带有价值,选取时可以从头部或者尾部选取一个coin。
分析:
|
代码:
|
区间型DP
特点:
- 求一段区间的解max/min/count
- 转移方程通过区间更新
- 从大到小更新,用记忆化搜索
例题1. Stone Game
There is a stone game.At the beginning of the game the player picks
n
piles of stones in a line.The goal is to merge the stones in one pile observing the following rules:
- At each step of the game, the player can merge two adjacent piles to a new pile.
- The score is the number of stones in the new pile.
You are to determine the minimum of the total score.
Example
For
[4, 1, 1, 4]
, in the best solution, the total score is18
:
> 1. Merge second and third piles => [4, 2, 4], score +2> 2. Merge the first two piles => [6, 4],score +6> 3. Merge the last two piles => [10], score +10>>
>
Other two examples:
[1, 1, 1, 1]
return8
[4, 4, 5, 9]
return43
给定数组,每次合并相邻元素直至全部合并,每次合并需要花费两个元素价值之和,返回最小的花费
分析:
|
- State:
dp[i][j]
表示把第i到第j个石子合并到一起的最小花费
- Function:
- 预处理
sum[i,j]
表示i到j所有石子价值和 dp[i][j] = min(dp[i][k]+dp[k+1][j]+sum[i,j])
对于所有k
属于{i,j}
- 预处理
- Intialize:
- for each i
dp[i][i] = 0
- for each i
- Answer:
dp[0][n-1]
代码:
|
例题2. Burst Balloons
Given
n
balloons, indexed from0
ton-1
. Each balloon is painted with a number on it represented by arraynums
. You are asked to burst all the balloons. If the you burst ballooni
you will getnums[left] * nums[i] * nums[right]
coins. Hereleft
andright
are adjacent indices ofi
. After the burst, theleft
andright
then becomes adjacent.Find the maximum coins you can collect by bursting the balloons wisely.
Note:
(1) You may imaginenums[-1] = nums[n] = 1
. They are not real therefore you can not burst them.
(2) 0 ≤n
≤ 500, 0 ≤nums[i]
≤ 100Example:
Given
[3, 1, 5, 8]
Return
167
> nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []> coins = 3*1*5 + 3*5*8 + 1*3*8 + 1*8*1 = 167>
戳气球,每次戳破气球i可以获得价值nums[i-1]*nums[i]&nums[i+1],返回可以获得的最大利润。
分析:
|
State:
dp[i][j]
表示把第i到第j个气球打爆获得的最大价值
Function:
计算
dp[i][j]
需要遍历ij区间内所有的点,看做最后一个从ij区间中删除的点,删除该点时获得的价值为nums[i-1]*nums[k]*nums[j+1]
,然后再删除该点之前,其左边和右边的节点已经全部被删除,所以转移方程为:dp[i][j] = max(nums[i-1]*nums[k]*nums[j+1] + dp[i][k-1] + dp[k+1][j])
对于所有k
属于{i,j}
Intialize:
- for each i
dp[i][i] = 0
- for each i
Answer:
dp[0][n-1]
代码:
|
例题3. Scramble String
Given a string s1, we may represent it as a binary tree by partitioning it to two non-empty substrings recursively.
Below is one possible representation of s1 =
"great"
:
> great> / \> gr eat> / \ / \> g r e at> / \> a t>>
>
To scramble the string, we may choose any non-leaf node and swap its two children.
For example, if we choose the node
"gr"
and swap its two children, it produces a scrambled string"rgeat"
.
> rgeat> / \> rg eat> / \ / \> r g e at> / \> a t>>
>
We say that
"rgeat"
is a scrambled string of"great"
.Similarly, if we continue to swap the children of nodes
"eat"
and"at"
, it produces a scrambled string"rgtae"
.
> rgtae> / \> rg tae> / \ / \> r g ta e> / \> t a>>
>
We say that
"rgtae"
is a scrambled string of"great"
.Given two strings s1 and s2 of the same length, determine if s2 is a scrambled string of s1
分析:
对于s1和s2,找不同的分割点k,将其分别分为两个子数组。若s1的两个子数组和对应的s2的两个子数组都是scramble的,则s1和s2就是scramble的。例如,s1[0…i]被分为0…k,k+1…i,其对应的s2子数组为0…k,k+1…i或者0…i-k-1, i-k…i(即s2的前k个元素或者后k个元素对应于s1的前k个元素,比如s1=abc,s2=acb,第一层递归时比较的是s1左边的子数组和s2左边的子数组以及s1右边的子数组和s2右边的子数组,第二层递归比较右边两个子数组时,就要比较s1右边子数组的左边子数组”b”和s2右边子数组的右边子数组”b”),只要两种情况里面有一种满足,则s1和s2就是scramble的。
( isScramble(s2[0…k], s1[0…k]) && isScramble(s2[k+1…j], s1[k+1…i]) ) || ( isScramble(s2[0…k], s1[i-k…i]) && isScramble(s2[k+1…j], s1[0…i-k-1]) ),(k = 0,1,2 … i-1,k相当于字符串的分割点)
因此可以用记忆化搜索来保存子问题,设dp[i][j][k]表示s2从j开始长度为k的子串是否可以由s1从i开始长度为k的子串转换而成:
状态:
dp[i][j][k]
表示s1从第i个开始s2从第j个开始的k个字母是否是scramble string
因此状态转移方程为:
|
初始化:
dp[i][j][1] = s1[i]==s[j].
答案:
dp[0][0][len]
代码:
|
递归也可以做
|
背包类DP
特点:
- 用值作为DP维度
- DP过程就是填写矩阵
- 可以用滚动数组优化
例题1. Backpack
Given n items with size Ai, an integer m denotes the size of a backpack. How full you can fill this backpack?
Example
If we have
4
items with size[2, 3, 5, 7]
, the backpack size is 11, we can select[2, 3, 5]
, so that the max size we can fill this backpack is10
. If the backpack size is12
. we can select[2, 3, 7]
so that we can fulfill the backpack.You function should return the max size we can fill in the given backpack.
给定背包容量和物品数组,返回最大的
分析:
不可以用贪心,比如[8,7,5],容量为12
如果用贪心法放了8就放不下其他的了,但是放7和5得到的总容量更大,所以不能用贪心法。
|
代码:
|
例题2. BackPack马甲变换1,硬币凑整
给定面值1,2,5,10的硬币无穷多个,请问能够凑成80元的方案总数。
分析:
|
代码:
|
例题3. Coin Change
You are given coins of different denominations and a total amount of money amount. Write a function to compute the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return
-1
.Example 1:
coins =[1, 2, 5]
, amount =11
return3
(11 = 5 + 5 + 1)Example 2:
coins =[2]
, amount =3
return-1
.
给定硬币面值,和总价值,返回能凑成总价值所用硬币数的最小值
思路:
和上一题思路类似
state:
dp[i][j]
表示用i种硬币凑成j元所需要的最少硬币数量
function:
|
initial:
|
answer:
dp[coins.length][amount]
代码:
|
优化:
例题4. BackPack 马甲变换2
把一个数组[1,24,5,6]尽量平分
可以转化为背包问题:
数组总和为36,一半为18
背包容量为18,用数组中的数字尽量将背包装满
Partition Equal Subset Sum
Given a non-empty array containing only positive integers, find if the array can be partitioned into two subsets such that the sum of elements in both subsets is equal.
Note:
- Each of the array element will not exceed 100.
- The array size will not exceed 200.
Example 1:
> Input: [1, 5, 11, 5]>> Output: true>> Explanation: The array can be partitioned as [1, 5, 5] and [11].>>
>
Example 2:
> Input: [1, 2, 3, 5]>> Output: false>> Explanation: The array cannot be partitioned into equal sum subsets.>
返回是否能够取到整个数组的一半。
|
例题5. Backpack II
给定物品占空间和物品价值数组,背包容量,如何装能够使得背物品价值最高
不可以用贪心算法
分析:
|
代码:
|
例题6. K Sum
给定数组A=[1,2,3,4],k=2,target=5
在A中选2个元素,和为5,返回方案个数
思路:
|
代码:
|
优化:
例题7. Minnimum Adjus
总结
- 区间类DP
- 从大到小去思考,将区间划分成小区间
- 主要通过记忆化搜索来解决
- 背包类DP
- 用值座位DP维度
- 用for循环填写矩阵数值
- 可以用滚动数组做优化