auto commit
This commit is contained in:
@ -2389,8 +2389,6 @@ private void backtracking(int row) {
|
||||
|
||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i]=dp[i-1]+dp[i-2]"/></div> <br>
|
||||
|
||||
dp[N] 即为所求。
|
||||
|
||||
考虑到 dp[i] 只与 dp[i - 1] 和 dp[i - 2] 有关,因此可以只用两个变量来存储 dp[i - 1] 和 dp[i - 2],使得原来的 O(N) 空间复杂度优化为 O(1) 复杂度。
|
||||
|
||||
```java
|
||||
@ -2468,16 +2466,6 @@ private int rob(int[] nums, int first, int last) {
|
||||
}
|
||||
```
|
||||
|
||||
**母牛生产**
|
||||
|
||||
[程序员代码面试指南-P181](#)
|
||||
|
||||
题目描述:假设农场中成熟的母牛每年都会生 1 头小母牛,并且永远不会死。第一年有 1 只小母牛,从第二年开始,母牛开始生小母牛。每只小母牛 3 年之后成熟又可以生小母牛。给定整数 N,求 N 年后牛的数量。
|
||||
|
||||
第 i 年成熟的牛的数量为:
|
||||
|
||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i]=dp[i-1]+dp[i-3]"/></div> <br>
|
||||
|
||||
**信件错排**
|
||||
|
||||
题目描述:有 N 个 信 和 信封,它们被打乱,求错误装信方式的数量。
|
||||
@ -2491,7 +2479,15 @@ private int rob(int[] nums, int first, int last) {
|
||||
|
||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i]=(i-1)*dp[i-2]+(i-1)*dp[i-1]"/></div> <br>
|
||||
|
||||
dp[N] 即为所求。
|
||||
**母牛生产**
|
||||
|
||||
[程序员代码面试指南-P181](#)
|
||||
|
||||
题目描述:假设农场中成熟的母牛每年都会生 1 头小母牛,并且永远不会死。第一年有 1 只小母牛,从第二年开始,母牛开始生小母牛。每只小母牛 3 年之后成熟又可以生小母牛。给定整数 N,求 N 年后牛的数量。
|
||||
|
||||
第 i 年成熟的牛的数量为:
|
||||
|
||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[i]=dp[i-1]+dp[i-3]"/></div> <br>
|
||||
|
||||
### 矩阵路径
|
||||
|
||||
@ -2517,10 +2513,8 @@ public int minPathSum(int[][] grid) {
|
||||
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]; // 只能从左侧走到该位置
|
||||
if (i == 0) {
|
||||
dp[j] = dp[j - 1];
|
||||
} else {
|
||||
dp[j] = Math.min(dp[j - 1], dp[j]);
|
||||
}
|
||||
@ -2584,17 +2578,18 @@ sumRange(0, 5) -> -3
|
||||
|
||||
```java
|
||||
class NumArray {
|
||||
|
||||
private int[] sums;
|
||||
|
||||
public NumArray(int[] nums) {
|
||||
sums = new int[nums.length];
|
||||
for (int i = 0; i < nums.length; i++) {
|
||||
sums[i] = i == 0 ? nums[0] : sums[i - 1] + nums[i];
|
||||
sums = new int[nums.length + 1];
|
||||
for (int i = 1; i <= nums.length; i++) {
|
||||
sums[i] = sums[i - 1] + nums[i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
public int sumRange(int i, int j) {
|
||||
return i == 0 ? sums[j] : sums[j] - sums[i - 1];
|
||||
return sums[j + 1] - sums[i];
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -2634,7 +2629,7 @@ return: 3, for 3 arithmetic slices in A: [1, 2, 3], [2, 3, 4] and [1, 2, 3, 4] i
|
||||
|
||||
dp[i] 表示以 A[i] 为结尾的等差递增子区间的个数。
|
||||
|
||||
如果 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。
|
||||
在 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。
|
||||
|
||||
```java
|
||||
public int numberOfArithmeticSlices(int[] A) {
|
||||
@ -2747,17 +2742,17 @@ public int numDecodings(String s) {
|
||||
|
||||
### 最长递增子序列
|
||||
|
||||
已知一个序列 {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 保持递增,即新序列中各个数仍然保持原数列中的先后顺序,称新序列为原序列的一个 **子序列** 。
|
||||
已知一个序列 {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 保持递增,即新序列中各个数仍然保持原数列中的先后顺序,称新序列为原序列的一个 **子序列** 。
|
||||
|
||||
如果在子序列中,当下标 ix > iy 时,S<sub>ix</sub> > S<sub>iy</sub>,称子序列为原序列的一个 **递增子序列** 。
|
||||
|
||||
定义一个数组 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} 。
|
||||
定义一个数组 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} 。
|
||||
|
||||
因为在求 dp[n] 时可能无法找到一个满足条件的递增子序列,此时 {S<sub>n</sub>} 就构成了递增子序列,需要对前面的求解方程做修改,令 dp[n] 最小为 1,即:
|
||||
|
||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[n]=max\{1,dp[i]+1|S_i<S_n\&\&i<n\}"/></div> <br>
|
||||
|
||||
对于一个长度为 N 的序列,最长递增子序列并不一定会以 S<sub>N</sub> 为结尾,因此 dp[N] 不是序列的最长递增子序列的长度,需要遍历 dp 数组找出最大值才是所要的结果,即 max{ dp[i] | 1 <= i <= N} 即为所求。
|
||||
对于一个长度为 N 的序列,最长递增子序列并不一定会以 S<sub>N</sub> 为结尾,因此 dp[N] 不是序列的最长递增子序列的长度,需要遍历 dp 数组找出最大值才是所要的结果,max{ dp[i] | 1 <= i <= N} 即为所求。
|
||||
|
||||
**最长递增子序列**
|
||||
|
||||
@ -2790,7 +2785,7 @@ for (int i = 0; i < n; i++) {
|
||||
return ret;
|
||||
```
|
||||
|
||||
以上解法的时间复杂度为 O(N<sup>2</sup>) ,可以使用二分查找将时间复杂度降低为 O(NlogN)。
|
||||
以上解法的时间复杂度为 O(N<sup>2</sup>),可以使用二分查找将时间复杂度降低为 O(NlogN)。
|
||||
|
||||
定义一个 tails 数组,其中 tails[i] 存储长度为 i + 1 的最长递增子序列的最后一个元素。对于一个元素 x,
|
||||
|
||||
@ -2915,19 +2910,19 @@ public int wiggleMaxLength(int[] nums) {
|
||||
|
||||
定义一个二维数组 dp 用来存储最长公共子序列的长度,其中 dp[i][j] 表示 S1 的前 i 个字符与 S2 的前 j 个字符最长公共子序列的长度。考虑 S1<sub>i</sub> 与 S2<sub>j</sub> 值是否相等,分为两种情况:
|
||||
|
||||
- 当 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] }。
|
||||
- 当 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] }。
|
||||
|
||||
综上,最长公共子序列的状态转移方程为:
|
||||
|
||||
<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."/></div> <br>
|
||||
|
||||
对于长度为 N 的序列 S<sub>1</sub> 和 长度为 M 的序列 S<sub>2</sub>,dp[N][M] 就是序列 S<sub>1</sub> 和序列 S<sub>2</sub> 的最长公共子序列长度。
|
||||
对于长度为 N 的序列 S<sub>1</sub> 和长度为 M 的序列 S<sub>2</sub>,dp[N][M] 就是序列 S<sub>1</sub> 和序列 S<sub>2</sub> 的最长公共子序列长度。
|
||||
|
||||
与最长递增子序列相比,最长公共子序列有以下不同点:
|
||||
|
||||
- 针对的是两个序列,求它们的最长公共子序列。
|
||||
- 在最长递增子序列中,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[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 数组找到最大者。
|
||||
|
||||
```java
|
||||
@ -2956,9 +2951,7 @@ public int lengthOfLCS(int[] nums1, int[] nums2) {
|
||||
- 第 i 件物品没添加到背包,总体积不超过 j 的前 i 件物品的最大价值就是总体积不超过 j 的前 i-1 件物品的最大价值,dp[i][j] = dp[i-1][j]。
|
||||
- 第 i 件物品添加到背包中,dp[i][j] = dp[i-1][j-w] + v。
|
||||
|
||||
第 i 件物品可添加也可以不添加,取决于哪种情况下最大价值更大。
|
||||
|
||||
综上,0-1 背包的状态转移方程为:
|
||||
第 i 件物品可添加也可以不添加,取决于哪种情况下最大价值更大。因此,0-1 背包的状态转移方程为:
|
||||
|
||||
<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)"/></div> <br>
|
||||
|
||||
@ -2981,11 +2974,11 @@ public int knapsack(int W, int N, int[] weights, int[] values) {
|
||||
|
||||
**空间优化**
|
||||
|
||||
在程序实现时可以对 0-1 背包做优化。观察状态转移方程可以知道,前 i 件物品的状态仅由前 i-1 件物品的状态有关,因此可以将 dp 定义为一维数组,其中 dp[j] 既可以表示 dp[i-1][j] 也可以表示 dp[i][j]。此时,
|
||||
在程序实现时可以对 0-1 背包做优化。观察状态转移方程可以知道,前 i 件物品的状态仅与前 i-1 件物品的状态有关,因此可以将 dp 定义为一维数组,其中 dp[j] 既可以表示 dp[i-1][j] 也可以表示 dp[i][j]。此时,
|
||||
|
||||
<div align="center"><img src="https://latex.codecogs.com/gif.latex?dp[j]=max(dp[j],dp[j-w]+v)"/></div> <br>
|
||||
|
||||
因为 dp[j-w] 表示 dp[i-1][j-w],因此不能先求 dp[i][j-w],以防止将 dp[i-1][j-w] 覆盖。也就是说要先计算 dp[i][j] 再计算 dp[i][j-w],在程序实现时需要按倒序来循环求解。
|
||||
因为 dp[j-w] 表示 dp[i-1][j-w],因此不能先求 dp[i][j-w],以防将 dp[i-1][j-w] 覆盖。也就是说要先计算 dp[i][j] 再计算 dp[i][j-w],在程序实现时需要按倒序来循环求解。
|
||||
|
||||
```java
|
||||
public int knapsack(int W, int N, int[] weights, int[] values) {
|
||||
@ -3205,7 +3198,7 @@ public int findMaxForm(String[] strs, int m, int n) {
|
||||
}
|
||||
```
|
||||
|
||||
**找零钱的方法数**
|
||||
**找零钱的最少硬币数**
|
||||
|
||||
[322. Coin Change (Medium)](https://leetcode.com/problems/coin-change/description/)
|
||||
|
||||
@ -3286,60 +3279,6 @@ public int combinationSum4(int[] nums, int target) {
|
||||
}
|
||||
```
|
||||
|
||||
**只能进行 k 次的股票交易**
|
||||
|
||||
[188. Best Time to Buy and Sell Stock IV (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/description/)
|
||||
|
||||
```java
|
||||
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];
|
||||
}
|
||||
```
|
||||
|
||||
**只能进行两次的股票交易**
|
||||
|
||||
[123. Best Time to Buy and Sell Stock III (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/description/)
|
||||
|
||||
```java
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
### 股票交易
|
||||
|
||||
**需要冷却期的股票交易**
|
||||
@ -3432,6 +3371,60 @@ public int maxProfit(int[] prices) {
|
||||
}
|
||||
```
|
||||
|
||||
**只能进行两次的股票交易**
|
||||
|
||||
[123. Best Time to Buy and Sell Stock III (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iii/description/)
|
||||
|
||||
```java
|
||||
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;
|
||||
}
|
||||
```
|
||||
|
||||
**只能进行 k 次的股票交易**
|
||||
|
||||
[188. Best Time to Buy and Sell Stock IV (Hard)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-iv/description/)
|
||||
|
||||
```java
|
||||
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];
|
||||
}
|
||||
```
|
||||
|
||||
### 字符串编辑
|
||||
|
||||
**删除两个字符串的字符使它们相等**
|
||||
@ -3450,11 +3443,8 @@ Explanation: You need one step to make "sea" to "ea" and another step to make "e
|
||||
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 = 0; i <= m; i++) {
|
||||
for (int j = 0; j <= n; j++) {
|
||||
if (i == 0 || j == 0) {
|
||||
continue;
|
||||
}
|
||||
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 {
|
||||
|
Reference in New Issue
Block a user