动态规划题目汇总(持续更新)
动态规划的核心是写出问题的状态转移方程(即递推公式),有了状态转移方程,编写代码就是很简单的事了。
如何写出状态转移方程才是动态规划最关键的问题。
因此,这里只给出每个问题的状态转移方程,代码只是对数学公式的翻译。
1. 钢条切割
该问题是《算法导论》中动态规划章节的第一个例题,是一个很简单的动态规划问题。
问题描述
某公司购买长钢条,并将其切割成短钢条出售,切割工序本身无成本。
假定该公司出售一段长度为
i
i
i 英寸的钢条价格为
P
i
(
i
=
1
,
2
,
3
,
4
…
,
n
)
P_i (i=1,2,3,4…,n)
Pi(i=1,2,3,4…,n) ,求最佳切割方案,使总收益最大。
钢条价格表部分实例如下:
长度 i i i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
价格 P i P_i Pi | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 | 24 | 30 |
解法
设
r
n
r_n
rn 为长度为
n
n
n 的钢条能获得的最大收益,
P
i
P_i
Pi 为长度为
i
i
i 的钢条的价格。
则该问题的状态转移方程为:
r
n
=
{
0
,
n
=
0
max
{
P
i
+
r
n
−
i
∣
i
=
1
,
…
,
n
}
,
n
>
0
r_n = \begin{cases} 0, & n = 0 \\ \max \{P_i + r_{n - i} \ | \ i = 1, \dots, n\}, & n > 0 \end{cases}
rn={0,max{Pi+rn−i ∣ i=1,…,n},n=0n>0
2. 矩阵链乘法
该问题是《算法导论》中动态规划章节的第二个例题,难度适中。
问题描述
给定一个 n n n 个矩阵的序列(矩阵链) < A 1 , A 2 , … , A n > < A_1, A_2, \dots, A_n > <A1,A2,…,An> ,我们希望计算它们的乘积 A 1 A 2 … A n A_1 A_2 \dots A_n A1A2…An ,矩阵 A i A_i Ai 的大小为 p i − 1 × p i ( 1 ⩽ i ⩽ n ) p_{i - 1} \times p_i (1 \leqslant i \leqslant n) pi−1×pi(1⩽i⩽n) 。求完全括号化方案,使得乘积 A 1 A 2 … A n A_1 A_2 \dots A_n A1A2…An 所需的标量乘法次数最少。
解法
设
m
[
i
,
j
]
m[i, j]
m[i,j] 为乘积
A
i
A
i
+
1
…
A
j
A_i A_{i + 1} \dots A_j
AiAi+1…Aj 所需的最小计算量,
A
i
…
j
A_{i \dots j}
Ai…j 为矩阵
A
i
,
A
i
+
1
,
…
,
A
j
A_i, A_{i + 1}, \dots, A_j
Ai,Ai+1,…,Aj 相乘得到的矩阵,
A
i
…
k
A_{i \dots k}
Ai…k 与
A
k
+
1
…
j
A_{k + 1 \dots j}
Ak+1…j 相乘的代价为
p
i
−
1
p
k
p
j
p_{i - 1} p_k p_j
pi−1pkpj 。
则该问题的状态转移方程为:
m
[
i
,
j
]
=
{
0
,
i
=
j
min
{
m
[
i
,
k
]
+
m
[
k
+
1
,
j
]
+
p
i
−
1
p
k
p
j
∣
k
=
i
,
…
,
j
}
,
i
<
j
m[i, j] = \begin{cases} 0, & i = j \\ \min \{m[i, k] + m[k + 1, j] + p_{i - 1} p_k p_j \ | \ k = i, \dots, j \}, & i < j \end{cases}
m[i,j]={0,min{m[i,k]+m[k+1,j]+pi−1pkpj ∣ k=i,…,j},i=ji<j
3. 最长公共子序列
该问题是《算法导论》中动态规划章节的例题,也是《剑指 Offer II》中的第95题和 LeetCode 中的第1143题,在LeetCode中的难度为中等。
LeetCode 1143. 最长公共子序列
问题描述
一个序列的子序列是指这样一个新的序列:它是由原序列在不改变元素的相对顺序的情况下删除某些元素(也可以不删除任何元素)后组成的新序列。
最长公共子序列(Longest-Common-Subsequence, LCS)问题:给定两个序列
X
m
=
<
x
1
,
x
2
,
…
,
x
m
>
X_m = < x_1, x_2, \dots, x_m >
Xm=<x1,x2,…,xm> 和
Y
n
=
<
y
1
,
y
2
,
…
,
y
n
>
Y_n = < y_1, y_2, \dots, y_n >
Yn=<y1,y2,…,yn> ,求解最长的公共子序列的长度。
解法
设
x
i
x_i
xi 和
y
j
y_j
yj 分别为
X
m
X_m
Xm 和
Y
n
Y_n
Yn 的第
i
i
i 、
j
j
j 个元素,
X
i
X_i
Xi 和
Y
j
Y_j
Yj 分别为
X
m
X_m
Xm 和
Y
n
Y_n
Yn 的前
i
i
i 、
j
j
j 个元素构成的序列,
c
[
i
,
j
]
c[i, j]
c[i,j] 为
X
i
X_i
Xi 与
Y
j
Y_j
Yj 的最长公共子序列的长度。
则该问题的状态转移方程为:
c
[
i
,
j
]
=
{
0
,
i
=
0
或
j
=
0
c
[
i
−
1
,
j
−
1
]
+
1
,
x
i
=
y
j
max
{
c
[
i
−
1
,
j
]
,
c
[
i
,
j
−
1
]
}
,
x
i
≠
y
j
c[i, j] = \begin{cases} 0, & i = 0 \ 或\ j = 0 \\ c[i - 1, j - 1] + 1, & x_i = y_j \\ \max \{c[i - 1, j], c[i, j - 1]\}, & x_i \neq y_j \end{cases}
c[i,j]=⎩
⎨
⎧0,c[i−1,j−1]+1,max{c[i−1,j],c[i,j−1]},i=0 或 j=0xi=yjxi=yj
4. 无重复字符的最长子串
该问题是《剑指 Offer II》中的第16题,也是 LeetCode 中的第3题,在 LeetCode 中的难度为中等。
LeetCode 3. 无重复字符的最长子串
问题描述
给定一个字符串,找出其中不含有重复字符的最长子串的长度。
解法
设
c
i
c_i
ci 为字符串中的第
i
i
i 个字符,
d
d
d 为
c
i
c_i
ci 与前面最近的相同字符的距离,
f
[
i
]
f[i]
f[i] 为以
c
i
c_i
ci 为结尾的无重复字符的最长子串的长度。
则该问题的状态转移方程为:
f
[
i
]
=
{
f
[
i
−
1
]
+
1
,
c
i
未出现过
d
,
d
⩽
f
[
i
−
1
]
f
[
i
−
1
]
+
1
,
d
>
f
[
i
−
1
]
f[i] = \begin{cases} f[i - 1] + 1, & c_i 未出现过 \\ d, & d \leqslant f[i - 1] \\ f[i - 1] + 1, & d > f[i - 1] \end{cases}
f[i]=⎩
⎨
⎧f[i−1]+1,d,f[i−1]+1,ci未出现过d⩽f[i−1]d>f[i−1]
5. 最长回文子串
该问题是 LeetCode 中的第5题,在 LeetCode 中的难度为中等。
LeetCode 5. 最长回文子串
问题描述
给定一个字符串 s s s ,找到 s s s 中最长的回文子串。
解法
设
c
i
c_i
ci 为字符串中的第
i
i
i 个字符,
f
[
i
,
j
]
f[i, j]
f[i,j] 为表示
s
s
s 从索引
i
i
i 到索引
j
j
j 的子串是否为回文子串的 bool 值。
则该问题的状态转移方程为:
f
[
i
,
j
]
=
{
t
r
u
e
,
i
=
j
t
r
u
e
,
i
+
1
=
j
且
c
i
=
c
j
f
[
i
+
1
,
j
−
1
]
,
i
+
1
<
j
且
c
i
=
c
j
f
a
l
s
e
,
c
i
≠
c
j
f[i, j] = \begin{cases} true, & i = j \\ true, & i + 1 = j \ 且\ c_i = c_j \\ f[i + 1, j - 1], & i + 1 < j \ 且\ c_i = c_j \\ false, & c_i \neq c_j \end{cases}
f[i,j]=⎩
⎨
⎧true,true,f[i+1,j−1],false,i=ji+1=j 且 ci=cji+1<j 且 ci=cjci=cj
在代码实现时,先令所有长度为 1 的子串的 f [ i , i ] f[i, i] f[i,i] 为 t r u e true true ,然后从长度为 2 的子串开始遍历(第一层循环),对于每个子串长度 l l l ,在字符串上从左到右遍历所有长度为 l l l 的子串(第二层循环),求它们的 f [ i , j ] f[i, j] f[i,j] 。
6. 正则表达式匹配
该问题是《剑指 Offer》中的第19题,也是 LeetCode 中的第10题,在 LeetCode 中的难度为困难。
LeetCode 10. 正则表达式匹配
问题描述
给定一个字符串 s s s 和一个字符规律 p p p ,请实现一个支持 ‘.’ 和 ‘*’ 的正则表达式匹配。
- ‘.’ 匹配任意单个字符
- ‘*’ 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖整个字符串 s s s ,而不是部分字符串。
解法
设
s
i
s_i
si 与
p
j
p_j
pj 分别为
s
s
s 与
p
p
p 中的第
i
i
i 、
j
j
j 个字符,
f
[
i
,
j
]
f[i, j]
f[i,j] 为表示字符串
s
s
s 的前
i
i
i 个字符和正则表达式
p
p
p 的前
j
j
j 个字符是否匹配的 bool 值。
则该问题的状态转移方程为:
f
[
i
,
j
]
=
{
i
f
p
j
≠
∗
{
f
[
i
−
1
,
j
−
1
]
,
s
i
=
p
j
f
a
l
s
e
,
s
i
≠
p
j
e
l
s
e
{
f
[
i
−
1
,
j
]
∣
∣
f
[
i
,
j
−
2
]
,
s
i
=
p
j
−
1
f
[
i
,
j
−
2
]
,
s
i
≠
p
j
−
1
f[i, j] = \begin{cases} if \ \ p_j \neq * \begin{cases} f[i - 1, j - 1], & s_i = p_j \\ false, & s_i \neq p_j \end{cases} \\ else \begin{cases} f[i - 1, j] \ || \ f[i, j - 2], & s_i = p_{j - 1} \\ f[i, j - 2], & s_i \neq p_{j - 1} \end{cases} \end{cases}
f[i,j]=⎩
⎨
⎧if pj=∗{f[i−1,j−1],false,si=pjsi=pjelse{f[i−1,j] ∣∣ f[i,j−2],f[i,j−2],si=pj−1si=pj−1
注意:状态转移方程中, s i s_i si 与 p j p_j pj 的“等于”关系(如 s i = p j s_i = p_j si=pj )不是简单的“=”,而是“二者可以匹配”,也就是表示 s i s_i si 与 p j p_j pj 的值相等或者 p j p_j pj 的值为“.”。
7. 连续子数组的最大和
该问题是《剑指 Offer》中的第42题,也是 LeetCode 中的第53题,在 LeetCode 中的难度为简单。
LeetCode 53. 最大子数组和
问题描述
给定一个整数数组 n u m s nums nums ,请找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和(子数组是数组中的一个连续部分)。
解法
设
a
i
a_i
ai 为数组
n
u
m
s
nums
nums 的第
i
i
i 个元素的值,
f
[
i
]
f[i]
f[i] 为以第
i
i
i 个元素结尾的子数组的最大和。
则该问题的状态转移方程为:
f
[
i
]
=
{
a
i
,
i
=
0
a
i
,
i
>
0
且
f
[
i
−
1
]
⩽
0
a
i
+
f
[
i
−
1
]
,
i
>
0
且
f
[
i
−
1
]
>
0
f[i] = \begin{cases} a_i, & i = 0 \\ a_i, & i > 0 \ 且\ f[i - 1] \leqslant 0 \\ a_i + f[i - 1], & i > 0 \ 且\ f[i - 1] > 0 \end{cases}
f[i]=⎩
⎨
⎧ai,ai,ai+f[i−1],i=0i>0 且 f[i−1]⩽0i>0 且 f[i−1]>0
8. 把数字翻译成字符串
该问题是《剑指 Offer》中的第46题,在 LeetCode 中的难度为中等。
剑指 Offer 46. 把数字翻译成字符串
问题描述
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
解法
设
f
[
i
]
f[i]
f[i] 为由原数字的第
0
−
i
0 - i
0−i 位组成的数字的翻译方法的总数,
s
[
i
−
1
,
i
]
s[i - 1, i]
s[i−1,i] 为原数字的第
i
−
1
i - 1
i−1 位与第
i
i
i 位组成的两位数。
则该问题的状态转移方程为:
f
[
i
]
=
{
f
[
i
−
1
]
+
f
[
i
−
2
]
,
i
>
0
且
10
⩽
s
[
i
−
1
,
i
]
⩽
25
f
[
i
−
1
]
,
e
l
s
e
f[i] = \begin{cases} f[i - 1] + f[i - 2], & i > 0 \ 且\ 10 \leqslant s[i - 1, i] \leqslant 25 \\ f[i - 1], & else \end{cases}
f[i]={f[i−1]+f[i−2],f[i−1],i>0 且 10⩽s[i−1,i]⩽25else
- 在代码实现时,令 f [ − 1 ] = 1 、 f [ 0 ] = 1 f[-1] = 1、 f[0] = 1 f[−1]=1、f[0]=1 ,然后从 f [ 1 ] f[1] f[1] 开始遍历。
- f [ i ] f[i] f[i] 也可以设为由原数字的第 i i i 位到最后一位组成的数字的翻译方法的总数,并据此写出从左向右的状态转移方程,从左向右与从右向左的方法是完全对称的。
9. 礼物的最大价值
该问题是《剑指 Offer》中的第47题,在 LeetCode 中的难度为中等。
剑指 Offer 47. 礼物的最大价值
问题描述
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算最多能拿到多少价值的礼物?
解法
设
f
[
i
]
[
j
]
f[i][j]
f[i][j] 为从第
i
i
i 行第
j
j
j 列的格子出发移动到右下角最多能拿到的礼物价值,
g
[
i
]
[
j
]
g[i][j]
g[i][j] 为第
i
i
i 行第
j
j
j 列的格子中的礼物的价值。
则该问题的状态转移方程为:
f
[
i
]
[
j
]
=
{
g
[
i
]
[
j
]
+
max
{
f
[
i
+
1
]
[
j
]
,
f
[
i
]
[
j
+
1
]
}
,
i
+
1
<
m
且
j
+
1
<
n
g
[
i
]
[
j
]
+
f
[
i
+
1
]
[
j
]
,
i
+
1
<
m
且
j
+
1
=
n
g
[
i
]
[
j
]
+
f
[
i
]
[
j
+
1
]
,
i
+
1
=
m
且
j
+
1
<
n
g
[
i
]
[
j
]
,
i
+
1
=
m
且
j
+
1
=
n
f[i][j] = \begin{cases} g[i][j] + \max \{ f[i + 1][j], f[i][j + 1] \}, & i + 1 < m \ 且\ j + 1 < n \\ g[i][j] + f[i + 1][j], & i + 1 < m \ 且\ j + 1 = n \\ g[i][j] + f[i][j + 1], & i + 1 = m \ 且\ j + 1 < n \\ g[i][j], & i + 1 = m \ 且\ j + 1 = n \end{cases}
f[i][j]=⎩
⎨
⎧g[i][j]+max{f[i+1][j],f[i][j+1]},g[i][j]+f[i+1][j],g[i][j]+f[i][j+1],g[i][j],i+1<m 且 j+1<ni+1<m 且 j+1=ni+1=m 且 j+1<ni+1=m 且 j+1=n
- 在代码实现时,可以直接将 g [ i ] [ j ] g[i][j] g[i][j] 作为 f [ i ] [ j ] f[i][j] f[i][j] 使用,从而减少空间占用,将空间复杂度从 O ( n ) O(n) O(n) 降低至 O ( 1 ) O(1) O(1) 。
10. 丑数
该问题是《剑指 Offer》中的第49题,也是 LeetCode 中的第264题,在 LeetCode 中的难度为中等。
剑指 Offer 49. 丑数
问题描述
我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
解法
设
f
[
i
]
f[i]
f[i] 为第
i
i
i 个丑数,
p
2
p_2
p2 、
p
3
p_3
p3 、
p
5
p_5
p5 是三个指针(开始时都指向第一个丑数
1
1
1 ),分别指向下一个要乘 2、3、5 的数。
则该问题的状态转移方程为:
f
[
i
]
=
min
{
2
×
f
[
p
2
]
,
3
×
f
[
p
3
]
,
5
×
f
[
p
5
]
}
f[i] = \min \{ 2 \times f[p_2], 3 \times f[p_3], 5 \times f[p_5] \}
f[i]=min{2×f[p2],3×f[p3],5×f[p5]}
在代码实现时,循环求 f [ i ] f[i] f[i] ,在每次循环中,检查三个指针,若 f [ i ] = k × p k f[i] = k \times p_k f[i]=k×pk ,则将 p k p_k pk 加一,从而防止 f f f 中记入重复的丑数。
11. n个骰子的点数
该问题是《剑指 Offer》中的第60题,在 LeetCode 中的难度为中等。
剑指 Offer 60. n个骰子的点数
问题描述
把 n 个骰子扔在地上,所有骰子朝上一面的点数之和为 s。输入 n,打印出 s 的所有可能的值出现的概率。
你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。
解法
设用
n
n
n 个骰子扔出点数和
x
x
x 的概率为
f
[
n
,
x
]
f[n, x]
f[n,x] 。
则该问题的状态转移方程为:
f
[
n
,
x
]
=
∑
i
=
1
6
(
f
[
n
−
1
,
x
−
i
]
×
1
6
)
f[n, x] = \sum_{i = 1}^{6} (f[n - 1, x - i] \times \frac{1}{6})
f[n,x]=i=1∑6(f[n−1,x−i]×61)
在代码实现时, f [ n − 1 , x − i ] f[n - 1, x - i] f[n−1,x−i] 会出现三种非法情况:
- x < i x < i x<i :总点数 < < < 第 i i i 个骰子的点数
- x − i > 6 ( n − 1 ) x - i > 6 (n - 1) x−i>6(n−1) :所需的前 n − 1 n - 1 n−1 个骰子的最大总和 > > > 前面 n − 1 n - 1 n−1 个骰子的可能的最大总和
- x − i < ( n − 1 ) x - i < (n - 1) x−i<(n−1) :所需的前 n − 1 n - 1 n−1 个骰子的最大总和 < < < 前面 n − 1 n - 1 n−1 个骰子的可能的最小总和
三种非法的 f [ n − 1 , x − i ] f[n - 1, x - i] f[n−1,x−i] 的值取0即可。
可以采用正向的逻辑编写代码,即对于有 n n n 个骰子的情况,不依次求 f [ n , x ] f[n, x] f[n,x] ,而是依次计算 f [ n − 1 , x ] f[n - 1, x] f[n−1,x] 对于每个 f [ n , x ] f[n, x] f[n,x] 的贡献,这样就无需考虑三种非法的 f [ n − 1 , x − i ] f[n - 1, x - i] f[n−1,x−i] 情况,在时间效率和代码复杂度上都更优。
12. 买卖股票的最佳时机
该问题是《剑指 Offer》中的第63题,也是 LeetCode 中的第121题,在 LeetCode 中的难度为中等。
121. 买卖股票的最佳时机
问题描述
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择某一天买入这只股票,并选择在未来的某一个不同的日子卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
解法
解法一
设
p
i
p_i
pi 表示第
i
i
i 天的价格,共有
n
n
n 天,
f
[
i
]
f[i]
f[i] 表示前
i
i
i 天中价格最低的那天的价格(史低)。
则该问题的状态转移方程为:
f
[
i
]
=
{
p
i
,
i
=
0
min
{
f
[
i
−
1
]
,
p
i
}
,
i
>
0
f[i] = \begin{cases} p_i , & i = 0 \\ \min \{ f[i - 1], p_i \}, & i > 0 \end{cases}
f[i]={pi,min{f[i−1],pi},i=0i>0
因此,该问题的解即为
max
{
p
i
−
f
[
i
]
∣
i
=
1
,
…
,
n
}
\max \{ p_i - f[i] \ | \ i = 1, \dots, n \}
max{pi−f[i] ∣ i=1,…,n} 。
解法二
设
p
i
p_i
pi 表示第
i
i
i 天的价格,共有
n
n
n 天,
g
[
i
]
g[i]
g[i] 表示在第
i
i
i 天卖出股票可以获得的最大利润。
则该问题的状态转移方程为:
g
[
i
]
=
{
0
,
i
=
0
max
{
p
i
−
p
i
−
1
,
0
}
,
i
>
0
且
g
[
i
−
1
]
=
0
max
{
g
[
i
−
1
]
+
p
i
−
p
i
−
1
,
0
}
,
i
>
0
且
g
[
i
−
1
]
>
0
g[i] = \begin{cases} 0 , &i = 0 \\ \max \{ p_i - p_{i - 1}, 0 \} , & i > 0 \ 且\ g[i - 1] = 0 \\ \max \{ g[i - 1] + p_i - p_{i - 1}, 0 \}, & i > 0 \ 且\ g[i - 1] > 0 \end{cases}
g[i]=⎩
⎨
⎧0,max{pi−pi−1,0},max{g[i−1]+pi−pi−1,0},i=0i>0 且 g[i−1]=0i>0 且 g[i−1]>0
因此,该问题的解即为
max
{
g
[
i
]
∣
i
=
1
,
…
,
n
}
\max \{ g[i] \ | \ i = 1, \dots, n \}
max{g[i] ∣ i=1,…,n} 。
显然,解法二不如解法一。
13. 买卖股票的最佳时机 II
该问题是 LeetCode 中的第122题,在 LeetCode 中的难度为中等。
122. 买卖股票的最佳时机 II
问题描述
给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。
在每一天,你可以决定是否购买和/或出售股票。你在任何时候最多只能持有一股股票。你也可以先购买,然后在同一天出售。
返回你能获得的最大利润 。
解法
设
p
i
p_i
pi 表示第
i
i
i 天的价格,共有
n
n
n 天,
f
[
i
]
f[i]
f[i] 表示前
i
i
i 天能够获得的最大利润。
则该问题的状态转移方程为:
f
[
i
]
=
{
p
i
,
i
=
0
f
[
i
−
1
]
,
i
>
0
且
p
i
−
1
⩾
p
i
f
[
i
−
1
]
+
p
i
−
p
i
−
1
,
i
>
0
且
p
i
−
1
<
p
i
f[i] = \begin{cases} p_i , & i = 0 \\ f[i - 1], & i > 0 \ 且\ p_{i - 1} \geqslant p_i \\ f[i - 1] + p_i - p_{i - 1}, & i > 0 \ 且\ p_{i - 1} < p_i \end{cases}
f[i]=⎩
⎨
⎧pi,f[i−1],f[i−1]+pi−pi−1,i=0i>0 且 pi−1⩾pii>0 且 pi−1<pi
14. 剪绳子
该问题是《剑指 Offer》中的第14题,也是 LeetCode 中的第343题,在 LeetCode 中的难度为中等。
剑指 Offer 14- I. 剪绳子
问题描述
给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
解法
设
f
[
i
]
f[i]
f[i] 表示长度为
i
i
i 的绳子可以得到的最大乘积。
则该问题的状态转移方程为:
f
[
i
]
=
max
{
max
{
f
[
i
−
j
]
∗
j
∣
j
=
1
,
…
,
i
−
1
}
,
i
}
f[i] = \max \{\max \{ f[i - j] * j \ | \ j = 1, \dots, i - 1 \}, i \}
f[i]=max{max{f[i−j]∗j ∣ j=1,…,i−1},i}
15. 最长递增子序列
该问题是 LeetCode 中的第300题,在 LeetCode 中的难度为中等。
300. 最长递增子序列
问题描述
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
解法
设
a
i
a_i
ai 为数组
n
u
m
s
nums
nums 的第
i
i
i 个元素的值,
f
[
i
]
f[i]
f[i] 表示以
a
i
a_i
ai 为结尾的最长子序列的长度。
则该问题的状态转移方程为:
f
[
i
]
=
{
1
,
i
=
0
max
{
f
[
j
]
∣
j
=
1
,
…
,
i
−
1
且
a
j
<
a
i
且
f
[
j
]
⩾
f
[
i
]
}
+
1
,
i
>
0
f[i] = \begin{cases} 1, & i = 0 \\ \max \{ f[j] \ | \ j = 1, \dots, i - 1 \ 且\ a_j < a_i \ 且\ f[j] \geqslant f[i] \} + 1, & i > 0 \end{cases}
f[i]={1,max{f[j] ∣ j=1,…,i−1 且 aj<ai 且 f[j]⩾f[i]}+1,i=0i>0
16. 编辑距离
该问题是 LeetCode 中的第72题,在 LeetCode 中的难度为困难。
LeetCode 72. 编辑距离
问题描述
给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数。
你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
解法
设
s
i
s_i
si 与
p
j
p_j
pj 分别为
w
o
r
d
1
word1
word1 与
w
o
r
d
2
word2
word2 中的第
i
i
i 、
j
j
j 个字符,
f
[
i
,
j
]
f[i, j]
f[i,j] 为将
w
o
r
d
1
word1
word1 的前
i
i
i 个字符转换成
w
o
r
d
2
word2
word2 的前
j
j
j 个字符所需要的最少操作数。
则该问题的状态转移方程为:
f
[
i
,
j
]
=
{
i
,
j
=
0
j
,
i
=
0
f
[
i
−
1
]
[
j
−
1
]
,
s
j
=
p
j
min
{
f
[
i
]
[
j
−
1
]
,
f
[
i
−
1
]
[
j
]
,
f
[
i
−
1
]
[
j
−
1
]
}
+
1
,
s
j
≠
p
j
f[i, j] = \begin{cases} i, & j = 0 \\ j, & i = 0 \\ f[i - 1][j - 1], & s_j = p_j \\ \min \{ f[i][j - 1], f[i - 1][j], f[i - 1][j - 1] \} + 1, & s_j \neq p_j \end{cases}
f[i,j]=⎩
⎨
⎧i,j,f[i−1][j−1],min{f[i][j−1],f[i−1][j],f[i−1][j−1]}+1,j=0i=0sj=pjsj=pj
注:若 s i s_i si 与 p j p_j pj 相等,则该位置无需操作;若 s i s_i si 与 p j p_j pj 不相等,则取插入、删除、更改三种操作中的最小值。
17. 甲板上的战舰
该问题是 LeetCode 中的第419题,在 LeetCode 中的难度为中等。
LeetCode 419. 甲板上的战舰
问题描述
给你一个大小为 m x n 的矩阵 board 表示甲板,其中,每个单元格可以是一艘战舰 ‘X’ 或者是一个空位 ‘.’ ,返回在甲板 board 上放置的战舰 的数量。
战舰只能水平或者垂直放置在 board 上。换句话说,战舰只能按 1 x k(1 行,k 列)或 k x 1(k 行,1 列)的形状建造,其中 k 可以是任意大小。两艘战舰之间至少有一个水平或垂直的空位分隔(即没有相邻的战舰)。
解法
设
b
i
,
j
b_{i,j}
bi,j 为甲板
b
o
a
r
d
board
board 上第
i
i
i 行第
j
j
j 列的位置的字符,
f
[
i
,
j
]
f[i,j]
f[i,j] 表示以第
i
i
i 行第
j
j
j 列的位置为右下角的子甲板上的战舰数量。
则该问题的状态转移方程为:
f
[
i
,
j
]
=
{
i
f
i
=
0
且
j
=
0
{
1
,
b
i
,
j
=
X
0
,
b
i
,
j
=
.
e
l
s
e
i
f
i
=
0
且
j
>
0
{
i
f
b
i
,
j
=
X
{
f
[
i
,
j
−
1
]
,
b
i
,
j
−
1
=
X
f
[
i
,
j
−
1
]
+
1
,
b
i
,
j
−
1
=
.
e
l
s
e
f
[
i
,
j
−
1
]
e
l
s
e
i
f
i
>
0
且
j
=
0
{
i
f
b
i
,
j
=
X
{
f
[
i
−
1
,
j
]
,
b
i
−
1
,
j
=
X
f
[
i
−
1
,
j
]
+
1
,
b
i
−
1
,
j
=
.
e
l
s
e
f
[
i
−
1
,
j
]
e
l
s
e
{
i
f
b
i
,
j
=
X
{
f
[
i
−
1
,
j
]
+
f
[
i
,
j
−
1
]
−
f
[
i
−
1
,
j
−
1
]
+
1
,
b
i
,
j
−
1
=
.
且
b
i
−
1
,
j
=
.
f
[
i
−
1
,
j
]
+
f
[
i
,
j
−
1
]
−
f
[
i
−
1
,
j
−
1
]
,
b
i
,
j
−
1
=
X
或
b
i
−
1
,
j
=
X
e
l
s
e
f
[
i
−
1
,
j
]
+
f
[
i
,
j
−
1
]
−
f
[
i
−
1
,
j
−
1
]
f[i,j] = \begin{cases} if \ \ \ \ \ \ \ \ \ \ i = 0 \ 且 \ j = 0 \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \begin{cases} 1, & \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ b_{i,j}=X \\ 0, & \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ b_{i,j}=. \end{cases} \\ else \ \ if \ \ i = 0 \ 且 \ j > 0 \begin{cases} if \ \ b_{i,j}=X \begin{cases} f[i,j-1], &\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ b_{i,j-1}=X \\ f[i,j-1] + 1, &\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ b_{i,j-1}=. \end{cases} \\ else \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ f[i,j-1] \end{cases} \\ else \ \ if \ \ i > 0 \ 且 \ j = 0 \begin{cases} if \ \ b_{i,j}=X \begin{cases} f[i-1,j], & \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ b_{i-1,j}=X \\ f[i-1,j] + 1, & \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ b_{i-1,j}=. \end{cases} \\ else \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ f[i-1,j] \end{cases} \\ else \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \begin{cases} if \ \ b_{i,j}=X \begin{cases} f[i-1,j] + f[i,j-1] - f[i-1,j-1] + 1, & b_{i,j-1}=. \ \ \ 且 \ b_{i-1,j}=. \\ f[i-1,j] + f[i,j-1] - f[i-1,j-1], & b_{i,j-1}=X \ 或 \ b_{i-1,j}=X \end{cases} \\ else \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ f[i-1,j] + f[i,j-1] - f[i-1,j-1] \end{cases} \\ \end{cases}
f[i,j]=⎩
⎨
⎧if i=0 且 j=0 {1,0, bi,j=X bi,j=.else if i=0 且 j>0⎩
⎨
⎧if bi,j=X{f[i,j−1],f[i,j−1]+1, bi,j−1=X bi,j−1=.else f[i,j−1]else if i>0 且 j=0⎩
⎨
⎧if bi,j=X{f[i−1,j],f[i−1,j]+1, bi−1,j=X bi−1,j=.else f[i−1,j]else ⎩
⎨
⎧if bi,j=X{f[i−1,j]+f[i,j−1]−f[i−1,j−1]+1,f[i−1,j]+f[i,j−1]−f[i−1,j−1],bi,j−1=. 且 bi−1,j=.bi,j−1=X 或 bi−1,j=Xelse f[i−1,j]+f[i,j−1]−f[i−1,j−1]