一、线型动态规划
1.1、单串问题
1.1.1、最经典单串LIS系列
题目:300. 最长递增子序列
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
解析
定义 d p [ i ] dp[i] dp[i] 为考虑前 i 个元素,以第 i 个数字结尾的最长上升子序列的长度,注意 n u m s [ i ] nums[i] nums[i] 必须被选取。
我们从小到大计算 d p dp dp 数组的值,状态转移方程为:(注意这里有一个遍历)
d p [ i ] = m a x ( d p [ j ] ) + 1 dp[i] = max(dp[j]) + 1 dp[i]=max(dp[j])+1,其中 0 ≤ j < i 0 \leq j < i 0≤j<i, 且 n u m [ j ] < n u m [ i ] num[j]<num[i] num[j]<num[i]
最后,整个数组的最长上升子序列即所有 d p [ i ] dp[i] dp[i]中的最大值。
给定一个未排序的整数数组,找到最长递增子序列的个数。
解析
这道题目我们要一起维护两个数组。
dp[i]含义和上题一样,count[i]表示以nums[i]为结尾的字符串,最长递增子序列的个数为count[i]
我们要考虑两个维度,一个是dp[i]的更新,一个是count[i]的更新。
那么如何更新count[i]呢?
-
那么在nums[i] > nums[j]前提下,如果在[0, i-1]的范围内,找到了j,使得dp[j] + 1 > dp[i],说明找到了一个更长的递增子序列。那么以j为结尾的子串的最长递增子序列的个数,就是最新的以i为结尾的子串的最长递增子序列的个数,即:count[i] = count[j]。
-
在nums[i] > nums[j]前提下,如果在[0, i-1]的范围内,找到了j,使得dp[j] + 1 == dp[i],说明找到了两个相同长度的递增子序列。那么以i为结尾的子串的最长递增子序列的个数 就应该加上以j为结尾的子串的最长递增子序列的个数,即:count[i] += count[j];
还没做
1.1.2、最大子数组和系列
题目:53. 最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
解析
我们用 d p [ i ] dp[i] dp[i] 代表以第 i i i 个数结尾的「连续子数组的最大和」,那么很显然我们要求的答案就是:
动态规划转移方程:
d p [ i ] = m a x { d p [ i − 1 ] + n u m s [ i ] , n u m s [ i ] } dp[i]=max\{dp[i-1]+nums[i],nums[i]\} dp[i]=max{dp[i−1]+nums[i],nums[i]}
题目:152. 乘积最大子数组
给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
解析
一开始觉得好简单,这不和上一题一样吗,动态规划转移方程:
d p [ i ] = m a x { d p [ i − 1 ] + n u m s [ i ] , n u m s [ i ] } dp[i]=max\{dp[i-1]+nums[i],nums[i]\} dp[i]=max{dp[i−1]+nums[i],nums[i]}
结果发现这一题比较坑,比如[-3,4,-3]的结果是36,不知道你看出来没有,他还有负负得正这么一出。接替方法自己去看题解吧。
1.1.3、打家劫舍系列
题目:198. 打家劫舍
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
解析
很简单,后面再碰到简单的我就不写解析了
题目:213. 打家劫舍 II
你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都 围成一圈 ,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警 。
给定一个代表每个房屋存放金额的非负整数数组,计算你 在不触动警报装置的情况下 ,能够偷窃到的最高金额。
解析
假设有
n
u
m
s
=
[
1
,
2
,
3
…
…
n
]
nums=[1,2,3……n]
nums=[1,2,3……n],即
n
n
n个房子首尾循环,定义:
d
p
f
[
n
]
dpf[n]
dpf[n]表示
n
u
m
s
=
[
1
,
2
,
3
…
…
n
]
nums=[1,2,3……n]
nums=[1,2,3……n],房子首尾循环且偷窃第n个房子得到的最高金额
d
p
k
[
n
]
dpk[n]
dpk[n]表示
n
u
m
s
=
[
1
,
2
,
3
…
…
n
]
nums=[1,2,3……n]
nums=[1,2,3……n],房子首尾不循环且偷窃第n个房子得到的最高金额
d
p
g
[
n
]
dpg[n]
dpg[n]表示
n
u
m
s
=
[
0
,
2
,
3
…
…
n
]
nums=[0,2,3……n]
nums=[0,2,3……n],房子首尾不循环且偷窃第n个房子得到的最高金额,即第一个房子不偷
那么 d p f [ i ] = m a x ( d p f [ i ] , d p k [ i − 1 ] , d p g [ i − 2 ] + n u m s [ i ] ) dpf[i]=max(dpf[i],dpk[i-1],dpg[i-2]+nums[i]) dpf[i]=max(dpf[i],dpk[i−1],dpg[i−2]+nums[i]),当然这样写并不规范,因为后面的 i i i是需要从0一直遍历到 i − 2 i-2 i−2的。
详细点就是:
- 选n的时候,相邻的1和n-1就不能选,那就是 d p g [ j ] , 0 ≤ j ≤ n − 2 dpg[j],0\leq j\leq n-2 dpg[j],0≤j≤n−2中的最高金额再加上 n u m s [ n ] nums[n] nums[n]
- 不选n时候,就是 d p k [ j ] , 0 ≤ j ≤ n − 1 dpk[j],0\leq j\leq n-1 dpk[j],0≤j≤n−1中的最高金额,注意这里1和n-1可以同时选,所以用首尾不循环算法
1.1.4、变形,需要两个位置的情况: dp[i][j] 以 j, i 结尾
如果序列 X 1 , X 2 , . . . , X n X_1, X_2, ..., X_n X1,X2,...,Xn 满足下列条件,就说它是 斐波那契式 的:
- n >= 3
- 对于所有 i + 2 <= n,都有 X i + X i + 1 = X i + 2 X_i + X_{i+1} = X_{i+2} Xi+Xi+1=Xi+2
给定一个严格递增的正整数数组形成序列,找到 A 中最长的斐波那契式的子序列的长度。如果一个不存在,返回 0 。
(回想一下,子序列是从原序列 A 中派生出来的,它从 A 中删掉任意数量的元素(也可以不删),而不改变其余元素的顺序。例如, [3, 5, 8] 是 [3, 4, 5, 6, 7, 8] 的一个子序列)
题目:1027. 最长等差数列
给定一个整数数组 A,返回 A 中最长等差子序列的长度。
回想一下,A 的子序列是列表 A [ i 1 ] , A [ i 2 ] , . . . , A [ i k ] A[i_1], A[i_2], ..., A[i_k] A[i1],A[i2],...,A[ik]其中 0 <= i 1 < i 2 < . . . < i k i_1 < i_2 < ... < i_k i1<i2<...<ik <= A.length - 1。并且如果 B[i+1] - B[i]( 0 <= i < B.length - 1) 的值都相同,那么序列 B 是等差的。
1.1.5、其他 分情况比较多 的问题
题目:32. 最长有效括号
给你一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长有效(格式正确且连续)括号子串的长度。
解析
我们定义 d p [ i ] dp[i] dp[i] 表示以下标 i i i 字符结尾的最长有效括号的长度。我们将 d p dp dp 数组全部初始化为 0 。显然有效的子串一定以 ‘)’ 结尾,因此我们可以知道以‘(’ 结尾的子串对应的 d p dp dp 值必定为 0 ,我们只需要求解‘)’ 在 d p dp dp 数组中对应位置的值。
-
s[i]=‘)’ 且 s[i−1]=‘(’,也就是字符串形如“……()”,我们可以推出: d p [ i ] = d p [ i − 2 ] + 2 dp[i]=dp[i−2]+2 dp[i]=dp[i−2]+2
-
s[i]=‘)’ 且 s[i−1]=‘)’,也就是字符串形如 “……))”,我们可以推出:
如果 s [ i − d p [ i − 1 ] − 1 ] = ‘ ( ’ s[i−dp[i−1]−1]=‘(’ s[i−dp[i−1]−1]=‘(’,那么 d p [ i ] = d p [ i − 1 ] + d p [ i − d p [ i − 1 ] − 2 ] + 2 dp[i]=dp[i−1]+dp[i−dp[i−1]−2]+2 dp[i]=dp[i−1]+dp[i−dp[i−1]−2]+2
题目:91. 解码方法
一条包含字母 A-Z 的消息通过以下映射进行了 编码 :
'A' -> 1
'B' -> 2
...
'Z' -> 26
要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,“111” 可以将 “1” 中的每个 “1” 映射为 “A” ,从而得到 “AAA” ,或者可以将 “11” 和 “1”(分别为 “K” 和 “A” )映射为 “KA” 。注意,“06” 不能映射为 “F” ,因为 “6” 和 “06” 不同。
给你一个只含数字的 非空 字符串 num ,请计算并返回 解码 方法的 总数 。
题目数据保证答案肯定是一个 32 位 的整数。
解析
dp[i]表示str[0…n]的译码方法总数
- 若 d p [ i ] = ′ 0 ′ dp[i]='0' dp[i]=′0′,那么若 d p [ i − 1 ] = ′ 1 ′ dp[i-1]='1' dp[i−1]=′1′ or ′ 2 ′ '2' ′2′,则 d p [ i ] = d p [ i − 2 ] dp[i]=dp[i-2] dp[i]=dp[i−2],否则返回0
- 若
d
p
[
i
−
1
]
=
′
1
′
dp[i-1]='1'
dp[i−1]=′1′,则
d
p
[
i
]
=
d
p
[
i
−
1
]
+
d
p
[
i
−
2
]
dp[i]=dp[i-1]+dp[i-2]
dp[i]=dp[i−1]+dp[i−2]
解释: s t r [ i − 1 ] str[i-1] str[i−1]与 s t r [ i ] str[i] str[i]分开编码为 d p [ i − 1 ] dp[i-1] dp[i−1],合并编码为 d p [ i − 2 ] dp[i-2] dp[i−2] - 若 d p [ i − 1 ] = ′ 2 ′ dp[i-1]='2' dp[i−1]=′2′且 ′ 1 ′ ≤ s t r [ i ] ≤ ′ 6 ′ '1'\leq str[i] \leq '6' ′1′≤str[i]≤′6′,那么 d p [ i ] = d p [ i − 1 ] + d p [ i − 2 ] dp[i]=dp[i-1]+dp[i-2] dp[i]=dp[i−1]+dp[i−2]
- 否则 d p [ i ] = d p [ i − 1 ] dp[i]=dp[i-1] dp[i]=dp[i−1]
1.1.6、带维度的单串问题(读不懂题目系列)
题目:887. 鸡蛋掉落
你将获得 K 个鸡蛋,并可以使用一栋从 1 到 N 共有 N 层楼的建筑。
每个蛋的功能都是一样的,如果一个蛋碎了,你就不能再把它扔下去。如果某个蛋扔下后没有摔碎,则可以将蛋捡起重复使用。
你知道存在楼层 F ,满足 0 <= F <= N 任何从高于 F 的楼层落下的鸡蛋都会碎,从 F 楼层或比它低的楼层落下的鸡蛋都不会破。
每次移动,你可以取一个鸡蛋(如果你有完整的鸡蛋)并把它从任一楼层 X 扔下(满足 1 <= X <= N)。
你的目标是确切地知道 F 的值是多少。
无论 F 的初始值如何,你确定 F 的值的最小移动次数是多少?
题目:403. 青蛙过河
一只青蛙想要过河。 假定河流被等分为 x 个单元格,并且在每一个单元格内都有可能放有一石子(也有可能没有)。 青蛙可以跳上石头,但是不可以跳入水中。
给定石子的位置列表(用单元格序号升序表示), 请判定青蛙能否成功过河(即能否在最后一步跳至最后一个石子上)。 开始时, 青蛙默认已站在第一个石子上,并可以假定它第一步只能跳跃一个单位(即只能从单元格1跳至单元格2)。
如果青蛙上一步跳跃了 k 个单位,那么它接下来的跳跃距离只能选择为 k - 1、k 或 k + 1个单位。 另请注意,青蛙只能向前方(终点的方向)跳跃。
请注意:
- 石子的数量 ≥ 2 且 < 1100;
- 每一个石子的位置序号都是一个非负整数,且其 < 231;
- 第一个石子的位置永远是0。
1.1.7、买股票系列
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
解析
dp[i][0] 下标为 i 这天结束的时候,不持股,手上拥有的现金数
dp[i][1] 下标为 i 这天结束的时候,持股,手上拥有的现金数
状态转移方程:
-
d p [ i ] [ 0 ] = M a t h . m a x ( d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 1 ] + p r i c e s [ i ] ) ; dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]); dp[i][0]=Math.max(dp[i−1][0],dp[i−1][1]+prices[i]);
-
d p [ i ] [ 1 ] = M a t h . m a x ( d p [ i − 1 ] [ 1 ] , − p r i c e s [ i ] ) ; dp[i][1] = Math.max(dp[i - 1][1], -prices[i]); dp[i][1]=Math.max(dp[i−1][1],−prices[i]);
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
解析
同上题,只是dp[i][1]的表达式出现了一点变化,状态转移方程:
-
d p [ i ] [ 0 ] = M a t h . m a x ( d p [ i − 1 ] [ 0 ] , d p [ i − 1 ] [ 1 ] + p r i c e s [ i ] ) ; dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]); dp[i][0]=Math.max(dp[i−1][0],dp[i−1][1]+prices[i]);
-
d p [ i ] [ 1 ] = M a t h . m a x ( d p [ i − 1 ] [ 1 ] , d p [ i − 1 ] [ 0 ] − p r i c e s [ i ] ) ; dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0]-prices[i]); dp[i][1]=Math.max(dp[i−1][1],dp[i−1][0]−prices[i]);
给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。
设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):
- 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
- 卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。
解析
又在上题的基础上变化了一点,
dp[i][0] 下标为 i 这天结束的时候,不持股且没有卖出,手上拥有的现金数
dp[i][1] 下标为 i 这天结束的时候,卖出手上的股票,不持股,手上拥有的现金数
dp[i][2] 下标为 i 这天结束的时候,持股,手上拥有的现金数
状态转移方程:
d
p
[
i
]
[
0
]
=
M
a
t
h
.
m
a
x
(
d
p
[
i
−
1
]
[
0
]
,
d
p
[
i
−
1
]
[
1
]
)
;
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1]);
dp[i][0]=Math.max(dp[i−1][0],dp[i−1][1]);
d
p
[
i
]
[
1
]
=
d
p
[
i
−
1
]
[
2
]
+
p
r
i
c
e
s
[
i
]
;
dp[i][1] = dp[i-1][2]+prices[i];
dp[i][1]=dp[i−1][2]+prices[i];
d
p
[
i
]
[
2
]
=
M
a
t
h
.
m
a
x
(
d
p
[
i
−
1
]
[
2
]
,
d
p
[
i
−
1
]
[
0
]
−
p
r
i
c
e
s
[
i
]
)
;
dp[i][2] = Math.max(dp[i - 1][2], dp[i - 1][0]-prices[i]);
dp[i][2]=Math.max(dp[i−1][2],dp[i−1][0]−prices[i]);
1.2、双串问题
1.2.1、最经典双串LCS系列
待续