diff --git a/notes/Leetcode 题解.md b/notes/Leetcode 题解.md
index 9e49dff5..b29be266 100644
--- a/notes/Leetcode 题解.md
+++ b/notes/Leetcode 题解.md
@@ -1841,9 +1841,9 @@ private int rob(int[] nums, int first, int last) {
定义一个数组 dp 存储错误方式数量,dp[i] 表示前 i 个信和信封的错误方式数量。假设第 i 个信装到第 j 个信封里面,而第 j 个信装到第 k 个信封里面。根据 i 和 k 是否相等,有两种情况:
-① i==k,交换 i 和 k 的信后,它们的信和信封在正确的位置,但是其余 i-2 封信有 dp[i-2] 种错误装信的方式。由于 j 有 i-1 种取值,因此共有 (i-1)\*dp[i-2] 种错误装信方式。
+1. 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 种取值,因此共有 (n-1)\*dp[i-1] 种错误装信方式。
+2. i != k,交换 i 和 j 的信后,第 i 个信和信封在正确的位置,其余 i-1 封信有 dp[i-1] 种错误装信方式。由于 j 有 i-1 种取值,因此共有 (n-1)\*dp[i-1] 种错误装信方式。
综上所述,错误装信数量方式数量为:
@@ -1898,7 +1898,10 @@ len = 2 : [4, 5], [5, 6] => tails[1] = 5
len = 3 : [4, 5, 6] => tails[2] = 6
```
-对于一个元素 x,如果它大于 tails 数组所有的值,那么把它添加到 tails 后面;如果 tails[i-1] < x <= tails[i],那么更新 tails[i] = x 。
+对于一个元素 x,
+
+- 如果它大于 tails 数组所有的值,那么把它添加到 tails 后面,表示最长递增子序列长度加 1;
+- 如果 tails[i-1] < x <= tails[i],那么更新 tails[i] = x。
可以看出 tails 数组保持有序,因此在查找 Si 位于 tails 数组的位置时就可以使用二分查找。
@@ -1926,6 +1929,43 @@ private int binarySearch(int[] nums, int first, int last, int key) {
}
```
+**一组整数对能够构成的最长链**
+
+[Leetcode : 646. Maximum Length of Pair Chain (Medium)](https://leetcode.com/problems/maximum-length-of-pair-chain/description/)
+
+```html
+Input: [[1,2], [2,3], [3,4]]
+Output: 2
+Explanation: The longest chain is [1,2] -> [3,4]
+```
+
+对于 (a, b) 和 (c, d) ,如果 b < c,则它们可以构成一条链。
+
+```java
+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 = 0; i < n; i++) {
+ for(int j = 0; j < i; j++) {
+ if(pairs[i][0] > pairs[j][1]){
+ dp[i] = Math.max(dp[i], dp[j] + 1);
+ }
+ }
+ }
+
+ int ret = 0;
+ for(int num : dp) {
+ ret = Math.max(ret, num);
+ }
+ return ret;
+}
+```
+
**最长摆动子序列**
[Leetcode : 376. Wiggle Subsequence (Medium)](https://leetcode.com/problems/wiggle-subsequence/description/)
@@ -1966,9 +2006,8 @@ public int wiggleMaxLength(int[] nums) {
定义一个二维数组 dp 用来存储最长公共子序列的长度,其中 dp[i][j] 表示 S1 的前 i 个字符与 S2 的前 j 个字符最长公共子序列的长度。考虑 S1i 与 S2j 值是否相等,分为两种情况:
-① 当 S1i==S2j 时,那么就能在 S1 的前 i-1 个字符与 S2 的前 j-1 个字符最长公共子序列的基础上再加上 S1i 这个值,最长公共子序列长度加 1 ,即 dp[i][j] = dp[i-1][j-1] + 1。
-
-② 当 S1i != S2j 时,此时最长公共子序列为 S1 的前 i-1 个字符和 S2 的前 j 个字符最长公共子序列,与 S1 的前 i 个字符和 S2 的前 j-1 个字符最长公共子序列,它们的最大者,即 dp[i][j] = max{ dp[i-1][j], dp[i][j-1] }。
+1. 当 S1i==S2j 时,那么就能在 S1 的前 i-1 个字符与 S2 的前 j-1 个字符最长公共子序列的基础上再加上 S1i 这个值,最长公共子序列长度加 1 ,即 dp[i][j] = dp[i-1][j-1] + 1。
+2. 当 S1i != S2j 时,此时最长公共子序列为 S1 的前 i-1 个字符和 S2 的前 j 个字符最长公共子序列,与 S1 的前 i 个字符和 S2 的前 j-1 个字符最长公共子序列,它们的最大者,即 dp[i][j] = max{ dp[i-1][j], dp[i][j-1] }。
综上,最长公共子序列的状态转移方程为:
@@ -1978,9 +2017,9 @@ public int wiggleMaxLength(int[] nums) {
与最长递增子序列相比,最长公共子序列有以下不同点:
-① 针对的是两个序列,求它们的最长公共子序列。
-② 在最长递增子序列中,dp[i] 表示以 Si 为结尾的最长递增子序列长度,子序列必须包含 Si ;在最长公共子序列中,dp[i][j] 表示 S1 中前 i 个字符与 S2 中前 j 个字符的最长公共子序列长度,不一定包含 S1i 和 S2j 。
-③ 由于 2 ,在求最终解时,最长公共子序列中 dp[N][M] 就是最终解,而最长递增子序列中 dp[N] 不是最终解,因为以 SN 为结尾的最长递增子序列不一定是整个序列最长递增子序列,需要遍历一遍 dp 数组找到最大者。
+- 针对的是两个序列,求它们的最长公共子序列。
+- 在最长递增子序列中,dp[i] 表示以 Si 为结尾的最长递增子序列长度,子序列必须包含 Si ;在最长公共子序列中,dp[i][j] 表示 S1 中前 i 个字符与 S2 中前 j 个字符的最长公共子序列长度,不一定包含 S1i 和 S2j 。
+- 由于 2 ,在求最终解时,最长公共子序列中 dp[N][M] 就是最终解,而最长递增子序列中 dp[N] 不是最终解,因为以 SN 为结尾的最长递增子序列不一定是整个序列最长递增子序列,需要遍历一遍 dp 数组找到最大者。
```java
public int lengthOfLCS(int[] nums1, int[] nums2) {
@@ -2002,8 +2041,8 @@ public int lengthOfLCS(int[] nums1, int[] nums2) {
定义一个二维数组 dp 存储最大价值,其中 dp[i][j] 表示体积不超过 j 的情况下,前 i 件物品能达到的最大价值。设第 i 件物品体积为 w,价值为 v,根据第 i 件物品是否添加到背包中,可以分两种情况讨论:
-① 第 i 件物品没添加到背包,总体积不超过 j 的前 i 件物品的最大价值就是总体积不超过 j 的前 i-1 件物品的最大价值,dp[i][j] = dp[i-1][j]。
-② 第 i 件物品添加到背包中,dp[i][j] = dp[i-1][j-w] + v。
+1. 第 i 件物品没添加到背包,总体积不超过 j 的前 i 件物品的最大价值就是总体积不超过 j 的前 i-1 件物品的最大价值,dp[i][j] = dp[i-1][j]。
+2. 第 i 件物品添加到背包中,dp[i][j] = dp[i-1][j-w] + v。
第 i 件物品可添加也可以不添加,取决于哪种情况下最大价值更大。
@@ -2069,33 +2108,25 @@ Output: true
Explanation: The array can be partitioned as [1, 5, 5] and [11].
```
-可以看成一个背包大小为 sum/2 的 0-1 背包问题,但是也有不同的地方,这里没有价值属性,并且背包必须被填满。
-
-以下实现使用了空间优化。
+可以看成一个背包大小为 sum/2 的 0-1 背包问题。
```java
-public boolean canPartition(int[] nums) {
- int sum = 0;
- for (int num : nums) {
- sum += num;
- }
- if (sum % 2 != 0) {
- return false;
- }
- int W = sum / 2;
- boolean[] dp = new boolean[W + 1];
- for (int i = 0; i <= W; i++) {
- if (nums[0] == i) {
- dp[i] = true;
- }
- }
- for (int i = 1; i < nums.length; i++) {
- for (int j = W; j >= nums[i]; j--) {
- dp[j] = dp[j] || dp[j - nums[i]];
- }
- }
- return dp[W];
-}
+ public boolean canPartition(int[] nums) {
+ int sum = 0;
+ for (int num : nums) sum += num;
+ 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 >= 0; i--) { // 从后往前,先计算 dp[i] 再计算 dp[i-num]
+ if (num <= i) {
+ dp[i] = dp[i] || dp[i - num];
+ }
+ }
+ }
+ return dp[W];
+ }
```
**字符串按单词列表分割**
@@ -2108,15 +2139,20 @@ dict = ["leet", "code"].
Return true because "leetcode" can be segmented as "leet code".
```
+这是一个完全背包问题,和 0-1 背包不同的是,完全背包中物品可以使用多次。在这一题当中,词典中的单词可以被使用多次。
+
+0-1 背包和完全背包在实现上的不同之处是,0-1 背包对物品的迭代是在最外层,而完全背包对物品的迭代是最最里层。
+
```java
public boolean wordBreak(String s, List 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) {
- if (word.length() <= i && word.equals(s.substring(i - word.length(), i))) {
- dp[i] = dp[i] || dp[i - word.length()];
+ 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];
}
}
}
@@ -2142,7 +2178,9 @@ Explanation:
There are 5 ways to assign symbols to make the sum of nums be target 3.
```
-该问题可以转换为 subset sum 问题,从而使用 0-1 背包的方法来求解。可以将这组数看成两部分,P 和 N,其中 P 使用正号,N 使用负号,有以下推导:
+该问题可以转换为 Subset Sum 问题,从而使用 0-1 背包的方法来求解。
+
+可以将这组数看成两部分,P 和 N,其中 P 使用正号,N 使用负号,有以下推导:
```html
sum(P) - sum(N) = target
@@ -2155,26 +2193,34 @@ sum(P) + sum(N) + sum(P) - sum(N) = target + sum(P) + sum(N)
```java
public int findTargetSumWays(int[] nums, int S) {
int sum = 0;
- for (int num : nums) {
- sum += num;
- }
- if (sum < S || (sum + S) % 2 == 1) {
- return 0;
- }
- return subsetSum(nums, (sum + S) >>> 1);
-}
-
-private int subsetSum(int[] nums, int targetSum) {
- Arrays.sort(nums);
- int[] dp = new int[targetSum + 1];
+ for (int num : nums) sum += num;
+ 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 i = 0; i < nums.length; i++) {
- int num = nums[i];
- for (int j = targetSum; j >= num; j--) {
- dp[j] = dp[j] + dp[j - num];
+ for (int num : nums) {
+ for (int i = W; i >= 0; i--) {
+ if (num <= i) {
+ dp[i] = dp[i] + dp[i - num];
+ }
}
}
- return dp[targetSum];
+ return dp[W];
+}
+```
+
+DFS 解法:
+
+```java
+public int findTargetSumWays(int[] nums, int S) {
+ return findTargetSumWays(nums, 0, S);
+}
+
+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]);
}
```
@@ -2446,10 +2492,36 @@ public int minDistance(String word1, String word2) {
}
```
-**修改一个字符串称为另一个字符串** // TODO
+**修改一个字符串称为另一个字符串**
[Leetcode : 72. Edit Distance (Hard)](https://leetcode.com/problems/edit-distance/description/)
+```java
+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];
+}
+```
+
### 分割整数
**分割整数的最大乘积**
@@ -2552,6 +2624,20 @@ public int uniquePaths(int m, int n) {
}
```
+也可以直接用数学公式求解,这是一个组合问题。机器人总共移动的次数 S=m+n-2,向下移动的次数 D=m-1,那么问题可以看成从 S 从取出 D 个位置的组合数量,这个问题的解为 C(S, D)。
+
+```java
+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;
+}
+```
+
**矩阵的最小路径和**
[Leetcode : 64. Minimum Path Sum (Medium)](https://leetcode.com/problems/minimum-path-sum/description/)
@@ -2616,43 +2702,6 @@ public int maxProfit(int[] prices) {
}
```
-**一组整数对能够构成的最长链**
-
-[Leetcode : 646. Maximum Length of Pair Chain (Medium)](https://leetcode.com/problems/maximum-length-of-pair-chain/description/)
-
-```html
-Input: [[1,2], [2,3], [3,4]]
-Output: 2
-Explanation: The longest chain is [1,2] -> [3,4]
-```
-
-对于 (a, b) 和 (c, d) ,如果 b < c,则它们可以构成一条链。
-
-```java
-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 = 0; i < n; i++) {
- for(int j = 0; j < i; j++) {
- if(pairs[i][0] > pairs[j][1]){
- dp[i] = Math.max(dp[i], dp[j] + 1);
- }
- }
- }
-
- int ret = 0;
- for(int num : dp) {
- ret = Math.max(ret, num);
- }
- return ret;
-}
-```
-
**买入和售出股票最大的收益**
[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) {
[Leetcode : 650. 2 Keys Keyboard (Medium)](https://leetcode.com/problems/2-keys-keyboard/description/)
+题目描述:最开始只有一个字符 A,问需要多少次操作能够得到 n 个字符 A,每次操作可以复制当前所有的字符,或者粘贴。
+
+```
+Input: 3
+Output: 3
+Explanation:
+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'.
+```
+
```java
public int minSteps(int n) {
int[] dp = new int[n + 1];
diff --git a/notes/分布式问题分析.md b/notes/分布式问题分析.md
index d88966f3..f684bf70 100644
--- a/notes/分布式问题分析.md
+++ b/notes/分布式问题分析.md
@@ -127,15 +127,15 @@
+### 6. 源地址哈希法 (IP Hash)
-### 6.源地址哈希法(ip hash)
-源地址哈希通过对客户端IP哈希计算得到的一个数值,用该数值对服务器数量进行取模运算,取模结果便是目标服务器的序号。
-- 优点:保证同一IP的客户端都会被hash到同一台服务器上。
-- 缺点:不利于集群扩展,后台服务器数量变更都会影响hash结果。可以采用一致性Hash改进。
+源地址哈希通过对客户端 IP 哈希计算得到的一个数值,用该数值对服务器数量进行取模运算,取模结果便是目标服务器的序号。
+
+- 优点:保证同一 IP 的客户端都会被 hash 到同一台服务器上。
+- 缺点:不利于集群扩展,后台服务器数量变更都会影响 hash 结果。可以采用一致性 Hash 改进。
-
## 实现
### 1. HTTP 重定向
diff --git a/pics/2018040302.jpg b/pics/2018040302.jpg
index f16c4e43..27daefae 100644
Binary files a/pics/2018040302.jpg and b/pics/2018040302.jpg differ