Skip to content

Commit 7ca1fcc

Browse files
committed
auto commit
1 parent 3e35079 commit 7ca1fcc

File tree

3 files changed

+159
-98
lines changed

3 files changed

+159
-98
lines changed

notes/Leetcode 题解.md

Lines changed: 154 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1841,9 +1841,9 @@ private int rob(int[] nums, int first, int last) {
18411841

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

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] 种错误装信方式。
18451845

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] 种错误装信方式。
18471847

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

@@ -1898,7 +1898,10 @@ len = 2 : [4, 5], [5, 6] => tails[1] = 5
18981898
len = 3 : [4, 5, 6] => tails[2] = 6
18991899
```
19001900

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。
19021905

19031906
可以看出 tails 数组保持有序,因此在查找 S<sub>i</sub> 位于 tails 数组的位置时就可以使用二分查找。
19041907

@@ -1926,6 +1929,43 @@ private int binarySearch(int[] nums, int first, int last, int key) {
19261929
}
19271930
```
19281931

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+
19291969
**最长摆动子序列**
19301970

19311971
[Leetcode : 376. Wiggle Subsequence (Medium)](https://leetcode.com/problems/wiggle-subsequence/description/)
@@ -1966,9 +2006,8 @@ public int wiggleMaxLength(int[] nums) {
19662006

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

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] }。
19722011

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

@@ -1978,9 +2017,9 @@ public int wiggleMaxLength(int[] nums) {
19782017

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

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 数组找到最大者。
19842023

19852024
```java
19862025
public int lengthOfLCS(int[] nums1, int[] nums2) {
@@ -2002,8 +2041,8 @@ public int lengthOfLCS(int[] nums1, int[] nums2) {
20022041

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

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。
20072046

20082047
第 i 件物品可添加也可以不添加,取决于哪种情况下最大价值更大。
20092048

@@ -2069,33 +2108,25 @@ Output: true
20692108
Explanation: The array can be partitioned as [1, 5, 5] and [11].
20702109
```
20712110

2072-
可以看成一个背包大小为 sum/2 的 0-1 背包问题,但是也有不同的地方,这里没有价值属性,并且背包必须被填满。
2073-
2074-
以下实现使用了空间优化。
2111+
可以看成一个背包大小为 sum/2 的 0-1 背包问题。
20752112

20762113
```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+
}
20992130
```
21002131

21012132
**字符串按单词列表分割**
@@ -2108,15 +2139,20 @@ dict = ["leet", "code"].
21082139
Return true because "leetcode" can be segmented as "leet code".
21092140
```
21102141

2142+
这是一个完全背包问题,和 0-1 背包不同的是,完全背包中物品可以使用多次。在这一题当中,词典中的单词可以被使用多次。
2143+
2144+
0-1 背包和完全背包在实现上的不同之处是,0-1 背包对物品的迭代是在最外层,而完全背包对物品的迭代是最最里层。
2145+
21112146
```java
21122147
public boolean wordBreak(String s, List<String> wordDict) {
21132148
int n = s.length();
21142149
boolean[] dp = new boolean[n + 1];
21152150
dp[0] = true;
21162151
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];
21202156
}
21212157
}
21222158
}
@@ -2142,7 +2178,9 @@ Explanation:
21422178
There are 5 ways to assign symbols to make the sum of nums be target 3.
21432179
```
21442180

2145-
该问题可以转换为 subset sum 问题,从而使用 0-1 背包的方法来求解。可以将这组数看成两部分,P 和 N,其中 P 使用正号,N 使用负号,有以下推导:
2181+
该问题可以转换为 Subset Sum 问题,从而使用 0-1 背包的方法来求解。
2182+
2183+
可以将这组数看成两部分,P 和 N,其中 P 使用正号,N 使用负号,有以下推导:
21462184

21472185
```html
21482186
sum(P) - sum(N) = target
@@ -2155,26 +2193,34 @@ sum(P) + sum(N) + sum(P) - sum(N) = target + sum(P) + sum(N)
21552193
```java
21562194
public int findTargetSumWays(int[] nums, int S) {
21572195
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;
21582201
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+
}
21632207
}
2164-
return subsetSum(nums, (sum + S) >>> 1);
2208+
return dp[W];
21652209
}
2210+
```
21662211

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;
21762222
}
2177-
return dp[targetSum];
2223+
return findTargetSumWays(nums, start + 1, S + nums[start]) + findTargetSumWays(nums, start + 1, S - nums[start]);
21782224
}
21792225
```
21802226

@@ -2446,10 +2492,36 @@ public int minDistance(String word1, String word2) {
24462492
}
24472493
```
24482494

2449-
**修改一个字符串称为另一个字符串** // TODO
2495+
**修改一个字符串称为另一个字符串**
24502496

24512497
[Leetcode : 72. Edit Distance (Hard)](https://leetcode.com/problems/edit-distance/description/)
24522498

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+
24532525
### 分割整数
24542526

24552527
**分割整数的最大乘积**
@@ -2552,6 +2624,20 @@ public int uniquePaths(int m, int n) {
25522624
}
25532625
```
25542626

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+
25552641
**矩阵的最小路径和**
25562642

25572643
[Leetcode : 64. Minimum Path Sum (Medium)](https://leetcode.com/problems/minimum-path-sum/description/)
@@ -2616,43 +2702,6 @@ public int maxProfit(int[] prices) {
26162702
}
26172703
```
26182704

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-
26562705
**买入和售出股票最大的收益**
26572706

26582707
[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) {
26792728

26802729
[Leetcode : 650. 2 Keys Keyboard (Medium)](https://leetcode.com/problems/2-keys-keyboard/description/)
26812730

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+
26822743
```java
26832744
public int minSteps(int n) {
26842745
int[] dp = new int[n + 1];

notes/分布式问题分析.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -127,14 +127,14 @@
127127

128128
<div align="center"> <img src="../pics//0ee0f61b-c782-441e-bf34-665650198ae0.jpg"/> </div><br>
129129

130+
### 6. 源地址哈希法 (IP Hash)
130131

131-
### 6.源地址哈希法(ip hash)
132-
源地址哈希通过对客户端IP哈希计算得到的一个数值,用该数值对服务器数量进行取模运算,取模结果便是目标服务器的序号。
133-
- 优点:保证同一IP的客户端都会被hash到同一台服务器上。
134-
- 缺点:不利于集群扩展,后台服务器数量变更都会影响hash结果。可以采用一致性Hash改进。
132+
源地址哈希通过对客户端 IP 哈希计算得到的一个数值,用该数值对服务器数量进行取模运算,取模结果便是目标服务器的序号。
135133

136-
<div align="center"> <img src="../pics//2018040302.jpg"/> </div><br>
134+
- 优点:保证同一 IP 的客户端都会被 hash 到同一台服务器上。
135+
- 缺点:不利于集群扩展,后台服务器数量变更都会影响 hash 结果。可以采用一致性 Hash 改进。
137136

137+
<div align="center"> <img src="../pics//2018040302.jpg"/> </div><br>
138138

139139
## 实现
140140

pics/2018040302.jpg

747 Bytes
Loading

0 commit comments

Comments
 (0)