Leetcode 题解 - 动态规划.md 39.8 KB
Newer Older
C
CyC2018 已提交
1
<!-- GFM-TOC -->
C
CyC2018 已提交
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
* [斐波那契数列](#斐波那契数列)
    * [爬楼梯](#爬楼梯)
    * [强盗抢劫](#强盗抢劫)
    * [强盗在环形街区抢劫](#强盗在环形街区抢劫)
    * [信件错排](#信件错排)
    * [母牛生产](#母牛生产)
* [矩阵路径](#矩阵路径)
    * [矩阵的最小路径和](#矩阵的最小路径和)
    * [矩阵的总路径数](#矩阵的总路径数)
* [数组区间](#数组区间)
    * [数组区间和](#数组区间和)
    * [数组中等差递增子区间的个数](#数组中等差递增子区间的个数)
* [分割整数](#分割整数)
    * [分割整数的最大乘积](#分割整数的最大乘积)
    * [按平方数来分割整数](#按平方数来分割整数)
    * [分割整数构成字母字符串](#分割整数构成字母字符串)
* [最长递增子序列](#最长递增子序列)
    * [最长递增子序列](#最长递增子序列)
    * [一组整数对能够构成的最长链](#一组整数对能够构成的最长链)
    * [最长摆动子序列](#最长摆动子序列)
* [最长公共子序列](#最长公共子序列)
* [0-1 背包](#0-1-背包)
    * [空间优化](#空间优化)
    * [无法使用贪心算法的解释](#无法使用贪心算法的解释)
    * [变种](#变种)
    * [划分数组为和相等的两部分](#划分数组为和相等的两部分)
    * [改变一组数的正负号使得它们的和为一给定数](#改变一组数的正负号使得它们的和为一给定数)
    * [01 字符构成最多的字符串](#01-字符构成最多的字符串)
    * [找零钱的最少硬币数](#找零钱的最少硬币数)
    * [找零钱的硬币数组合](#找零钱的硬币数组合)
    * [字符串按单词列表分割](#字符串按单词列表分割)
    * [组合总和](#组合总和)
* [股票交易](#股票交易)
    * [需要冷却期的股票交易](#需要冷却期的股票交易)
    * [需要交易费用的股票交易](#需要交易费用的股票交易)
    * [只能进行两次的股票交易](#只能进行两次的股票交易)
    * [只能进行 k 次的股票交易](#只能进行-k-次的股票交易)
* [字符串编辑](#字符串编辑)
    * [删除两个字符串的字符使它们相等](#删除两个字符串的字符使它们相等)
    * [编辑距离](#编辑距离)
    * [复制粘贴字符](#复制粘贴字符)
C
CyC2018 已提交
43
<!-- GFM-TOC -->
C
CyC2018 已提交
44 45


C
CyC2018 已提交
46 47
递归和动态规划都是将原问题拆成多个子问题然后求解,他们之间最本质的区别是,动态规划保存了子问题的解,避免重复计算。

C
CyC2018 已提交
48
# 斐波那契数列
C
CyC2018 已提交
49

C
CyC2018 已提交
50
## 爬楼梯
C
CyC2018 已提交
51

C
CyC2018 已提交
52
[70. Climbing Stairs (Easy)](https://leetcode.com/problems/climbing-stairs/description/)
C
CyC2018 已提交
53

C
CyC2018 已提交
54
题目描述:有 N 阶楼梯,每次可以上一阶或者两阶,求有多少种上楼梯的方法。
C
CyC2018 已提交
55

C
CyC2018 已提交
56
定义一个数组 dp 存储上楼梯的方法数(为了方便讨论,数组下标从 1 开始),dp[i] 表示走到第 i 个楼梯的方法数目。
C
CyC2018 已提交
57

C
CyC2018 已提交
58
第 i 个楼梯可以从第 i-1 和 i-2 个楼梯再走一步到达,走到第 i 个楼梯的方法数为走到第 i-1 和第 i-2 个楼梯的方法数之和。
C
CyC2018 已提交
59

C
CyC2018 已提交
60
<!--<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i]=dp[i-1]+dp[i-2]" class="mathjax-pic"/></div> <br>-->
C
CyC2018 已提交
61

C
CyC2018 已提交
62
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/14fe1e71-8518-458f-a220-116003061a83.png" width="200px"> </div><br>
C
CyC2018 已提交
63 64


C
CyC2018 已提交
65
考虑到 dp[i] 只与 dp[i - 1] 和 dp[i - 2] 有关,因此可以只用两个变量来存储 dp[i - 1] 和 dp[i - 2],使得原来的 O(N) 空间复杂度优化为 O(1) 复杂度。
C
CyC2018 已提交
66 67

```java
C
CyC2018 已提交
68 69 70 71 72 73 74 75 76 77 78
public int climbStairs(int n) {
    if (n <= 2) {
        return n;
    }
    int pre2 = 1, pre1 = 2;
    for (int i = 2; i < n; i++) {
        int cur = pre1 + pre2;
        pre2 = pre1;
        pre1 = cur;
    }
    return pre1;
C
CyC2018 已提交
79 80 81
}
```

C
CyC2018 已提交
82
## 强盗抢劫
C
CyC2018 已提交
83

C
CyC2018 已提交
84
[198. House Robber (Easy)](https://leetcode.com/problems/house-robber/description/)
C
CyC2018 已提交
85 86 87

题目描述:抢劫一排住户,但是不能抢邻近的住户,求最大抢劫量。

C
CyC2018 已提交
88
定义 dp 数组用来存储最大的抢劫量,其中 dp[i] 表示抢到第 i 个住户时的最大抢劫量。
C
CyC2018 已提交
89

C
CyC2018 已提交
90
由于不能抢劫邻近住户,如果抢劫了第 i -1 个住户,那么就不能再抢劫第 i 个住户,所以
C
CyC2018 已提交
91

C
CyC2018 已提交
92
<!--<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i]=max(dp[i-2]+nums[i],dp[i-1])" class="mathjax-pic"/></div> <br>-->
C
CyC2018 已提交
93

C
CyC2018 已提交
94 95
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/2de794ca-aa7b-48f3-a556-a0e2708cb976.jpg" width="350px"> </div><br>

C
CyC2018 已提交
96 97

```java
C
CyC2018 已提交
98 99 100 101 102 103 104 105
public int rob(int[] nums) {
    int pre2 = 0, pre1 = 0;
    for (int i = 0; i < nums.length; i++) {
        int cur = Math.max(pre2 + nums[i], pre1);
        pre2 = pre1;
        pre1 = cur;
    }
    return pre1;
C
CyC2018 已提交
106 107 108
}
```

C
CyC2018 已提交
109
## 强盗在环形街区抢劫
C
CyC2018 已提交
110

C
CyC2018 已提交
111
[213. House Robber II (Medium)](https://leetcode.com/problems/house-robber-ii/description/)
C
CyC2018 已提交
112 113

```java
Q
quyan 已提交
114
public int rob(int[] nums) {
C
CyC2018 已提交
115 116 117 118 119 120 121 122
    if (nums == null || nums.length == 0) {
        return 0;
    }
    int n = nums.length;
    if (n == 1) {
        return nums[0];
    }
    return Math.max(rob(nums, 0, n - 2), rob(nums, 1, n - 1));
C
CyC2018 已提交
123 124
}

Q
quyan 已提交
125
private int rob(int[] nums, int first, int last) {
C
CyC2018 已提交
126 127 128 129 130 131 132
    int pre2 = 0, pre1 = 0;
    for (int i = first; i <= last; i++) {
        int cur = Math.max(pre1, pre2 + nums[i]);
        pre2 = pre1;
        pre1 = cur;
    }
    return pre1;
C
CyC2018 已提交
133 134 135
}
```

C
CyC2018 已提交
136
## 信件错排
C
CyC2018 已提交
137

C
CyC2018 已提交
138
题目描述:有 N 个 信 和 信封,它们被打乱,求错误装信方式的数量。
C
CyC2018 已提交
139

C
CyC2018 已提交
140
定义一个数组 dp 存储错误方式数量,dp[i] 表示前 i 个信和信封的错误方式数量。假设第 i 个信装到第 j 个信封里面,而第 j 个信装到第 k 个信封里面。根据 i 和 k 是否相等,有两种情况:
C
CyC2018 已提交
141

C
CyC2018 已提交
142 143
- i==k,交换 i 和 k 的信后,它们的信和信封在正确的位置,但是其余 i-2 封信有 dp[i-2] 种错误装信的方式。由于 j 有 i-1 种取值,因此共有 (i-1)\*dp[i-2] 种错误装信方式。
- i != k,交换 i 和 j 的信后,第 i 个信和信封在正确的位置,其余 i-1 封信有 dp[i-1] 种错误装信方式。由于 j 有 i-1 种取值,因此共有 (i-1)\*dp[i-1] 种错误装信方式。
C
CyC2018 已提交
144 145 146

综上所述,错误装信数量方式数量为:

C
CyC2018 已提交
147
<!--<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i]=(i-1)*dp[i-2]+(i-1)*dp[i-1]" class="mathjax-pic"/></div> <br>-->
C
CyC2018 已提交
148

C
CyC2018 已提交
149
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/da1f96b9-fd4d-44ca-8925-fb14c5733388.png" width="350px"> </div><br>
C
CyC2018 已提交
150

C
CyC2018 已提交
151
## 母牛生产
C
CyC2018 已提交
152 153 154

[程序员代码面试指南-P181](#)

C
CyC2018 已提交
155
题目描述:假设农场中成熟的母牛每年都会生 1 头小母牛,并且永远不会死。第一年有 1 只小母牛,从第二年开始,母牛开始生小母牛。每只小母牛 3 年之后成熟又可以生小母牛。给定整数 N,求 N 年后牛的数量。
C
CyC2018 已提交
156

C
CyC2018 已提交
157
第 i 年成熟的牛的数量为:
C
CyC2018 已提交
158

C
CyC2018 已提交
159
<!--<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i]=dp[i-1]+dp[i-3]" class="mathjax-pic"/></div> <br>-->
C
CyC2018 已提交
160

C
CyC2018 已提交
161
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/879814ee-48b5-4bcb-86f5-dcc400cb81ad.png" width="250px"> </div><br>
C
CyC2018 已提交
162

C
CyC2018 已提交
163
# 矩阵路径
C
CyC2018 已提交
164

C
CyC2018 已提交
165
## 矩阵的最小路径和
C
CyC2018 已提交
166

C
CyC2018 已提交
167
[64. Minimum Path Sum (Medium)](https://leetcode.com/problems/minimum-path-sum/description/)
C
CyC2018 已提交
168 169 170

```html
[[1,3,1],
C
CyC2018 已提交
171 172 173
 [1,5,1],
 [4,2,1]]
Given the above grid map, return 7. Because the path 1→3→1→1→1 minimizes the sum.
C
CyC2018 已提交
174 175 176 177 178
```

题目描述:求从矩阵的左上角到右下角的最小路径和,每次只能向右和向下移动。

```java
C
CyC2018 已提交
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
public int minPathSum(int[][] grid) {
    if (grid.length == 0 || grid[0].length == 0) {
        return 0;
    }
    int m = grid.length, n = grid[0].length;
    int[] dp = new int[n];
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            if (j == 0) {
                dp[j] = dp[j];        // 只能从上侧走到该位置
            } else if (i == 0) {
                dp[j] = dp[j - 1];    // 只能从左侧走到该位置
            } else {
                dp[j] = Math.min(dp[j - 1], dp[j]);
            }
            dp[j] += grid[i][j];
        }
    }
    return dp[n - 1];
C
CyC2018 已提交
198 199 200
}
```

C
CyC2018 已提交
201
## 矩阵的总路径数
C
CyC2018 已提交
202

C
CyC2018 已提交
203
[62. Unique Paths (Medium)](https://leetcode.com/problems/unique-paths/description/)
C
CyC2018 已提交
204 205 206

题目描述:统计从矩阵左上角到右下角的路径总数,每次只能向右或者向下移动。

C
CyC2018 已提交
207
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/dc82f0f3-c1d4-4ac8-90ac-d5b32a9bd75a.jpg" width=""> </div><br>
C
CyC2018 已提交
208 209

```java
C
CyC2018 已提交
210 211 212 213 214 215 216 217 218
public int uniquePaths(int m, int n) {
    int[] dp = new int[n];
    Arrays.fill(dp, 1);
    for (int i = 1; i < m; i++) {
        for (int j = 1; j < n; j++) {
            dp[j] = dp[j] + dp[j - 1];
        }
    }
    return dp[n - 1];
C
CyC2018 已提交
219 220 221
}
```

C
CyC2018 已提交
222
也可以直接用数学公式求解,这是一个组合问题。机器人总共移动的次数 S=m+n-2,向下移动的次数 D=m-1,那么问题可以看成从 S 中取出 D 个位置的组合数量,这个问题的解为 C(S, D)。
C
CyC2018 已提交
223 224

```java
C
CyC2018 已提交
225 226 227 228 229 230 231 232
public int uniquePaths(int m, int n) {
    int S = m + n - 2;  // 总共的移动次数
    int D = m - 1;      // 向下的移动次数
    long ret = 1;
    for (int i = 1; i <= D; i++) {
        ret = ret * (S - D + i) / i;
    }
    return (int) ret;
C
CyC2018 已提交
233 234 235
}
```

C
CyC2018 已提交
236
# 数组区间
C
CyC2018 已提交
237

C
CyC2018 已提交
238
## 数组区间和
C
CyC2018 已提交
239

C
CyC2018 已提交
240
[303. Range Sum Query - Immutable (Easy)](https://leetcode.com/problems/range-sum-query-immutable/description/)
C
CyC2018 已提交
241 242

```html
C
CyC2018 已提交
243
Given nums = [-2, 0, 3, -5, 2, -1]
C
CyC2018 已提交
244

C
CyC2018 已提交
245 246 247
sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3
C
CyC2018 已提交
248 249
```

C
CyC2018 已提交
250
求区间 i \~ j 的和,可以转换为 sum[j + 1] - sum[i],其中 sum[i] 为 0 \~ i - 1 的和。
C
CyC2018 已提交
251 252

```java
C
CyC2018 已提交
253
class NumArray {
C
CyC2018 已提交
254

C
CyC2018 已提交
255
    private int[] sums;
C
CyC2018 已提交
256

C
CyC2018 已提交
257 258 259 260 261 262
    public NumArray(int[] nums) {
        sums = new int[nums.length + 1];
        for (int i = 1; i <= nums.length; i++) {
            sums[i] = sums[i - 1] + nums[i - 1];
        }
    }
C
CyC2018 已提交
263

C
CyC2018 已提交
264 265 266
    public int sumRange(int i, int j) {
        return sums[j + 1] - sums[i];
    }
C
CyC2018 已提交
267 268 269
}
```

C
CyC2018 已提交
270
## 数组中等差递增子区间的个数
C
CyC2018 已提交
271

C
CyC2018 已提交
272
[413. Arithmetic Slices (Medium)](https://leetcode.com/problems/arithmetic-slices/description/)
C
CyC2018 已提交
273 274

```html
C
CyC2018 已提交
275 276
A = [1, 2, 3, 4]
return: 3, for 3 arithmetic slices in A: [1, 2, 3], [2, 3, 4] and [1, 2, 3, 4] itself.
C
CyC2018 已提交
277 278
```

C
CyC2018 已提交
279
dp[i] 表示以 A[i] 为结尾的等差递增子区间的个数。
C
CyC2018 已提交
280

C
CyC2018 已提交
281
在 A[i] - A[i - 1] == A[i - 1] - A[i - 2] 的条件下,{A[i - 2], A[i - 1], A[i]} 是一个等差递增子区间。如果 {A[i - 3], A[i - 2], A[i - 1]} 是一个等差递增子区间,那么 {A[i - 3], A[i - 2], A[i - 1], A[i]} 也是等差递增子区间,dp[i] = dp[i-1] + 1。
C
CyC2018 已提交
282 283

```java
C
CyC2018 已提交
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
public int numberOfArithmeticSlices(int[] A) {
    if (A == null || A.length == 0) {
        return 0;
    }
    int n = A.length;
    int[] dp = new int[n];
    for (int i = 2; i < n; i++) {
        if (A[i] - A[i - 1] == A[i - 1] - A[i - 2]) {
            dp[i] = dp[i - 1] + 1;
        }
    }
    int total = 0;
    for (int cnt : dp) {
        total += cnt;
    }
    return total;
C
CyC2018 已提交
300 301 302
}
```

C
CyC2018 已提交
303
# 分割整数
C
CyC2018 已提交
304

C
CyC2018 已提交
305
## 分割整数的最大乘积
C
CyC2018 已提交
306

C
CyC2018 已提交
307
[343. Integer Break (Medim)](https://leetcode.com/problems/integer-break/description/)
C
CyC2018 已提交
308

C
CyC2018 已提交
309
题目描述:For example, given n = 2, return 1 (2 = 1 + 1); given n = 10, return 36 (10 = 3 + 3 + 4).
C
CyC2018 已提交
310 311

```java
C
CyC2018 已提交
312 313 314 315 316 317 318 319 320
public int integerBreak(int n) {
    int[] dp = new int[n + 1];
    dp[1] = 1;
    for (int i = 2; i <= n; i++) {
        for (int j = 1; j <= i - 1; j++) {
            dp[i] = Math.max(dp[i], Math.max(j * dp[i - j], j * (i - j)));
        }
    }
    return dp[n];
C
CyC2018 已提交
321 322 323
}
```

C
CyC2018 已提交
324
## 按平方数来分割整数
C
CyC2018 已提交
325

C
CyC2018 已提交
326
[279. Perfect Squares(Medium)](https://leetcode.com/problems/perfect-squares/description/)
C
CyC2018 已提交
327

C
CyC2018 已提交
328
题目描述:For example, given n = 12, return 3 because 12 = 4 + 4 + 4; given n = 13, return 2 because 13 = 4 + 9.
C
CyC2018 已提交
329 330

```java
C
CyC2018 已提交
331 332 333 334 335 336 337 338 339 340 341 342 343 344
public int numSquares(int n) {
    List<Integer> squareList = generateSquareList(n);
    int[] dp = new int[n + 1];
    for (int i = 1; i <= n; i++) {
        int min = Integer.MAX_VALUE;
        for (int square : squareList) {
            if (square > i) {
                break;
            }
            min = Math.min(min, dp[i - square] + 1);
        }
        dp[i] = min;
    }
    return dp[n];
C
CyC2018 已提交
345 346
}

C
CyC2018 已提交
347 348 349 350 351 352 353 354 355 356
private List<Integer> generateSquareList(int n) {
    List<Integer> squareList = new ArrayList<>();
    int diff = 3;
    int square = 1;
    while (square <= n) {
        squareList.add(square);
        square += diff;
        diff += 2;
    }
    return squareList;
C
CyC2018 已提交
357 358 359
}
```

C
CyC2018 已提交
360
## 分割整数构成字母字符串
C
CyC2018 已提交
361

C
CyC2018 已提交
362
[91. Decode Ways (Medium)](https://leetcode.com/problems/decode-ways/description/)
C
CyC2018 已提交
363

C
CyC2018 已提交
364
题目描述:Given encoded message "12", it could be decoded as "AB" (1 2) or "L" (12).
C
CyC2018 已提交
365 366

```java
C
CyC2018 已提交
367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388
public int numDecodings(String s) {
    if (s == null || s.length() == 0) {
        return 0;
    }
    int n = s.length();
    int[] dp = new int[n + 1];
    dp[0] = 1;
    dp[1] = s.charAt(0) == '0' ? 0 : 1;
    for (int i = 2; i <= n; i++) {
        int one = Integer.valueOf(s.substring(i - 1, i));
        if (one != 0) {
            dp[i] += dp[i - 1];
        }
        if (s.charAt(i - 2) == '0') {
            continue;
        }
        int two = Integer.valueOf(s.substring(i - 2, i));
        if (two <= 26) {
            dp[i] += dp[i - 2];
        }
    }
    return dp[n];
C
CyC2018 已提交
389 390 391
}
```

C
CyC2018 已提交
392
# 最长递增子序列
C
CyC2018 已提交
393

C
CyC2018 已提交
394
已知一个序列 {S<sub>1</sub>, S<sub>2</sub>,...,S<sub>n</sub>},取出若干数组成新的序列 {S<sub>i1</sub>, S<sub>i2</sub>,..., S<sub>im</sub>},其中 i1、i2 ... im 保持递增,即新序列中各个数仍然保持原数列中的先后顺序,称新序列为原序列的一个 **子序列**
C
CyC2018 已提交
395

C
CyC2018 已提交
396
如果在子序列中,当下标 ix > iy 时,S<sub>ix</sub> > S<sub>iy</sub>,称子序列为原序列的一个 **递增子序列**
C
CyC2018 已提交
397

C
CyC2018 已提交
398
定义一个数组 dp 存储最长递增子序列的长度,dp[n] 表示以 S<sub>n</sub> 结尾的序列的最长递增子序列长度。对于一个递增子序列 {S<sub>i1</sub>, S<sub>i2</sub>,...,S<sub>im</sub>},如果 im < n 并且 S<sub>im</sub> < S<sub>n</sub>,此时 {S<sub>i1</sub>, S<sub>i2</sub>,..., S<sub>im</sub>, S<sub>n</sub>} 为一个递增子序列,递增子序列的长度增加 1。满足上述条件的递增子序列中,长度最长的那个递增子序列就是要找的,在长度最长的递增子序列上加上 S<sub>n</sub> 就构成了以 S<sub>n</sub> 为结尾的最长递增子序列。因此 dp[n] = max{ dp[i]+1 | S<sub>i</sub> < S<sub>n</sub> && i < n} 。
C
CyC2018 已提交
399

C
CyC2018 已提交
400
因为在求 dp[n] 时可能无法找到一个满足条件的递增子序列,此时 {S<sub>n</sub>} 就构成了递增子序列,需要对前面的求解方程做修改,令 dp[n] 最小为 1,即:
C
CyC2018 已提交
401

C
CyC2018 已提交
402
<!--<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[n]=max\{1,dp[i]+1|S_i<S_n\&\&i<n\}" class="mathjax-pic"/></div> <br>-->
C
CyC2018 已提交
403

C
CyC2018 已提交
404
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/ee994da4-0fc7-443d-ac56-c08caf00a204.jpg" width="350px"> </div><br>
C
CyC2018 已提交
405

C
CyC2018 已提交
406
对于一个长度为 N 的序列,最长递增子序列并不一定会以 S<sub>N</sub> 为结尾,因此 dp[N] 不是序列的最长递增子序列的长度,需要遍历 dp 数组找出最大值才是所要的结果,max{ dp[i] | 1 <= i <= N} 即为所求。
C
CyC2018 已提交
407

C
CyC2018 已提交
408
## 最长递增子序列
C
CyC2018 已提交
409

C
CyC2018 已提交
410
[300. Longest Increasing Subsequence (Medium)](https://leetcode.com/problems/longest-increasing-subsequence/description/)
C
CyC2018 已提交
411 412

```java
C
CyC2018 已提交
413 414 415 416 417 418 419 420 421 422 423 424 425
public int lengthOfLIS(int[] nums) {
    int n = nums.length;
    int[] dp = new int[n];
    for (int i = 0; i < n; i++) {
        int max = 1;
        for (int j = 0; j < i; j++) {
            if (nums[i] > nums[j]) {
                max = Math.max(max, dp[j] + 1);
            }
        }
        dp[i] = max;
    }
    return Arrays.stream(dp).max().orElse(0);
C
CyC2018 已提交
426 427 428
}
```

C
CyC2018 已提交
429
使用 Stream 求最大值会导致运行时间过长,可以改成以下形式:
C
CyC2018 已提交
430 431

```java
C
CyC2018 已提交
432 433 434
int ret = 0;
for (int i = 0; i < n; i++) {
    ret = Math.max(ret, dp[i]);
C
CyC2018 已提交
435
}
C
CyC2018 已提交
436
return ret;
C
CyC2018 已提交
437 438
```

C
CyC2018 已提交
439
以上解法的时间复杂度为 O(N<sup>2</sup>),可以使用二分查找将时间复杂度降低为 O(NlogN)。
C
CyC2018 已提交
440

C
CyC2018 已提交
441
定义一个 tails 数组,其中 tails[i] 存储长度为 i + 1 的最长递增子序列的最后一个元素。对于一个元素 x,
C
CyC2018 已提交
442

C
CyC2018 已提交
443 444
- 如果它大于 tails 数组所有的值,那么把它添加到 tails 后面,表示最长递增子序列长度加 1;
- 如果 tails[i-1] < x <= tails[i],那么更新 tails[i] = x。
C
CyC2018 已提交
445

C
CyC2018 已提交
446
例如对于数组 [4,3,6,5],有:
C
CyC2018 已提交
447 448

```html
C
CyC2018 已提交
449 450 451 452 453 454
tails      len      num
[]         0        4
[4]        1        3
[3]        1        6
[3,6]      2        5
[3,5]      2        null
C
CyC2018 已提交
455 456
```

C
CyC2018 已提交
457
可以看出 tails 数组保持有序,因此在查找 S<sub>i</sub> 位于 tails 数组的位置时就可以使用二分查找。
C
CyC2018 已提交
458 459

```java
C
CyC2018 已提交
460 461 462 463 464 465 466 467 468 469 470 471
public int lengthOfLIS(int[] nums) {
    int n = nums.length;
    int[] tails = new int[n];
    int len = 0;
    for (int num : nums) {
        int index = binarySearch(tails, len, num);
        tails[index] = num;
        if (index == len) {
            len++;
        }
    }
    return len;
C
CyC2018 已提交
472 473
}

C
CyC2018 已提交
474 475 476 477 478 479 480 481 482 483 484 485 486
private int binarySearch(int[] tails, int len, int key) {
    int l = 0, h = len;
    while (l < h) {
        int mid = l + (h - l) / 2;
        if (tails[mid] == key) {
            return mid;
        } else if (tails[mid] > key) {
            h = mid;
        } else {
            l = mid + 1;
        }
    }
    return l;
C
CyC2018 已提交
487 488 489
}
```

C
CyC2018 已提交
490
## 一组整数对能够构成的最长链
C
CyC2018 已提交
491

C
CyC2018 已提交
492
[646. Maximum Length of Pair Chain (Medium)](https://leetcode.com/problems/maximum-length-of-pair-chain/description/)
C
CyC2018 已提交
493 494

```html
C
CyC2018 已提交
495 496 497
Input: [[1,2], [2,3], [3,4]]
Output: 2
Explanation: The longest chain is [1,2] -> [3,4]
C
CyC2018 已提交
498 499
```

C
CyC2018 已提交
500
题目描述:对于 (a, b) 和 (c, d) ,如果 b < c,则它们可以构成一条链。
C
CyC2018 已提交
501 502

```java
C
CyC2018 已提交
503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518
public int findLongestChain(int[][] pairs) {
    if (pairs == null || pairs.length == 0) {
        return 0;
    }
    Arrays.sort(pairs, (a, b) -> (a[0] - b[0]));
    int n = pairs.length;
    int[] dp = new int[n];
    Arrays.fill(dp, 1);
    for (int i = 1; i < n; i++) {
        for (int j = 0; j < i; j++) {
            if (pairs[j][1] < pairs[i][0]) {
                dp[i] = Math.max(dp[i], dp[j] + 1);
            }
        }
    }
    return Arrays.stream(dp).max().orElse(0);
C
CyC2018 已提交
519 520 521
}
```

C
CyC2018 已提交
522
## 最长摆动子序列
C
CyC2018 已提交
523

C
CyC2018 已提交
524
[376. Wiggle Subsequence (Medium)](https://leetcode.com/problems/wiggle-subsequence/description/)
C
CyC2018 已提交
525 526

```html
C
CyC2018 已提交
527 528 529
Input: [1,7,4,9,2,5]
Output: 6
The entire sequence is a wiggle sequence.
C
CyC2018 已提交
530

C
CyC2018 已提交
531 532 533
Input: [1,17,5,10,13,15,10,5,16,8]
Output: 7
There are several subsequences that achieve this length. One is [1,17,10,13,10,16,8].
C
CyC2018 已提交
534

C
CyC2018 已提交
535 536
Input: [1,2,3,4,5,6,7,8,9]
Output: 2
C
CyC2018 已提交
537 538
```

C
CyC2018 已提交
539
要求:使用 O(N) 时间复杂度求解。
C
CyC2018 已提交
540 541

```java
C
CyC2018 已提交
542 543 544 545 546 547 548 549 550 551 552 553 554
public int wiggleMaxLength(int[] nums) {
    if (nums == null || nums.length == 0) {
        return 0;
    }
    int up = 1, down = 1;
    for (int i = 1; i < nums.length; i++) {
        if (nums[i] > nums[i - 1]) {
            up = down + 1;
        } else if (nums[i] < nums[i - 1]) {
            down = up + 1;
        }
    }
    return Math.max(up, down);
C
CyC2018 已提交
555 556 557
}
```

C
CyC2018 已提交
558
# 最长公共子序列
C
CyC2018 已提交
559

C
CyC2018 已提交
560
对于两个子序列 S1 和 S2,找出它们最长的公共子序列。
C
CyC2018 已提交
561

C
CyC2018 已提交
562
定义一个二维数组 dp 用来存储最长公共子序列的长度,其中 dp[i][j] 表示 S1 的前 i 个字符与 S2 的前 j 个字符最长公共子序列的长度。考虑 S1<sub>i</sub> 与 S2<sub>j</sub> 值是否相等,分为两种情况:
C
CyC2018 已提交
563

C
CyC2018 已提交
564 565
- 当 S1<sub>i</sub>==S2<sub>j</sub> 时,那么就能在 S1 的前 i-1 个字符与 S2 的前 j-1 个字符最长公共子序列的基础上再加上 S1<sub>i</sub> 这个值,最长公共子序列长度加 1,即 dp[i][j] = dp[i-1][j-1] + 1。
- 当 S1<sub>i</sub> != S2<sub>j</sub> 时,此时最长公共子序列为 S1 的前 i-1 个字符和 S2 的前 j 个字符最长公共子序列,或者 S1 的前 i 个字符和 S2 的前 j-1 个字符最长公共子序列,取它们的最大者,即 dp[i][j] = max{ dp[i-1][j], dp[i][j-1] }。
C
CyC2018 已提交
566 567 568

综上,最长公共子序列的状态转移方程为:

C
CyC2018 已提交
569
<!--<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i][j]=\left\{\begin{array}{rcl}dp[i-1][j-1]&&{S1_i==S2_j}\\max(dp[i-1][j],dp[i][j-1])&&{S1_i<>S2_j}\end{array}\right." class="mathjax-pic"/></div> <br>-->
C
CyC2018 已提交
570

C
CyC2018 已提交
571
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/ecd89a22-c075-4716-8423-e0ba89230e9a.jpg" width="450px"> </div><br>
C
CyC2018 已提交
572

C
CyC2018 已提交
573
对于长度为 N 的序列 S<sub>1</sub> 和长度为 M 的序列 S<sub>2</sub>,dp[N][M] 就是序列 S<sub>1</sub> 和序列 S<sub>2</sub> 的最长公共子序列长度。
C
CyC2018 已提交
574 575 576

与最长递增子序列相比,最长公共子序列有以下不同点:

C
CyC2018 已提交
577 578 579
- 针对的是两个序列,求它们的最长公共子序列。
- 在最长递增子序列中,dp[i] 表示以 S<sub>i</sub> 为结尾的最长递增子序列长度,子序列必须包含 S<sub>i</sub> ;在最长公共子序列中,dp[i][j] 表示 S1 中前 i 个字符与 S2 中前 j 个字符的最长公共子序列长度,不一定包含 S1<sub>i</sub> 和 S2<sub>j</sub>
- 在求最终解时,最长公共子序列中 dp[N][M] 就是最终解,而最长递增子序列中 dp[N] 不是最终解,因为以 S<sub>N</sub> 为结尾的最长递增子序列不一定是整个序列最长递增子序列,需要遍历一遍 dp 数组找到最大者。
C
CyC2018 已提交
580 581

```java
C
CyC2018 已提交
582 583 584 585 586 587 588 589 590 591 592 593 594
public int lengthOfLCS(int[] nums1, int[] nums2) {
    int n1 = nums1.length, n2 = nums2.length;
    int[][] dp = new int[n1 + 1][n2 + 1];
    for (int i = 1; i <= n1; i++) {
        for (int j = 1; j <= n2; j++) {
            if (nums1[i - 1] == nums2[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    return dp[n1][n2];
C
CyC2018 已提交
595 596 597
}
```

C
CyC2018 已提交
598
# 0-1 背包
C
CyC2018 已提交
599

C
CyC2018 已提交
600
有一个容量为 N 的背包,要用这个背包装下物品的价值最大,这些物品有两个属性:体积 w 和价值 v。
C
CyC2018 已提交
601

C
CyC2018 已提交
602
定义一个二维数组 dp 存储最大价值,其中 dp[i][j] 表示前 i 件物品体积不超过 j 的情况下能达到的最大价值。设第 i 件物品体积为 w,价值为 v,根据第 i 件物品是否添加到背包中,可以分两种情况讨论:
C
CyC2018 已提交
603

C
CyC2018 已提交
604 605
- 第 i 件物品没添加到背包,总体积不超过 j 的前 i 件物品的最大价值就是总体积不超过 j 的前 i-1 件物品的最大价值,dp[i][j] = dp[i-1][j]
- 第 i 件物品添加到背包中,dp[i][j] = dp[i-1][j-w] + v。
C
CyC2018 已提交
606

C
CyC2018 已提交
607
第 i 件物品可添加也可以不添加,取决于哪种情况下最大价值更大。因此,0-1 背包的状态转移方程为:
C
CyC2018 已提交
608

C
CyC2018 已提交
609
<!--<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i][j]=max(dp[i-1][j],dp[i-1][j-w]+v)" class="mathjax-pic"/></div> <br>-->
C
CyC2018 已提交
610

C
CyC2018 已提交
611
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/8cb2be66-3d47-41ba-b55b-319fc68940d4.png" width="400px"> </div><br>
C
CyC2018 已提交
612 613

```java
C
CyC2018 已提交
614 615 616 617 618 619 620 621 622 623 624 625 626
public int knapsack(int W, int N, int[] weights, int[] values) {
    int[][] dp = new int[N + 1][W + 1];
    for (int i = 1; i <= N; i++) {
        int w = weights[i - 1], v = values[i - 1];
        for (int j = 1; j <= W; j++) {
            if (j >= w) {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w] + v);
            } else {
                dp[i][j] = dp[i - 1][j];
            }
        }
    }
    return dp[N][W];
C
CyC2018 已提交
627 628 629
}
```

C
CyC2018 已提交
630
## 空间优化
C
CyC2018 已提交
631

C
CyC2018 已提交
632
在程序实现时可以对 0-1 背包做优化。观察状态转移方程可以知道,前 i 件物品的状态仅与前 i-1 件物品的状态有关,因此可以将 dp 定义为一维数组,其中 dp[j] 既可以表示 dp[i-1][j] 也可以表示 dp[i][j]。此时,
C
CyC2018 已提交
633

C
CyC2018 已提交
634
<!--<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[j]=max(dp[j],dp[j-w]+v)" class="mathjax-pic"/></div> <br>-->
C
CyC2018 已提交
635

C
CyC2018 已提交
636
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/9ae89f16-7905-4a6f-88a2-874b4cac91f4.jpg" width="300px"> </div><br>
C
CyC2018 已提交
637

C
CyC2018 已提交
638
因为 dp[j-w] 表示 dp[i-1][j-w],因此不能先求 dp[i][j-w],以防将 dp[i-1][j-w] 覆盖。也就是说要先计算 dp[i][j] 再计算 dp[i][j-w],在程序实现时需要按倒序来循环求解。
C
CyC2018 已提交
639 640

```java
C
CyC2018 已提交
641 642 643 644 645 646 647 648 649 650 651
public int knapsack(int W, int N, int[] weights, int[] values) {
    int[] dp = new int[W + 1];
    for (int i = 1; i <= N; i++) {
        int w = weights[i - 1], v = values[i - 1];
        for (int j = W; j >= 1; j--) {
            if (j >= w) {
                dp[j] = Math.max(dp[j], dp[j - w] + v);
            }
        }
    }
    return dp[W];
C
CyC2018 已提交
652 653 654
}
```

C
CyC2018 已提交
655
## 无法使用贪心算法的解释
C
CyC2018 已提交
656

C
CyC2018 已提交
657
0-1 背包问题无法使用贪心算法来求解,也就是说不能按照先添加性价比最高的物品来达到最优,这是因为这种方式可能造成背包空间的浪费,从而无法达到最优。考虑下面的物品和一个容量为 5 的背包,如果先添加物品 0 再添加物品 1,那么只能存放的价值为 16,浪费了大小为 2 的空间。最优的方式是存放物品 1 和物品 2,价值为 22.
C
CyC2018 已提交
658

C
CyC2018 已提交
659 660 661 662 663
| id | w | v | v/w |
| --- | --- | --- | --- |
| 0 | 1 | 6 | 6 |
| 1 | 2 | 10 | 5 |
| 2 | 3 | 12 | 4 |
C
CyC2018 已提交
664

C
CyC2018 已提交
665
## 变种
C
CyC2018 已提交
666

C
CyC2018 已提交
667
- 完全背包:物品数量为无限个
C
CyC2018 已提交
668

C
CyC2018 已提交
669
- 多重背包:物品数量有限制
C
CyC2018 已提交
670

C
CyC2018 已提交
671
- 多维费用背包:物品不仅有重量,还有体积,同时考虑这两种限制
C
CyC2018 已提交
672

C
CyC2018 已提交
673
- 其它:物品之间相互约束或者依赖
C
CyC2018 已提交
674

C
CyC2018 已提交
675
## 划分数组为和相等的两部分
C
CyC2018 已提交
676

C
CyC2018 已提交
677
[416. Partition Equal Subset Sum (Medium)](https://leetcode.com/problems/partition-equal-subset-sum/description/)
C
CyC2018 已提交
678 679

```html
C
CyC2018 已提交
680
Input: [1, 5, 11, 5]
C
CyC2018 已提交
681

C
CyC2018 已提交
682
Output: true
C
CyC2018 已提交
683

C
CyC2018 已提交
684
Explanation: The array can be partitioned as [1, 5, 5] and [11].
C
CyC2018 已提交
685 686
```

C
CyC2018 已提交
687
可以看成一个背包大小为 sum/2 的 0-1 背包问题。
C
CyC2018 已提交
688 689

```java
C
CyC2018 已提交
690 691 692 693 694 695 696 697 698 699 700 701 702 703
public boolean canPartition(int[] nums) {
    int sum = computeArraySum(nums);
    if (sum % 2 != 0) {
        return false;
    }
    int W = sum / 2;
    boolean[] dp = new boolean[W + 1];
    dp[0] = true;
    for (int num : nums) {                 // 0-1 背包一个物品只能用一次
        for (int i = W; i >= num; i--) {   // 从后往前,先计算 dp[i] 再计算 dp[i-num]
            dp[i] = dp[i] || dp[i - num];
        }
    }
    return dp[W];
C
CyC2018 已提交
704 705
}

C
CyC2018 已提交
706 707 708 709 710 711
private int computeArraySum(int[] nums) {
    int sum = 0;
    for (int num : nums) {
        sum += num;
    }
    return sum;
C
CyC2018 已提交
712 713 714
}
```

C
CyC2018 已提交
715
## 改变一组数的正负号使得它们的和为一给定数
C
CyC2018 已提交
716

C
CyC2018 已提交
717
[494. Target Sum (Medium)](https://leetcode.com/problems/target-sum/description/)
C
CyC2018 已提交
718 719

```html
C
CyC2018 已提交
720 721
Input: nums is [1, 1, 1, 1, 1], S is 3.
Output: 5
C
CyC2018 已提交
722 723
Explanation:

C
CyC2018 已提交
724 725 726 727 728
-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3
C
CyC2018 已提交
729

C
CyC2018 已提交
730
There are 5 ways to assign symbols to make the sum of nums be target 3.
C
CyC2018 已提交
731 732
```

C
CyC2018 已提交
733
该问题可以转换为 Subset Sum 问题,从而使用 0-1 背包的方法来求解。
C
CyC2018 已提交
734

C
CyC2018 已提交
735
可以将这组数看成两部分,P 和 N,其中 P 使用正号,N 使用负号,有以下推导:
C
CyC2018 已提交
736 737

```html
C
CyC2018 已提交
738 739 740
                  sum(P) - sum(N) = target
sum(P) + sum(N) + sum(P) - sum(N) = target + sum(P) + sum(N)
                       2 * sum(P) = target + sum(nums)
C
CyC2018 已提交
741 742
```

C
CyC2018 已提交
743
因此只要找到一个子集,令它们都取正号,并且和等于 (target + sum(nums))/2,就证明存在解。
C
CyC2018 已提交
744 745

```java
C
CyC2018 已提交
746 747 748 749 750 751 752 753 754 755 756 757 758 759
public int findTargetSumWays(int[] nums, int S) {
    int sum = computeArraySum(nums);
    if (sum < S || (sum + S) % 2 == 1) {
        return 0;
    }
    int W = (sum + S) / 2;
    int[] dp = new int[W + 1];
    dp[0] = 1;
    for (int num : nums) {
        for (int i = W; i >= num; i--) {
            dp[i] = dp[i] + dp[i - num];
        }
    }
    return dp[W];
C
CyC2018 已提交
760 761
}

C
CyC2018 已提交
762 763 764 765 766 767
private int computeArraySum(int[] nums) {
    int sum = 0;
    for (int num : nums) {
        sum += num;
    }
    return sum;
C
CyC2018 已提交
768 769 770
}
```

C
CyC2018 已提交
771
DFS 解法:
C
CyC2018 已提交
772 773

```java
C
CyC2018 已提交
774 775
public int findTargetSumWays(int[] nums, int S) {
    return findTargetSumWays(nums, 0, S);
C
CyC2018 已提交
776 777
}

C
CyC2018 已提交
778 779 780 781 782 783
private int findTargetSumWays(int[] nums, int start, int S) {
    if (start == nums.length) {
        return S == 0 ? 1 : 0;
    }
    return findTargetSumWays(nums, start + 1, S + nums[start])
            + findTargetSumWays(nums, start + 1, S - nums[start]);
C
CyC2018 已提交
784 785 786
}
```

C
CyC2018 已提交
787
## 01 字符构成最多的字符串
C
CyC2018 已提交
788

C
CyC2018 已提交
789
[474. Ones and Zeroes (Medium)](https://leetcode.com/problems/ones-and-zeroes/description/)
C
CyC2018 已提交
790 791

```html
C
CyC2018 已提交
792 793
Input: Array = {"10", "0001", "111001", "1", "0"}, m = 5, n = 3
Output: 4
C
CyC2018 已提交
794

C
CyC2018 已提交
795
Explanation: There are totally 4 strings can be formed by the using of 5 0s and 3 1s, which are "10","0001","1","0"
C
CyC2018 已提交
796 797
```

C
CyC2018 已提交
798
这是一个多维费用的 0-1 背包问题,有两个背包大小,0 的数量和 1 的数量。
C
CyC2018 已提交
799 800

```java
C
CyC2018 已提交
801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821
public int findMaxForm(String[] strs, int m, int n) {
    if (strs == null || strs.length == 0) {
        return 0;
    }
    int[][] dp = new int[m + 1][n + 1];
    for (String s : strs) {    // 每个字符串只能用一次
        int ones = 0, zeros = 0;
        for (char c : s.toCharArray()) {
            if (c == '0') {
                zeros++;
            } else {
                ones++;
            }
        }
        for (int i = m; i >= zeros; i--) {
            for (int j = n; j >= ones; j--) {
                dp[i][j] = Math.max(dp[i][j], dp[i - zeros][j - ones] + 1);
            }
        }
    }
    return dp[m][n];
C
CyC2018 已提交
822 823 824
}
```

C
CyC2018 已提交
825
## 找零钱的最少硬币数
C
CyC2018 已提交
826

C
CyC2018 已提交
827
[322. Coin Change (Medium)](https://leetcode.com/problems/coin-change/description/)
C
CyC2018 已提交
828 829

```html
C
CyC2018 已提交
830 831 832
Example 1:
coins = [1, 2, 5], amount = 11
return 3 (11 = 5 + 5 + 1)
C
CyC2018 已提交
833

C
CyC2018 已提交
834 835 836
Example 2:
coins = [2], amount = 3
return -1.
C
CyC2018 已提交
837 838 839 840
```

题目描述:给一些面额的硬币,要求用这些硬币来组成给定面额的钱数,并且使得硬币数量最少。硬币可以重复使用。

C
CyC2018 已提交
841 842 843
- 物品:硬币
- 物品大小:面额
- 物品价值:数量
C
CyC2018 已提交
844

C
CyC2018 已提交
845
因为硬币可以重复使用,因此这是一个完全背包问题。完全背包只需要将 0-1 背包中逆序遍历 dp 数组改为正序遍历即可。
C
CyC2018 已提交
846 847

```java
C
CyC2018 已提交
848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864
public int coinChange(int[] coins, int amount) {
    if (amount == 0 || coins == null || coins.length == 0) {
        return 0;
    }
    int[] dp = new int[amount + 1];
    for (int coin : coins) {
        for (int i = coin; i <= amount; i++) { //将逆序遍历改为正序遍历
            if (i == coin) {
                dp[i] = 1;
            } else if (dp[i] == 0 && dp[i - coin] != 0) {
                dp[i] = dp[i - coin] + 1;
            } else if (dp[i - coin] != 0) {
                dp[i] = Math.min(dp[i], dp[i - coin] + 1);
            }
        }
    }
    return dp[amount] == 0 ? -1 : dp[amount];
C
CyC2018 已提交
865 866 867
}
```

C
CyC2018 已提交
868
## 找零钱的硬币数组合
C
CyC2018 已提交
869

C
CyC2018 已提交
870
[518\. Coin Change 2 (Medium)](https://leetcode.com/problems/coin-change-2/description/)
C
CyC2018 已提交
871 872

```text-html-basic
C
CyC2018 已提交
873 874 875
Input: amount = 5, coins = [1, 2, 5]
Output: 4
Explanation: there are four ways to make up the amount:
C
CyC2018 已提交
876 877 878 879 880 881
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
```

C
CyC2018 已提交
882
完全背包问题,使用 dp 记录可达成目标的组合数目。
C
CyC2018 已提交
883 884

```java
C
CyC2018 已提交
885 886 887 888 889 890 891 892 893 894 895 896
public int change(int amount, int[] coins) {
    if (amount == 0 || coins == null || coins.length == 0) {
        return 0;
    }
    int[] dp = new int[amount + 1];
    dp[0] = 1;
    for (int coin : coins) {
        for (int i = coin; i <= amount; i++) {
            dp[i] += dp[i - coin];
        }
    }
    return dp[amount];
C
CyC2018 已提交
897 898 899
}
```

C
CyC2018 已提交
900
## 字符串按单词列表分割
C
CyC2018 已提交
901

C
CyC2018 已提交
902
[139. Word Break (Medium)](https://leetcode.com/problems/word-break/description/)
C
CyC2018 已提交
903 904

```html
C
CyC2018 已提交
905 906 907
s = "leetcode",
dict = ["leet", "code"].
Return true because "leetcode" can be segmented as "leet code".
C
CyC2018 已提交
908 909
```

C
CyC2018 已提交
910
dict 中的单词没有使用次数的限制,因此这是一个完全背包问题。该问题涉及到字典中单词的使用顺序,因此可理解为涉及顺序的完全背包问题。
C
CyC2018 已提交
911 912 913 914

求解顺序的完全背包问题时,对物品的迭代应该放在最里层。

```java
C
CyC2018 已提交
915 916 917 918 919 920 921 922 923 924 925 926 927
public boolean wordBreak(String s, List<String> wordDict) {
    int n = s.length();
    boolean[] dp = new boolean[n + 1];
    dp[0] = true;
    for (int i = 1; i <= n; i++) {
        for (String word : wordDict) {   // 对物品的迭代应该放在最里层
            int len = word.length();
            if (len <= i && word.equals(s.substring(i - len, i))) {
                dp[i] = dp[i] || dp[i - len];
            }
        }
    }
    return dp[n];
C
CyC2018 已提交
928 929 930
}
```

C
CyC2018 已提交
931
## 组合总和
C
CyC2018 已提交
932

C
CyC2018 已提交
933
[377. Combination Sum IV (Medium)](https://leetcode.com/problems/combination-sum-iv/description/)
C
CyC2018 已提交
934 935

```html
C
CyC2018 已提交
936 937
nums = [1, 2, 3]
target = 4
C
CyC2018 已提交
938

C
CyC2018 已提交
939 940 941 942 943 944 945 946
The possible combination ways are:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
C
CyC2018 已提交
947

C
CyC2018 已提交
948
Note that different sequences are counted as different combinations.
C
CyC2018 已提交
949

C
CyC2018 已提交
950
Therefore the output is 7.
C
CyC2018 已提交
951 952 953 954 955
```

涉及顺序的完全背包。

```java
C
CyC2018 已提交
956 957 958 959 960 961 962 963 964 965 966 967 968
public int combinationSum4(int[] nums, int target) {
    if (nums == null || nums.length == 0) {
        return 0;
    }
    int[] maximum = new int[target + 1];
    maximum[0] = 1;
    Arrays.sort(nums);
    for (int i = 1; i <= target; i++) {
        for (int j = 0; j < nums.length && nums[j] <= i; j++) {
            maximum[i] += maximum[i - nums[j]];
        }
    }
    return maximum[target];
C
CyC2018 已提交
969 970 971
}
```

C
CyC2018 已提交
972
# 股票交易
C
CyC2018 已提交
973

C
CyC2018 已提交
974
## 需要冷却期的股票交易
C
CyC2018 已提交
975

C
CyC2018 已提交
976
[309. Best Time to Buy and Sell Stock with Cooldown(Medium)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-cooldown/description/)
C
CyC2018 已提交
977 978 979

题目描述:交易之后需要有一天的冷却时间。

C
CyC2018 已提交
980
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/83acbb02-872a-4178-b22a-c89c3cb60263.jpg" width="300px"> </div><br>
C
CyC2018 已提交
981 982

```java
C
CyC2018 已提交
983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000
public int maxProfit(int[] prices) {
    if (prices == null || prices.length == 0) {
        return 0;
    }
    int N = prices.length;
    int[] buy = new int[N];
    int[] s1 = new int[N];
    int[] sell = new int[N];
    int[] s2 = new int[N];
    s1[0] = buy[0] = -prices[0];
    sell[0] = s2[0] = 0;
    for (int i = 1; i < N; i++) {
        buy[i] = s2[i - 1] - prices[i];
        s1[i] = Math.max(buy[i - 1], s1[i - 1]);
        sell[i] = Math.max(buy[i - 1], s1[i - 1]) + prices[i];
        s2[i] = Math.max(s2[i - 1], sell[i - 1]);
    }
    return Math.max(sell[N - 1], s2[N - 1]);
C
CyC2018 已提交
1001 1002 1003
}
```

C
CyC2018 已提交
1004
## 需要交易费用的股票交易
C
CyC2018 已提交
1005

C
CyC2018 已提交
1006
[714. Best Time to Buy and Sell Stock with Transaction Fee (Medium)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-with-transaction-fee/description/)
C
CyC2018 已提交
1007 1008

```html
C
CyC2018 已提交
1009 1010 1011 1012 1013 1014 1015 1016
Input: prices = [1, 3, 2, 8, 4, 9], fee = 2
Output: 8
Explanation: The maximum profit can be achieved by:
Buying at prices[0] = 1
Selling at prices[3] = 8
Buying at prices[4] = 4
Selling at prices[5] = 9
The total profit is ((8 - 1) - 2) + ((9 - 4) - 2) = 8.
C
CyC2018 已提交
1017 1018 1019 1020
```

题目描述:每交易一次,都要支付一定的费用。

C
CyC2018 已提交
1021
<div align="center"> <img src="https://cs-notes-1256109796.cos.ap-guangzhou.myqcloud.com/1e2c588c-72b7-445e-aacb-d55dc8a88c29.png" width="300px"> </div><br>
C
CyC2018 已提交
1022 1023

```java
C
CyC2018 已提交
1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038
public int maxProfit(int[] prices, int fee) {
    int N = prices.length;
    int[] buy = new int[N];
    int[] s1 = new int[N];
    int[] sell = new int[N];
    int[] s2 = new int[N];
    s1[0] = buy[0] = -prices[0];
    sell[0] = s2[0] = 0;
    for (int i = 1; i < N; i++) {
        buy[i] = Math.max(sell[i - 1], s2[i - 1]) - prices[i];
        s1[i] = Math.max(buy[i - 1], s1[i - 1]);
        sell[i] = Math.max(buy[i - 1], s1[i - 1]) - fee + prices[i];
        s2[i] = Math.max(s2[i - 1], sell[i - 1]);
    }
    return Math.max(sell[N - 1], s2[N - 1]);
C
CyC2018 已提交
1039 1040 1041 1042
}
```


C
CyC2018 已提交
1043
## 只能进行两次的股票交易
C
CyC2018 已提交
1044

C
CyC2018 已提交
1045
[123. Best Time to Buy and Sell Stock III (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/description/)
C
CyC2018 已提交
1046 1047

```java
C
CyC2018 已提交
1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065
public int maxProfit(int[] prices) {
    int firstBuy = Integer.MIN_VALUE, firstSell = 0;
    int secondBuy = Integer.MIN_VALUE, secondSell = 0;
    for (int curPrice : prices) {
        if (firstBuy < -curPrice) {
            firstBuy = -curPrice;
        }
        if (firstSell < firstBuy + curPrice) {
            firstSell = firstBuy + curPrice;
        }
        if (secondBuy < firstSell - curPrice) {
            secondBuy = firstSell - curPrice;
        }
        if (secondSell < secondBuy + curPrice) {
            secondSell = secondBuy + curPrice;
        }
    }
    return secondSell;
C
CyC2018 已提交
1066 1067 1068
}
```

C
CyC2018 已提交
1069
## 只能进行 k 次的股票交易
C
CyC2018 已提交
1070

C
CyC2018 已提交
1071
[188. Best Time to Buy and Sell Stock IV (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/description/)
C
CyC2018 已提交
1072 1073

```java
C
CyC2018 已提交
1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093
public int maxProfit(int k, int[] prices) {
    int n = prices.length;
    if (k >= n / 2) {   // 这种情况下该问题退化为普通的股票交易问题
        int maxProfit = 0;
        for (int i = 1; i < n; i++) {
            if (prices[i] > prices[i - 1]) {
                maxProfit += prices[i] - prices[i - 1];
            }
        }
        return maxProfit;
    }
    int[][] maxProfit = new int[k + 1][n];
    for (int i = 1; i <= k; i++) {
        int localMax = maxProfit[i - 1][0] - prices[0];
        for (int j = 1; j < n; j++) {
            maxProfit[i][j] = Math.max(maxProfit[i][j - 1], prices[j] + localMax);
            localMax = Math.max(localMax, maxProfit[i - 1][j] - prices[j]);
        }
    }
    return maxProfit[k][n - 1];
C
CyC2018 已提交
1094 1095 1096
}
```

C
CyC2018 已提交
1097
# 字符串编辑
C
CyC2018 已提交
1098

C
CyC2018 已提交
1099
## 删除两个字符串的字符使它们相等
C
CyC2018 已提交
1100

C
CyC2018 已提交
1101
[583. Delete Operation for Two Strings (Medium)](https://leetcode.com/problems/delete-operation-for-two-strings/description/)
C
CyC2018 已提交
1102 1103

```html
C
CyC2018 已提交
1104 1105 1106
Input: "sea", "eat"
Output: 2
Explanation: You need one step to make "sea" to "ea" and another step to make "eat" to "ea".
C
CyC2018 已提交
1107 1108 1109 1110 1111
```

可以转换为求两个字符串的最长公共子序列问题。

```java
C
CyC2018 已提交
1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124
public int minDistance(String word1, String word2) {
    int m = word1.length(), n = word2.length();
    int[][] dp = new int[m + 1][n + 1];
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]);
            }
        }
    }
    return m + n - 2 * dp[m][n];
C
CyC2018 已提交
1125 1126 1127
}
```

C
CyC2018 已提交
1128
## 编辑距离
C
CyC2018 已提交
1129

C
CyC2018 已提交
1130
[72. Edit Distance (Hard)](https://leetcode.com/problems/edit-distance/description/)
C
CyC2018 已提交
1131 1132

```html
C
CyC2018 已提交
1133
Example 1:
C
CyC2018 已提交
1134

C
CyC2018 已提交
1135 1136
Input: word1 = "horse", word2 = "ros"
Output: 3
C
CyC2018 已提交
1137
Explanation:
C
CyC2018 已提交
1138 1139 1140 1141
horse -> rorse (replace 'h' with 'r')
rorse -> rose (remove 'r')
rose -> ros (remove 'e')
Example 2:
C
CyC2018 已提交
1142

C
CyC2018 已提交
1143 1144
Input: word1 = "intention", word2 = "execution"
Output: 5
C
CyC2018 已提交
1145
Explanation:
C
CyC2018 已提交
1146 1147 1148 1149 1150
intention -> inention (remove 't')
inention -> enention (replace 'i' with 'e')
enention -> exention (replace 'n' with 'x')
exention -> exection (replace 'n' with 'c')
exection -> execution (insert 'u')
C
CyC2018 已提交
1151 1152 1153 1154 1155
```

题目描述:修改一个字符串成为另一个字符串,使得修改次数最少。一次修改操作包括:插入一个字符、删除一个字符、替换一个字符。

```java
C
CyC2018 已提交
1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177
public int minDistance(String word1, String word2) {
    if (word1 == null || word2 == null) {
        return 0;
    }
    int m = word1.length(), n = word2.length();
    int[][] dp = new int[m + 1][n + 1];
    for (int i = 1; i <= m; i++) {
        dp[i][0] = i;
    }
    for (int i = 1; i <= n; i++) {
        dp[0][i] = i;
    }
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                dp[i][j] = Math.min(dp[i - 1][j - 1], Math.min(dp[i][j - 1], dp[i - 1][j])) + 1;
            }
        }
    }
    return dp[m][n];
C
CyC2018 已提交
1178 1179 1180
}
```

C
CyC2018 已提交
1181
## 复制粘贴字符
C
CyC2018 已提交
1182

C
CyC2018 已提交
1183
[650. 2 Keys Keyboard (Medium)](https://leetcode.com/problems/2-keys-keyboard/description/)
C
CyC2018 已提交
1184

C
CyC2018 已提交
1185
题目描述:最开始只有一个字符 A,问需要多少次操作能够得到 n 个字符 A,每次操作可以复制当前所有的字符,或者粘贴。
C
CyC2018 已提交
1186 1187

```
C
CyC2018 已提交
1188 1189
Input: 3
Output: 3
C
CyC2018 已提交
1190
Explanation:
C
CyC2018 已提交
1191 1192 1193 1194
Intitally, we have one character 'A'.
In step 1, we use Copy All operation.
In step 2, we use Paste operation to get 'AA'.
In step 3, we use Paste operation to get 'AAA'.
C
CyC2018 已提交
1195 1196 1197
```

```java
C
CyC2018 已提交
1198 1199 1200 1201 1202 1203
public int minSteps(int n) {
    if (n == 1) return 0;
    for (int i = 2; i <= Math.sqrt(n); i++) {
        if (n % i == 0) return i + minSteps(n / i);
    }
    return n;
C
CyC2018 已提交
1204 1205 1206 1207
}
```

```java
C
CyC2018 已提交
1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220
public int minSteps(int n) {
    int[] dp = new int[n + 1];
    int h = (int) Math.sqrt(n);
    for (int i = 2; i <= n; i++) {
        dp[i] = i;
        for (int j = 2; j <= h; j++) {
            if (i % j == 0) {
                dp[i] = dp[j] + dp[i / j];
                break;
            }
        }
    }
    return dp[n];
C
CyC2018 已提交
1221 1222
}
```
C
CyC2018 已提交
1223 1224 1225 1226




C
CyC2018 已提交
1227
</br><div align="center">关注公众号 CyC2018 获取更多精彩内容!在公众号后台回复关键字 **资料** 可领取一份技术面试复习思维导图,帮你理清多而杂的面试知识点。
C
CyC2018 已提交
1228
<div align="center"><img width="180px" src="https://cyc-1256109796.cos.ap-guangzhou.myqcloud.com/%E5%85%AC%E4%BC%97%E5%8F%B7.jpg"></img></div>