Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 031670e

Browse files
committed
Update 01.Interval-DP.md
1 parent 10196df commit 031670e

File tree

1 file changed

+239
-19
lines changed

1 file changed

+239
-19
lines changed

‎Contents/10.Dynamic-Programming/05.Interval-DP/01.Interval-DP.md‎

Lines changed: 239 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,49 @@
44

55
> **区间动态规划**:线性 DP 的一种,简称为「区间 DP」。以「区间长度」划分阶段,以两个坐标(区间的左、右端点)作为状态的维度。一个状态通常由被它包含且比它更小的区间状态转移而来。
66
7-
区间 DP 的主要思想就是现在小区间内得到最优解,再利用小区间的最优解合并,从而得到大区间的最优解,最终得到整个区间的最优解。
7+
区间 DP 的主要思想就是:先在小区间内得到最优解,再利用小区间的最优解合并,从而得到大区间的最优解,最终得到整个区间的最优解。
88

9-
常见的区间 DP 的状态转移方程一般为:$dp[i][j] = max / min \lbrace dp[i][k] + dp[k + 1][j] + cost \rbrace,\quad i < k \le j$。
9+
根据小区间向大区间转移情况的不同,常见的区间 DP 问题可以分为两种:
1010

11-
1. 其中状态 $dp[i][j]$ 表示为:区间 $[i, j]$ (即下标位置 $i$ 到下标位置 $j$ 上所有元素)上的最大价值。
12-
2. $cost$ 表示为:将两个区间 $[i, k]$ 与 $[k + 1, j]$ 中所有元素合并起来的代价。
13-
3. 这里的 $max / min$ 取决于题目是求最大值还是求最小值。
11+
1. 单个区间从中间向两侧更大区间转移的区间 DP 问题。比如从区间 $[i + 1, j - 1]$ 转移到更大区间 $[i, j]$。
12+
2. 多个(大于等于 2ドル$ 个)小区间转移到大区间的区间 DP 问题。比如从区间 $[i, k]$ 和区间 $[k, j]$ 转移到区间 $[i, j]$。
13+
14+
下面我们讲解一下这两种区间 DP 问题的基本解题思路。
1415

1516
### 1.2 区间 DP 问题的基本思路
1617

17-
区间 DP 的基本解题思路比较固定,具体步骤如下:
18+
#### 1.2.1 第 1 种区间 DP 问题基本思路
19+
20+
从中间向两侧转移的区间 DP 问题的状态转移方程一般为:$dp[i][j] = max \lbrace dp[i + 1][j - 1], \quad dp[i + 1][j], \quad dp[i][j - 1] \rbrace + cost[i][j], \quad i \le j$。
21+
22+
1. 其中 $dp[i][j]$ 表示为:区间 $[i, j]$(即下标位置 $i$ 到下标位置 $j$ 上所有元素)上的最大价值。
23+
2. $cost$ 表示为:从小区间转移到区间 $[i, j]$ 的代价。
24+
3. 这里的 $max / min$ 取决于题目是求最大值还是求最小值。
25+
26+
从中间向两侧转移的区间 DP 问题的基本解题思路如下:
27+
28+
1. 枚举区间的起点;
29+
2. 枚举区间的终点;
30+
3. 根据状态转移方程计算从小区间转移到更大区间后的最优值。
31+
32+
对应代码如下:
33+
34+
```Python
35+
for i in range(size - 1, -1, -1): # 枚举区间起点
36+
for j in range(i + 1, size): # 枚举区间终点
37+
# 状态转移方程,计算转移到更大区间后的最优值
38+
dp[i][j] = max(dp[i + 1][j - 1], dp[i + 1][j], dp[i][j - 1]) + cost[i][j]
39+
```
40+
41+
#### 1.2.3 第 2 种区间 DP 问题基本思路
42+
43+
多个(大于等于 2ドル$ 个)小区间转移到大区间的区间 DP 问题的状态转移方程一般为:$dp[i][j] = max / min \lbrace dp[i][k] + dp[k + 1][j] + cost[i][j] \rbrace,\quad i < k \le j$。
44+
45+
1. 其中状态 $dp[i][j]$ 表示为:区间 $[i, j]$ (即下标位置 $i$ 到下标位置 $j$ 上所有元素)上的最大价值。
46+
2. $cost[i][j]$ 表示为:将两个区间 $[i, k]$ 与 $[k + 1, j]$ 中的元素合并为区间 $[i, j]$ 中的元素的代价。
47+
3. 这里的 $max / min$ 取决于题目是求最大值还是求最小值。
48+
49+
多个小区间转移到大区间的区间 DP 问题的基本解题思路如下:
1850

