@@ -1841,9 +1841,9 @@ private int rob(int[] nums, int first, int last) {
1841
1841
1842
1842
定义一个数组 dp 存储错误方式数量,dp[ i] 表示前 i 个信和信封的错误方式数量。假设第 i 个信装到第 j 个信封里面,而第 j 个信装到第 k 个信封里面。根据 i 和 k 是否相等,有两种情况:
1843
1843
1844
- ① i==k,交换 i 和 k 的信后,它们的信和信封在正确的位置,但是其余 i-2 封信有 dp[ i-2] 种错误装信的方式。由于 j 有 i-1 种取值,因此共有 (i-1)\* dp[ i-2] 种错误装信方式。
1844
+ 1 . i==k,交换 i 和 k 的信后,它们的信和信封在正确的位置,但是其余 i-2 封信有 dp[ i-2] 种错误装信的方式。由于 j 有 i-1 种取值,因此共有 (i-1)\* dp[ i-2] 种错误装信方式。
1845
1845
1846
- ② i != k,交换 i 和 j 的信后,第 i 个信和信封在正确的位置,其余 i-1 封信有 dp[ i-1] 种错误装信方式。由于 j 有 i-1 种取值,因此共有 (n-1)\* dp[ i-1] 种错误装信方式。
1846
+ 2 . i != k,交换 i 和 j 的信后,第 i 个信和信封在正确的位置,其余 i-1 封信有 dp[ i-1] 种错误装信方式。由于 j 有 i-1 种取值,因此共有 (n-1)\* dp[ i-1] 种错误装信方式。
1847
1847
1848
1848
综上所述,错误装信数量方式数量为:
1849
1849
@@ -1898,7 +1898,10 @@ len = 2 : [4, 5], [5, 6] => tails[1] = 5
1898
1898
len = 3 : [4, 5, 6] => tails[2] = 6
1899
1899
```
1900
1900
1901
- 对于一个元素 x,如果它大于 tails 数组所有的值,那么把它添加到 tails 后面;如果 tails[ i-1] < x <= tails[ i] ,那么更新 tails[ i] = x 。
1901
+ 对于一个元素 x,
1902
+
1903
+ - 如果它大于 tails 数组所有的值,那么把它添加到 tails 后面,表示最长递增子序列长度加 1;
1904
+ - 如果 tails[ i-1] < x <= tails[ i] ,那么更新 tails[ i] = x。
1902
1905
1903
1906
可以看出 tails 数组保持有序,因此在查找 S<sub >i</sub > 位于 tails 数组的位置时就可以使用二分查找。
1904
1907
@@ -1926,6 +1929,43 @@ private int binarySearch(int[] nums, int first, int last, int key) {
1926
1929
}
1927
1930
```
1928
1931
1932
+ ** 一组整数对能够构成的最长链**
1933
+
1934
+ [ Leetcode : 646. Maximum Length of Pair Chain (Medium)] ( https://leetcode.com/problems/maximum-length-of-pair-chain/description/ )
1935
+
1936
+ ``` html
1937
+ Input: [[1,2], [2,3], [3,4]]
1938
+ Output: 2
1939
+ Explanation: The longest chain is [1,2] -> [3,4]
1940
+ ```
1941
+
1942
+ 对于 (a, b) 和 (c, d) ,如果 b < c,则它们可以构成一条链。
1943
+
1944
+ ``` java
1945
+ public int findLongestChain(int [][] pairs) {
1946
+ if (pairs == null || pairs. length == 0 ) {
1947
+ return 0 ;
1948
+ }
1949
+ Arrays . sort(pairs, (a, b) - > (a[0 ] - b[0 ]));
1950
+ int n = pairs. length;
1951
+ int [] dp = new int [n];
1952
+ Arrays . fill(dp, 1 );
1953
+ for (int i = 0 ; i < n; i++ ) {
1954
+ for (int j = 0 ; j < i; j++ ) {
1955
+ if (pairs[i][0 ] > pairs[j][1 ]){
1956
+ dp[i] = Math . max(dp[i], dp[j] + 1 );
1957
+ }
1958
+ }
1959
+ }
1960
+
1961
+ int ret = 0 ;
1962
+ for (int num : dp) {
1963
+ ret = Math . max(ret, num);
1964
+ }
1965
+ return ret;
1966
+ }
1967
+ ```
1968
+
1929
1969
** 最长摆动子序列**
1930
1970
1931
1971
[ Leetcode : 376. Wiggle Subsequence (Medium)] ( https://leetcode.com/problems/wiggle-subsequence/description/ )
@@ -1966,9 +2006,8 @@ public int wiggleMaxLength(int[] nums) {
1966
2006
1967
2007
定义一个二维数组 dp 用来存储最长公共子序列的长度,其中 dp[ i] [ j ] 表示 S1 的前 i 个字符与 S2 的前 j 个字符最长公共子序列的长度。考虑 S1<sub >i</sub > 与 S2<sub >j</sub > 值是否相等,分为两种情况:
1968
2008
1969
- ① 当 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。
1970
-
1971
- ② 当 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 ] }。
2009
+ 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。
2010
+ 2 . 当 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 ] }。
1972
2011
1973
2012
综上,最长公共子序列的状态转移方程为:
1974
2013
@@ -1978,9 +2017,9 @@ public int wiggleMaxLength(int[] nums) {
1978
2017
1979
2018
与最长递增子序列相比,最长公共子序列有以下不同点:
1980
2019
1981
- ① 针对的是两个序列,求它们的最长公共子序列。
1982
- ② 在最长递增子序列中,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 > 。
1983
- ③ 由于 2 ,在求最终解时,最长公共子序列中 dp[ N] [ M ] 就是最终解,而最长递增子序列中 dp[ N] 不是最终解,因为以 S<sub >N</sub > 为结尾的最长递增子序列不一定是整个序列最长递增子序列,需要遍历一遍 dp 数组找到最大者。
2020
+ - 针对的是两个序列,求它们的最长公共子序列。
2021
+ - 在最长递增子序列中,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 > 。
2022
+ - 由于 2 ,在求最终解时,最长公共子序列中 dp[ N] [ M ] 就是最终解,而最长递增子序列中 dp[ N] 不是最终解,因为以 S<sub >N</sub > 为结尾的最长递增子序列不一定是整个序列最长递增子序列,需要遍历一遍 dp 数组找到最大者。
1984
2023
1985
2024
``` java
1986
2025
public int lengthOfLCS(int [] nums1, int [] nums2) {
@@ -2002,8 +2041,8 @@ public int lengthOfLCS(int[] nums1, int[] nums2) {
2002
2041
2003
2042
定义一个二维数组 dp 存储最大价值,其中 dp[ i] [ j ] 表示体积不超过 j 的情况下,前 i 件物品能达到的最大价值。设第 i 件物品体积为 w,价值为 v,根据第 i 件物品是否添加到背包中,可以分两种情况讨论:
2004
2043
2005
- ① 第 i 件物品没添加到背包,总体积不超过 j 的前 i 件物品的最大价值就是总体积不超过 j 的前 i-1 件物品的最大价值,dp[ i] [ j ] = dp[ i-1] [ j ] 。
2006
- ② 第 i 件物品添加到背包中,dp[ i] [ j ] = dp[ i-1] [ j-w ] + v。
2044
+ 1 . 第 i 件物品没添加到背包,总体积不超过 j 的前 i 件物品的最大价值就是总体积不超过 j 的前 i-1 件物品的最大价值,dp[ i] [ j ] = dp[ i-1] [ j ] 。
2045
+ 2 . 第 i 件物品添加到背包中,dp[ i] [ j ] = dp[ i-1] [ j-w ] + v。
2007
2046
2008
2047
第 i 件物品可添加也可以不添加,取决于哪种情况下最大价值更大。
2009
2048
@@ -2069,33 +2108,25 @@ Output: true
2069
2108
Explanation: The array can be partitioned as [1, 5, 5] and [11].
2070
2109
```
2071
2110
2072
- 可以看成一个背包大小为 sum/2 的 0-1 背包问题,但是也有不同的地方,这里没有价值属性,并且背包必须被填满。
2073
-
2074
- 以下实现使用了空间优化。
2111
+ 可以看成一个背包大小为 sum/2 的 0-1 背包问题。
2075
2112
2076
2113
``` java
2077
- public boolean canPartition(int [] nums) {
2078
- int sum = 0 ;
2079
- for (int num : nums) {
2080
- sum += num;
2081
- }
2082
- if (sum % 2 != 0 ) {
2083
- return false ;
2084
- }
2085
- int W = sum / 2 ;
2086
- boolean [] dp = new boolean [W + 1 ];
2087
- for (int i = 0 ; i <= W ; i++ ) {
2088
- if (nums[0 ] == i) {
2089
- dp[i] = true ;
2090
- }
2091
- }
2092
- for (int i = 1 ; i < nums. length; i++ ) {
2093
- for (int j = W ; j >= nums[i]; j-- ) {
2094
- dp[j] = dp[j] || dp[j - nums[i]];
2095
- }
2096
- }
2097
- return dp[W ];
2098
- }
2114
+ public boolean canPartition(int [] nums) {
2115
+ int sum = 0 ;
2116
+ for (int num : nums) sum += num;
2117
+ if (sum % 2 != 0 ) return false ;
2118
+ int W = sum / 2 ;
2119
+ boolean [] dp = new boolean [W + 1 ];
2120
+ dp[0 ] = true ;
2121
+ for (int num : nums) { // 0-1 背包一个物品只能用一次
2122
+ for (int i = W ; i >= 0 ; i-- ) { // 从后往前,先计算 dp[i] 再计算 dp[i-num]
2123
+ if (num <= i) {
2124
+ dp[i] = dp[i] || dp[i - num];
2125
+ }
2126
+ }
2127
+ }
2128
+ return dp[W ];
2129
+ }
2099
2130
```
2100
2131
2101
2132
** 字符串按单词列表分割**
@@ -2108,15 +2139,20 @@ dict = ["leet", "code"].
2108
2139
Return true because "leetcode" can be segmented as "leet code".
2109
2140
```
2110
2141
2142
+ 这是一个完全背包问题,和 0-1 背包不同的是,完全背包中物品可以使用多次。在这一题当中,词典中的单词可以被使用多次。
2143
+
2144
+ 0-1 背包和完全背包在实现上的不同之处是,0-1 背包对物品的迭代是在最外层,而完全背包对物品的迭代是最最里层。
2145
+
2111
2146
``` java
2112
2147
public boolean wordBreak(String s, List<String > wordDict) {
2113
2148
int n = s. length();
2114
2149
boolean [] dp = new boolean [n + 1 ];
2115
2150
dp[0 ] = true ;
2116
2151
for (int i = 1 ; i <= n; i++ ) {
2117
- for (String word : wordDict) {
2118
- if (word. length() <= i && word. equals(s. substring(i - word. length(), i))) {
2119
- dp[i] = dp[i] || dp[i - word. length()];
2152
+ for (String word : wordDict) { // 每个单词可以使用多次
2153
+ int len = word. length();
2154
+ if (len <= i && word. equals(s. substring(i - len, i))) {
2155
+ dp[i] = dp[i] || dp[i - len];
2120
2156
}
2121
2157
}
2122
2158
}
@@ -2142,7 +2178,9 @@ Explanation:
2142
2178
There are 5 ways to assign symbols to make the sum of nums be target 3.
2143
2179
```
2144
2180
2145
- 该问题可以转换为 subset sum 问题,从而使用 0-1 背包的方法来求解。可以将这组数看成两部分,P 和 N,其中 P 使用正号,N 使用负号,有以下推导:
2181
+ 该问题可以转换为 Subset Sum 问题,从而使用 0-1 背包的方法来求解。
2182
+
2183
+ 可以将这组数看成两部分,P 和 N,其中 P 使用正号,N 使用负号,有以下推导:
2146
2184
2147
2185
``` html
2148
2186
sum(P) - sum(N) = target
@@ -2155,26 +2193,34 @@ sum(P) + sum(N) + sum(P) - sum(N) = target + sum(P) + sum(N)
2155
2193
``` java
2156
2194
public int findTargetSumWays(int [] nums, int S ) {
2157
2195
int sum = 0 ;
2196
+ for (int num : nums) sum += num;
2197
+ if (sum < S || (sum + S ) % 2 == 1 ) return 0 ;
2198
+ int W = (sum + S ) / 2 ;
2199
+ int [] dp = new int [W + 1 ];
2200
+ dp[0 ] = 1 ;
2158
2201
for (int num : nums) {
2159
- sum += num;
2160
- }
2161
- if (sum < S || (sum + S ) % 2 == 1 ) {
2162
- return 0 ;
2202
+ for (int i = W ; i >= 0 ; i-- ) {
2203
+ if (num <= i) {
2204
+ dp[i] = dp[i] + dp[i - num];
2205
+ }
2206
+ }
2163
2207
}
2164
- return subsetSum(nums, (sum + S ) >>> 1 ) ;
2208
+ return dp[ W ] ;
2165
2209
}
2210
+ ```
2166
2211
2167
- private int subsetSum(int [] nums, int targetSum) {
2168
- Arrays . sort(nums);
2169
- int [] dp = new int [targetSum + 1 ];
2170
- dp[0 ] = 1 ;
2171
- for (int i = 0 ; i < nums. length; i++ ) {
2172
- int num = nums[i];
2173
- for (int j = targetSum; j >= num; j-- ) {
2174
- dp[j] = dp[j] + dp[j - num];
2175
- }
2212
+ DFS 解法:
2213
+
2214
+ ``` java
2215
+ public int findTargetSumWays(int [] nums, int S ) {
2216
+ return findTargetSumWays(nums, 0 , S );
2217
+ }
2218
+
2219
+ private int findTargetSumWays(int [] nums, int start, int S ) {
2220
+ if (start == nums. length) {
2221
+ return S == 0 ? 1 : 0 ;
2176
2222
}
2177
- return dp[targetSum] ;
2223
+ return findTargetSumWays(nums, start + 1 , S + nums[start]) + findTargetSumWays(nums, start + 1 , S - nums[start]) ;
2178
2224
}
2179
2225
```
2180
2226
@@ -2446,10 +2492,36 @@ public int minDistance(String word1, String word2) {
2446
2492
}
2447
2493
```
2448
2494
2449
- ** 修改一个字符串称为另一个字符串** // TODO
2495
+ ** 修改一个字符串称为另一个字符串**
2450
2496
2451
2497
[ Leetcode : 72. Edit Distance (Hard)] ( https://leetcode.com/problems/edit-distance/description/ )
2452
2498
2499
+ ``` java
2500
+ public int minDistance(String word1, String word2) {
2501
+ if (word1 == null || word2 == null ) {
2502
+ return 0 ;
2503
+ }
2504
+ int m = word1. length(), n = word2. length();
2505
+ int [][] dp = new int [m + 1 ][n + 1 ];
2506
+ for (int i = 1 ; i <= m; i++ ) {
2507
+ dp[i][0 ] = i;
2508
+ }
2509
+ for (int i = 1 ; i <= n; i++ ) {
2510
+ dp[0 ][i] = i;
2511
+ }
2512
+ for (int i = 1 ; i <= m; i++ ) {
2513
+ for (int j = 1 ; j <= n; j++ ) {
2514
+ if (word1. charAt(i - 1 ) == word2. charAt(j - 1 )) {
2515
+ dp[i][j] = dp[i - 1 ][j - 1 ];
2516
+ } else {
2517
+ dp[i][j] = Math . min(dp[i - 1 ][j - 1 ], Math . min(dp[i][j - 1 ], dp[i - 1 ][j])) + 1 ;
2518
+ }
2519
+ }
2520
+ }
2521
+ return dp[m][n];
2522
+ }
2523
+ ```
2524
+
2453
2525
### 分割整数
2454
2526
2455
2527
** 分割整数的最大乘积**
@@ -2552,6 +2624,20 @@ public int uniquePaths(int m, int n) {
2552
2624
}
2553
2625
```
2554
2626
2627
+ 也可以直接用数学公式求解,这是一个组合问题。机器人总共移动的次数 S=m+n-2,向下移动的次数 D=m-1,那么问题可以看成从 S 从取出 D 个位置的组合数量,这个问题的解为 C(S, D)。
2628
+
2629
+ ``` java
2630
+ public int uniquePaths(int m, int n) {
2631
+ int S = m + n - 2 ; // 总共的移动次数
2632
+ int D = m - 1 ; // 向下的移动次数
2633
+ long ret = 1 ;
2634
+ for (int i = 1 ; i <= D ; i++ ) {
2635
+ ret = ret * (S - D + i) / i;
2636
+ }
2637
+ return (int ) ret;
2638
+ }
2639
+ ```
2640
+
2555
2641
** 矩阵的最小路径和**
2556
2642
2557
2643
[ Leetcode : 64. Minimum Path Sum (Medium)] ( https://leetcode.com/problems/minimum-path-sum/description/ )
@@ -2616,43 +2702,6 @@ public int maxProfit(int[] prices) {
2616
2702
}
2617
2703
```
2618
2704
2619
- ** 一组整数对能够构成的最长链**
2620
-
2621
- [ Leetcode : 646. Maximum Length of Pair Chain (Medium)] ( https://leetcode.com/problems/maximum-length-of-pair-chain/description/ )
2622
-
2623
- ``` html
2624
- Input: [[1,2], [2,3], [3,4]]
2625
- Output: 2
2626
- Explanation: The longest chain is [1,2] -> [3,4]
2627
- ```
2628
-
2629
- 对于 (a, b) 和 (c, d) ,如果 b < c,则它们可以构成一条链。
2630
-
2631
- ``` java
2632
- public int findLongestChain(int [][] pairs) {
2633
- if (pairs == null || pairs. length == 0 ) {
2634
- return 0 ;
2635
- }
2636
- Arrays . sort(pairs, (a, b) - > (a[0 ] - b[0 ]));
2637
- int n = pairs. length;
2638
- int [] dp = new int [n];
2639
- Arrays . fill(dp, 1 );
2640
- for (int i = 0 ; i < n; i++ ) {
2641
- for (int j = 0 ; j < i; j++ ) {
2642
- if (pairs[i][0 ] > pairs[j][1 ]){
2643
- dp[i] = Math . max(dp[i], dp[j] + 1 );
2644
- }
2645
- }
2646
- }
2647
-
2648
- int ret = 0 ;
2649
- for (int num : dp) {
2650
- ret = Math . max(ret, num);
2651
- }
2652
- return ret;
2653
- }
2654
- ```
2655
-
2656
2705
** 买入和售出股票最大的收益**
2657
2706
2658
2707
[ Leetcode : 121. Best Time to Buy and Sell Stock (Easy)] ( https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/ )
@@ -2679,6 +2728,18 @@ public int maxProfit(int[] prices) {
2679
2728
2680
2729
[ Leetcode : 650. 2 Keys Keyboard (Medium)] ( https://leetcode.com/problems/2-keys-keyboard/description/ )
2681
2730
2731
+ 题目描述:最开始只有一个字符 A,问需要多少次操作能够得到 n 个字符 A,每次操作可以复制当前所有的字符,或者粘贴。
2732
+
2733
+ ```
2734
+ Input: 3
2735
+ Output: 3
2736
+ Explanation:
2737
+ Intitally, we have one character 'A'.
2738
+ In step 1, we use Copy All operation.
2739
+ In step 2, we use Paste operation to get 'AA'.
2740
+ In step 3, we use Paste operation to get 'AAA'.
2741
+ ```
2742
+
2682
2743
``` java
2683
2744
public int minSteps(int n) {
2684
2745
int [] dp = new int [n + 1 ];
0 commit comments