1951
1. 枚举区间长度;
2052
2. 枚举区间的起点,根据区间起点和区间长度得出区间终点;
@@ -38,14 +70,110 @@ for l in range(1, n): # 枚举区间长度
3870

3971
下面我们根据几个例子来讲解一下区间 DP 问题的具体解题思路。
4072

41-
### 2.1 戳气球
73+
### 2.1 最长回文子序列
4274

4375
#### 2.1.1 题目链接
4476

45-
- [312. 戳气球 - 力扣](https://leetcode.cn/problems/burst-balloons/)
77+
- [516. 最长回文子序列 - 力扣](https://leetcode.cn/problems/longest-palindromic-subsequence/)
4678

4779
#### 2.1.2 题目大意
4880

81+
**描述**:给定一个字符串 $s$。
82+
83+
**要求**:找出其中最长的回文子序列,并返回该序列的长度。
84+
85+
**说明**:
86+
87+
- **子序列**:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
88+
- 1ドル \le s.length \le 1000$。
89+
- $s$ 仅由小写英文字母组成。
90+
91+
**示例**:
92+
93+
- 示例 1:
94+
95+
```Python
96+
输入:s = "bbbab"
97+
输出:4
98+
解释:一个可能的最长回文子序列为 "bbbb"
99+
```
100+
101+
- 示例 2:
102+
103+
```Python
104+
输入:s = "cbbd"
105+
输出:2
106+
解释:一个可能的最长回文子序列为 "bb"
107+
```
108+
109+
#### 2.1.3 解题思路
110+
111+
##### 思路 1:动态规划
112+
113+
###### 1. 划分阶段
114+
115+
按照区间长度进行阶段划分。
116+
117+
###### 2. 定义状态
118+
119+
定义状态 $dp[i][j]$ 表示为:字符串 $s$ 在区间 $[i, j]$ 范围内的最长回文子序列长度。
120+
121+
###### 3. 状态转移方程
122+
123+
我们对区间 $[i, j]$ 边界位置上的字符 $s[i]$ 与 $s[j]$ 进行分类讨论:
124+
125+
1. 如果 $s[i] = s[j],ドル则 $dp[i][j]$ 为区间 $[i + 1, j - 1]$ 范围内最长回文子序列长度 + 2ドル,ドル即 $dp[i][j] = dp[i + 1][j - 1] + 2$。
126+
2. 如果 $s[i] \ne s[j],ドル则 $dp[i][j]$ 取决于以下两种情况,取其最大的一种:
127+
1. 加入 $s[i]$ 所能组成的最长回文子序列长度,即:$dp[i][j] = dp[i][j - 1]$。
128+
2. 加入 $s[j]$ 所能组成的最长回文子序列长度,即:$dp[i][j] = dp[i - 1][j]$。
129+
130+
则状态转移方程为:
131+
132+
$dp[i][j] = \begin{cases} max \lbrace dp[i + 1][j - 1] + 2 \rbrace & s[i] = s[j] \cr max \lbrace dp[i][j - 1], dp[i - 1][j] \rbrace & s[i] \ne s[j] \end{cases}$
133+
134+
###### 4. 初始条件
135+
136+
- 单个字符的最长回文序列是 1ドル,ドル即 $dp[i][i] = 1$。
137+
138+
###### 5. 最终结果
139+
140+
由于 $dp[i][j]$ 依赖于 $dp[i + 1][j - 1]$、$dp[i + 1][j]$、$dp[i][j - 1],ドル所以我们应该按照从下到上、从左到右的顺序进行遍历。
141+
142+
根据我们之前定义的状态,$dp[i][j]$ 表示为:字符串 $s$ 在区间 $[i, j]$ 范围内的最长回文子序列长度。所以最终结果为 $dp[0][size - 1]$。
143+
144+
##### 思路 1:代码
145+
146+
```Python
147+
class Solution:
148+
def longestPalindromeSubseq(self, s: str) -> int:
149+
size = len(s)
150+
dp = [[0 for _ in range(size)] for _ in range(size)]
151+
for i in range(size):
152+
dp[i][i] = 1
153+
154+
for i in range(size - 1, -1, -1):
155+
for j in range(i + 1, size):
156+
if s[i] == s[j]:
157+
dp[i][j] = dp[i + 1][j - 1] + 2
158+
else:
159+
dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])
160+
161+
return dp[0][size - 1]
162+
```
163+
164+
##### 思路 1:复杂度分析
165+
166+
- **时间复杂度**:$O(n^2),ドル其中 $n$ 为字符串 $s$ 的长度。
167+
- **空间复杂度**:$O(n^2)$。
168+
169+
### 2.2 戳气球
170+
171+
#### 2.2.1 题目链接
172+
173+
- [312. 戳气球 - 力扣](https://leetcode.cn/problems/burst-balloons/)
174+
175+
#### 2.2.2 题目大意
176+
49177
**描述**:有 $n$ 个气球,编号为 0ドル \sim n - 1,ドル每个气球上都有一个数字,这些数字存在数组 $nums$ 中。现在开始戳破气球。其中戳破第 $i$ 个气球,可以获得 $nums[i - 1] \times nums[i] \times nums[i + 1]$ 枚硬币,这里的 $i - 1$ 和 $i + 1$ 代表和 $i$ 相邻的两个气球的编号。如果 $i - 1$ 或 $i + 1$ 超出了数组的边界,那么就当它是一个数字为 1ドル$ 的气球。
50178

51179
**要求**:求出能获得硬币的最大数量。
@@ -78,7 +206,7 @@ nums = [1,5] --> [5] --> []
78206
coins = 1*1*5 + 1*5*1 = 10
79207
```
80208

81-
#### 2.1.3 解题思路
209+
#### 2.2.3 解题思路
82210

83211
##### 思路 1:动态规划
84212

@@ -108,7 +236,7 @@ $dp[i][j] = max \lbrace dp[i][k] + dp[k][j] + nums[i] \times nums[k] \times nums
108236

109237
###### 5. 最终结果
110238

111-
根据我们之前定义的状态,$dp[i][j]$ 表示为:戳破所有气球 $i$ 与气球 $j$ 之间的气球(不包含气球 $i$ 和 气球 $j$),所能获取的最多硬币数。所以最终结果为 $dp[0][n + 1]$。
239+
根据我们之前定义的状态,$dp[i][j]$ 表示为:戳破所有气球 $i$ 与气球 $j$ 之间的气球(不包含气球 $i$ 和 气球 $j$),所能获取的最多硬币数。所以最终结果为 $dp[0][n + 1]$。
112240

113241
##### 思路 1:代码
114242

@@ -139,22 +267,114 @@ class Solution:
139267
- **时间复杂度**:$O(n^3),ドル其中 $n$ 为气球数量。
140268
- **空间复杂度**:$O(n^2)$。
141269

142-
### 2.2 切棍子的最小成本
270+
### 2.3 切棍子的最小成本
143271

144-
#### 2.2.1 题目链接
272+
#### 2.3.1 题目链接
145273

146274
- [1547. 切棍子的最小成本 - 力扣](https://leetcode.cn/problems/minimum-cost-to-cut-a-stick/)
147275

148-
#### 2.2.2 题目大意
276+
#### 2.3.2 题目大意
149277

150-
#### 2.2.3 解题思路
278+
**描述**:给定一个整数 $n,ドル代表一根长度为 $n$ 个单位的木根,木棍从 0ドル \sim n$ 标记了若干位置。例如,长度为 6ドル$ 的棍子可以标记如下:
151279

152-
### 2.3 最长回文子串
280+
![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/08/09/statement.jpg)
153281

154-
#### 2.3.1 题目链接
282+
再给定一个整数数组 $cuts,ドル其中 $cuts[i]$ 表示需要将棍子切开的位置。
155283

156-
-[5. 最长回文子串 - 力扣](https://leetcode.cn/problems/longest-palindromic-substring/)
284+
我们可以按照顺序完成切割,也可以根据需要更改切割顺序。
157285

158-
#### 2.3.2 题目大意
286+
每次切割的成本都是当前要切割的棍子的长度,切棍子的总成本是所有次切割成本的总和。对棍子进行切割将会把一根木棍分成两根较小的木棍(这两根小木棍的长度和就是切割前木棍的长度)。
287+
288+
**要求**:返回切棍子的最小总成本。
289+
290+
**说明**:
291+
292+
- 2ドル \le n \le 10^6$。
293+
- 1ドル \le cuts.length \le min(n - 1, 100)$。
294+
- 1ドル \le cuts[i] \le n - 1$。
295+
- $cuts$ 数组中的所有整数都互不相同。
296+
297+
**示例**:
298+
299+
- 示例 1:
300+
301+
![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/08/09/e1.jpg)
302+
303+
```Python
304+
输入:n = 7, cuts = [1,3,4,5]
305+
输出:16
306+
解释:按 [1, 3, 4, 5] 的顺序切割的情况如下所示。
307+
第一次切割长度为 7 的棍子,成本为 7 。第二次切割长度为 6 的棍子(即第一次切割得到的第二根棍子),第三次切割为长度 4 的棍子,最后切割长度为 3 的棍子。总成本为 7 + 6 + 4 + 3 = 20 。而将切割顺序重新排列为 [3, 5, 1, 4] 后,总成本 = 16(如示例图中 7 + 4 + 3 + 2 = 16)。
308+
```
309+
310+
![](https://assets.leetcode-cn.com/aliyun-lc-upload/uploads/2020/08/09/e11.jpg)
311+
312+
- 示例 2:
313+
314+
```Python
315+
输入:n = 9, cuts = [5,6,1,4,2]
316+
输出:22
317+
解释:如果按给定的顺序切割,则总成本为 25。总成本 <= 25 的切割顺序很多,例如,[4, 6, 5, 2, 1] 的总成本 = 22,是所有可能方案中成本最小的。
318+
```
319+
320+
#### 2.3.3 解题思路
321+
322+
##### 思路 1:动态规划
323+
324+
我们可以预先在数组 $cuts$ 种添加位置 0ドル$ 和位置 $n,ドル然后对数组 $cuts$ 进行排序。这样待切割的木棍就对应了数组中连续元素构成的「区间」。
325+
326+
###### 1. 划分阶段
327+
328+
按照区间长度进行阶段划分。
329+
330+
###### 2. 定义状态
331+
332+
定义状态 $dp[i][j]$ 表示为:切割区间为 $[i, j]$ 上的小木棍的最小成本。
333+
334+
###### 3. 状态转移方程
335+
336+
假设位置 $i$ 与位置 $j$ 之间最后一个切割的位置为 $k,ドル则 $dp[i][j]$ 取决与由 $k$ 作为切割点分割出的两个区间 $[i, k]$ 与 $[k, j]$ 上的最小成本 + 切割位置 $k$ 所带来的成本。
337+
338+
而切割位置 $k$ 所带来的成本是这段区间所代表的小木棍的长度,即 $cuts[j] - cuts[i]$。
339+
340+
则状态转移方程为:$dp[i][j] = min \lbrace dp[i][k] + dp[k][j] + cuts[j] - cuts[i] \rbrace, \quad i < k < j$
341+
342+
###### 4. 初始条件
343+
344+
- 相邻位置之间没有切割点,不需要切割,最小成本为 0ドル,ドル即 $dp[i - 1][i] = 0$。
345+
- 其余位置默认为最小成本为一个极大值,即 $dp[i][j] = \infty, \quad i + 1 \ne j$。
346+
347+
###### 5. 最终结果
348+
349+
根据我们之前定义的状态,$dp[i][j]$ 表示为:切割区间为 $[i, j]$ 上的小木棍的最小成本。 所以最终结果为 $dp[0][size - 1]$。
350+
351+
##### 思路 1:代码
352+
353+
```Python
354+
class Solution:
355+
def minCost(self, n: int, cuts: List[int]) -> int:
356+
cuts.append(0)
357+
cuts.append(n)
358+
cuts.sort()
359+
360+
size = len(cuts)
361+
dp = [[float('inf') for _ in range(size)] for _ in range(size)]
362+
for i in range(1, size):
363+
dp[i - 1][i] = 0
364+
365+
for l in range(3, size + 1): # 枚举区间长度
366+
for i in range(size): # 枚举区间起点
367+
j = i + l - 1 # 根据起点和长度得到终点
368+
if j >= size:
369+
continue
370+
dp[i][j] = float('inf')
371+
for k in range(i + 1, j): # 枚举区间分割点
372+
# 状态转移方程,计算合并区间后的最优值
373+
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k][j] + cuts[j] - cuts[i])
374+
return dp[0][size - 1]
375+
```
376+
377+
##### 思路 1:复杂度分析
159378

160-
#### 2.3.3 解题思路
379+
- **时间复杂度**:$O(m^3),ドル其中 $m$ 为数组 $cuts$ 的元素个数。
380+
- **空间复杂度**:$O(m^2)$。

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /