diff --git a/notes/Leetcode 题解.md b/notes/Leetcode 题解.md
index 102fe9b6..44f3d9dc 100644
--- a/notes/Leetcode 题解.md
+++ b/notes/Leetcode 题解.md
@@ -1,18 +1,18 @@
* [算法思想](#算法思想)
- * [贪心思想](#贪心思想)
* [双指针](#双指针)
* [排序](#排序)
* [快速选择](#快速选择)
* [堆排序](#堆排序)
* [桶排序](#桶排序)
* [荷兰国旗问题](#荷兰国旗问题)
+ * [贪心思想](#贪心思想)
* [二分查找](#二分查找)
+ * [分治](#分治)
* [搜索](#搜索)
* [BFS](#bfs)
* [DFS](#dfs)
* [Backtracking](#backtracking)
- * [分治](#分治)
* [动态规划](#动态规划)
* [斐波那契数列](#斐波那契数列)
* [矩阵路径](#矩阵路径)
@@ -55,9 +55,449 @@
# 算法思想
+## 双指针
+
+双指针主要用于遍历数组,两个指针指向不同的元素,从而协同完成任务。
+
+**有序数组的 Two Sum**
+
+[Leetcode :167. Two Sum II - Input array is sorted (Easy)](https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/description/)
+
+```html
+Input: numbers={2, 7, 11, 15}, target=9
+Output: index1=1, index2=2
+```
+
+题目描述:在有序数组中找出两个数,使它们的和为 target。
+
+使用双指针,一个指针指向值较小的元素,一个指针指向值较大的元素。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。
+
+- 如果两个指针指向元素的和 sum == target,那么得到要求的结果;
+- 如果 sum > target,移动较大的元素,使 sum 变小一些;
+- 如果 sum < target,移动较小的元素,使 sum 变大一些。
+
+```java
+public int[] twoSum(int[] numbers, int target) {
+ int i = 0, j = numbers.length - 1;
+ while (i < j) {
+ int sum = numbers[i] + numbers[j];
+ if (sum == target) {
+ return new int[]{i + 1, j + 1};
+ } else if (sum < target) {
+ i++;
+ } else {
+ j--;
+ }
+ }
+ return null;
+}
+```
+
+**两数平方和**
+
+[633. Sum of Square Numbers (Easy)](https://leetcode.com/problems/sum-of-square-numbers/description/)
+
+```html
+Input: 5
+Output: True
+Explanation: 1 * 1 + 2 * 2 = 5
+```
+
+题目描述:判断一个数是否为两个数的平方和,例如 5 = 12 + 22。
+
+```java
+public boolean judgeSquareSum(int c) {
+ int i = 0, j = (int) Math.sqrt(c);
+ while (i <= j) {
+ int powSum = i * i + j * j;
+ if (powSum == c) {
+ return true;
+ } else if (powSum > c) {
+ j--;
+ } else {
+ i++;
+ }
+ }
+ return false;
+}
+```
+
+**反转字符串中的元音字符**
+
+[345. Reverse Vowels of a String (Easy)](https://leetcode.com/problems/reverse-vowels-of-a-string/description/)
+
+```html
+Given s = "leetcode", return "leotcede".
+```
+
+使用双指针,指向待反转的两个元音字符,一个指针从头向尾遍历,一个指针从尾到头遍历。
+
+```java
+private final static HashSet vowels = new HashSet<>(Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'));
+
+public String reverseVowels(String s) {
+ int i = 0, j = s.length() - 1;
+ char[] result = new char[s.length()];
+ while (i <= j) {
+ char ci = s.charAt(i);
+ char cj = s.charAt(j);
+ if (!vowels.contains(ci)) {
+ result[i++] = ci;
+ } else if (!vowels.contains(cj)) {
+ result[j--] = cj;
+ } else {
+ result[i++] = cj;
+ result[j--] = ci;
+ }
+ }
+ return new String(result);
+}
+```
+
+**回文字符串**
+
+[680. Valid Palindrome II (Easy)](https://leetcode.com/problems/valid-palindrome-ii/description/)
+
+```html
+Input: "abca"
+Output: True
+Explanation: You could delete the character 'c'.
+```
+
+题目描述:可以删除一个字符,判断是否能构成回文字符串。
+
+```java
+public boolean validPalindrome(String s) {
+ int i = -1, j = s.length();
+ while (++i < --j) {
+ if (s.charAt(i) != s.charAt(j)) {
+ return isPalindrome(s, i, j - 1) || isPalindrome(s, i + 1, j);
+ }
+ }
+ return true;
+}
+
+private boolean isPalindrome(String s, int i, int j) {
+ while (i < j) {
+ if (s.charAt(i++) != s.charAt(j--)) {
+ return false;
+ }
+ }
+ return true;
+}
+```
+
+**归并两个有序数组**
+
+[88. Merge Sorted Array (Easy)](https://leetcode.com/problems/merge-sorted-array/description/)
+
+```html
+Input:
+nums1 = [1,2,3,0,0,0], m = 3
+nums2 = [2,5,6], n = 3
+
+Output: [1,2,2,3,5,6]
+```
+
+题目描述:把归并结果存到第一个数组上。
+
+需要从尾开始遍历,否则在 nums1 上归并得到的值会覆盖还未进行归并比较的值。
+
+```java
+public void merge(int[] nums1, int m, int[] nums2, int n) {
+ int index1 = m - 1, index2 = n - 1;
+ int indexMerge = m + n - 1;
+ while (index1 >= 0 || index2 >= 0) {
+ if (index1 < 0) {
+ nums1[indexMerge--] = nums2[index2--];
+ } else if (index2 < 0) {
+ nums1[indexMerge--] = nums1[index1--];
+ } else if (nums1[index1] > nums2[index2]) {
+ nums1[indexMerge--] = nums1[index1--];
+ } else {
+ nums1[indexMerge--] = nums2[index2--];
+ }
+ }
+}
+```
+
+**判断链表是否存在环**
+
+[141. Linked List Cycle (Easy)](https://leetcode.com/problems/linked-list-cycle/description/)
+
+使用双指针,一个指针每次移动一个节点,一个指针每次移动两个节点,如果存在环,那么这两个指针一定会相遇。
+
+```java
+public boolean hasCycle(ListNode head) {
+ if (head == null) {
+ return false;
+ }
+ ListNode l1 = head, l2 = head.next;
+ while (l1 != null && l2 != null && l2.next != null) {
+ if (l1 == l2) {
+ return true;
+ }
+ l1 = l1.next;
+ l2 = l2.next.next;
+ }
+ return false;
+}
+```
+
+**最长子序列**
+
+[524. Longest Word in Dictionary through Deleting (Medium)](https://leetcode.com/problems/longest-word-in-dictionary-through-deleting/description/)
+
+```
+Input:
+s = "abpcplea", d = ["ale","apple","monkey","plea"]
+
+Output:
+"apple"
+```
+
+题目描述:删除 s 中的一些字符,使得它构成字符串列表 d 中的一个字符串,找出能构成的最长字符串。如果有多个相同长度的结果,返回字典序的最大字符串。
+
+```java
+public String findLongestWord(String s, List d) {
+ String longestWord = "";
+ for (String target : d) {
+ int l1 = longestWord.length(), l2 = target.length();
+ if (l1 > l2 || (l1 == l2 && longestWord.compareTo(target) < 0)) {
+ continue;
+ }
+ if (isValid(s, target)) {
+ longestWord = target;
+ }
+ }
+ return longestWord;
+}
+
+private boolean isValid(String s, String target) {
+ int i = 0, j = 0;
+ while (i < s.length() && j < target.length()) {
+ if (s.charAt(i) == target.charAt(j)) {
+ j++;
+ }
+ i++;
+ }
+ return j == target.length();
+}
+```
+
+## 排序
+
+### 快速选择
+
+用于求解 **Kth Element** 问题,使用快速排序的 partition() 进行实现。
+
+需要先打乱数组,否则最坏情况下时间复杂度为 O(N2)。
+
+### 堆排序
+
+用于求解 **TopK Elements** 问题,通过维护一个大小为 K 的堆,堆中的元素就是 TopK Elements。
+
+堆排序也可以用于求解 Kth Element 问题,堆顶元素就是 Kth Element。
+
+快速选择也可以求解 TopK Elements 问题,因为找到 Kth Element 之后,再遍历一次数组,所有小于等于 Kth Element 的元素都是 TopK Elements。
+
+可以看到,快速选择和堆排序都可以求解 Kth Element 和 TopK Elements 问题。
+
+**Kth Element**
+
+[215. Kth Largest Element in an Array (Medium)](https://leetcode.com/problems/kth-largest-element-in-an-array/description/)
+
+**排序** :时间复杂度 O(NlogN),空间复杂度 O(1)
+
+```java
+public int findKthLargest(int[] nums, int k) {
+ Arrays.sort(nums);
+ return nums[nums.length - k];
+}
+```
+
+**堆排序** :时间复杂度 O(NlogK),空间复杂度 O(K)。
+
+```java
+public int findKthLargest(int[] nums, int k) {
+ PriorityQueue pq = new PriorityQueue<>(); // 小顶堆
+ for (int val : nums) {
+ pq.add(val);
+ if (pq.size() > k) // 维护堆的大小为 K
+ pq.poll();
+ }
+ return pq.peek();
+}
+```
+
+**快速选择** :时间复杂度 O(N),空间复杂度 O(1)
+
+```java
+public int findKthLargest(int[] nums, int k) {
+ k = nums.length - k;
+ int l = 0, h = nums.length - 1;
+ while (l < h) {
+ int j = partition(nums, l, h);
+ if (j == k) {
+ break;
+ } else if (j < k) {
+ l = j + 1;
+ } else {
+ h = j - 1;
+ }
+ }
+ return nums[k];
+}
+
+private int partition(int[] a, int l, int h) {
+ int i = l, j = h + 1;
+ while (true) {
+ while (a[++i] < a[l] && i < h) ;
+ while (a[--j] > a[l] && j > l) ;
+ if (i >= j) {
+ break;
+ }
+ swap(a, i, j);
+ }
+ swap(a, l, j);
+ return j;
+}
+
+private void swap(int[] a, int i, int j) {
+ int t = a[i];
+ a[i] = a[j];
+ a[j] = t;
+}
+```
+
+### 桶排序
+
+**出现频率最多的 k 个数**
+
+[347. Top K Frequent Elements (Medium)](https://leetcode.com/problems/top-k-frequent-elements/description/)
+
+```html
+Given [1,1,1,2,2,3] and k = 2, return [1,2].
+```
+
+设置若干个桶,每个桶存储出现频率相同的数,并且桶的下标代表桶中数出现的频率,即第 i 个桶中存储的数出现的频率为 i。
+
+把数都放到桶之后,从后向前遍历桶,最先得到的 k 个数就是出现频率最多的的 k 个数。
+
+```java
+public List topKFrequent(int[] nums, int k) {
+ Map frequencyForNum = new HashMap<>();
+ for (int num : nums) {
+ frequencyForNum.put(num, frequencyForNum.getOrDefault(num, 0) + 1);
+ }
+ List[] buckets = new ArrayList[nums.length + 1];
+ for (int key : frequencyForNum.keySet()) {
+ int frequency = frequencyForNum.get(key);
+ if (buckets[frequency] == null) {
+ buckets[frequency] = new ArrayList<>();
+ }
+ buckets[frequency].add(key);
+ }
+ List topK = new ArrayList<>();
+ for (int i = buckets.length - 1; i >= 0 && topK.size() < k; i--) {
+ if (buckets[i] != null) {
+ topK.addAll(buckets[i]);
+ }
+ }
+ return topK;
+}
+```
+
+**按照字符出现次数对字符串排序**
+
+[451. Sort Characters By Frequency (Medium)](https://leetcode.com/problems/sort-characters-by-frequency/description/)
+
+```html
+Input:
+"tree"
+
+Output:
+"eert"
+
+Explanation:
+'e' appears twice while 'r' and 't' both appear once.
+So 'e' must appear before both 'r' and 't'. Therefore "eetr" is also a valid answer.
+```
+
+```java
+public String frequencySort(String s) {
+ Map frequencyForNum = new HashMap<>();
+ for (char c : s.toCharArray())
+ frequencyForNum.put(c, frequencyForNum.getOrDefault(c, 0) + 1);
+
+ List[] frequencyBucket = new ArrayList[s.length() + 1];
+ for (char c : frequencyForNum.keySet()) {
+ int f = frequencyForNum.get(c);
+ if (frequencyBucket[f] == null) {
+ frequencyBucket[f] = new ArrayList<>();
+ }
+ frequencyBucket[f].add(c);
+ }
+ StringBuilder str = new StringBuilder();
+ for (int i = frequencyBucket.length - 1; i >= 0; i--) {
+ if (frequencyBucket[i] == null) {
+ continue;
+ }
+ for (char c : frequencyBucket[i]) {
+ for (int j = 0; j < i; j++) {
+ str.append(c);
+ }
+ }
+ }
+ return str.toString();
+}
+```
+
+### 荷兰国旗问题
+
+荷兰国旗包含三种颜色:红、白、蓝。
+
+有三种颜色的球,算法的目标是将这三种球按颜色顺序正确地排列。
+
+它其实是三向切分快速排序的一种变种,在三向切分快速排序中,每次切分都将数组分成三个区间:小于切分元素、等于切分元素、大于切分元素,而该算法是将数组分成三个区间:等于红色、等于白色、等于蓝色。
+
+
+
+**按颜色进行排序**
+
+[75. Sort Colors (Medium)](https://leetcode.com/problems/sort-colors/description/)
+
+```html
+Input: [2,0,2,1,1,0]
+Output: [0,0,1,1,2,2]
+```
+
+题目描述:只有 0/1/2 三种颜色。
+
+```java
+public void sortColors(int[] nums) {
+ int zero = -1, one = 0, two = nums.length;
+ while (one < two) {
+ if (nums[one] == 0) {
+ swap(nums, ++zero, one++);
+ } else if (nums[one] == 2) {
+ swap(nums, --two, one);
+ } else {
+ ++one;
+ }
+ }
+}
+
+private void swap(int[] nums, int i, int j) {
+ int t = nums[i];
+ nums[i] = nums[j];
+ nums[j] = t;
+}
+```
+
## 贪心思想
-贪心思想保证每次操作都是局部最优的,并且最后得到的结果是全局最优的。
+保证每次操作都是局部最优的,并且最后得到的结果是全局最优的。
**分配饼干**
@@ -74,7 +514,7 @@ You need to output 2.
题目描述:每个孩子都有一个满足度,每个饼干都有一个大小,只有饼干的大小大于等于一个孩子的满足度,该孩子才会获得满足。求解最多可以获得满足的孩子数量。
-因为最小的孩子最容易得到满足,因此先满足最小孩子。给一个孩子的饼干应当尽量小又能满足该孩子,这样大饼干就能拿来给满足度比较大的孩子。因此贪心策略
+给一个孩子的饼干应当尽量小又能满足该孩子,这样大饼干就能拿来给满足度比较大的孩子。因为最小的孩子最容易得到满足,所以先满足最小的孩子。
证明:假设在某次选择中,贪心策略选择给当前满足度最小的孩子分配第 m 个饼干,第 m 个饼干为可以满足该孩子的最小饼干。假设存在一种最优策略,给该孩子分配第 n 个饼干,并且 m < n。我们可以发现,经过这一轮分配,贪心策略分配后剩下的饼干一定有一个比最优策略来得大。因此在后续的分配中,贪心策略一定能满足更多的孩子。也就是说不存在比贪心策略更优的策略,即贪心策略就是最优策略。
@@ -362,434 +802,6 @@ public int maxProfit(int[] prices) {
}
```
-## 双指针
-
-双指针主要用于遍历数组,两个指针指向不同的元素,从而协同完成任务。
-
-**有序数组的 Two Sum**
-
-[Leetcode :167. Two Sum II - Input array is sorted (Easy)](https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/description/)
-
-```html
-Input: numbers={2, 7, 11, 15}, target=9
-Output: index1=1, index2=2
-```
-
-题目描述:在有序数组中找出两个数,使它们的和为 target。
-
-使用双指针,一个指针指向值较小的元素,一个指针指向值较大的元素。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。
-
-如果两个指针指向元素的和 sum == target,那么得到要求的结果;如果 sum > target,移动较大的元素,使 sum 变小一些;如果 sum < target,移动较小的元素,使 sum 变大一些。
-
-```java
-public int[] twoSum(int[] numbers, int target) {
- int i = 0, j = numbers.length - 1;
- while (i < j) {
- int sum = numbers[i] + numbers[j];
- if (sum == target) {
- return new int[]{i + 1, j + 1};
- } else if (sum < target) {
- i++;
- } else {
- j--;
- }
- }
- return null;
-}
-```
-
-**两数平方和**
-
-[633. Sum of Square Numbers (Easy)](https://leetcode.com/problems/sum-of-square-numbers/description/)
-
-```html
-Input: 5
-Output: True
-Explanation: 1 * 1 + 2 * 2 = 5
-```
-
-题目描述:判断一个数是否为两个数的平方和,例如 5 = 12 + 22。
-
-```java
-public boolean judgeSquareSum(int c) {
- int i = 0, j = (int) Math.sqrt(c);
- while (i <= j) {
- int powSum = i * i + j * j;
- if (powSum == c) {
- return true;
- } else if (powSum > c) {
- j--;
- } else {
- i++;
- }
- }
- return false;
-}
-```
-
-**反转字符串中的元音字符**
-
-[345. Reverse Vowels of a String (Easy)](https://leetcode.com/problems/reverse-vowels-of-a-string/description/)
-
-```html
-Given s = "leetcode", return "leotcede".
-```
-
-使用双指针,指向待反转的两个元音字符,一个指针从头向尾遍历,一个指针从尾到头遍历。
-
-```java
-private final static HashSet vowels = new HashSet<>(Arrays.asList('a', 'e', 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'));
-
-public String reverseVowels(String s) {
- int i = 0, j = s.length() - 1;
- char[] result = new char[s.length()];
- while (i <= j) {
- char ci = s.charAt(i);
- char cj = s.charAt(j);
- if (!vowels.contains(ci)) {
- result[i++] = ci;
- } else if (!vowels.contains(cj)) {
- result[j--] = cj;
- } else {
- result[i++] = cj;
- result[j--] = ci;
- }
- }
- return new String(result);
-}
-```
-
-**回文字符串**
-
-[680. Valid Palindrome II (Easy)](https://leetcode.com/problems/valid-palindrome-ii/description/)
-
-```html
-Input: "abca"
-Output: True
-Explanation: You could delete the character 'c'.
-```
-
-题目描述:可以删除一个字符,判断是否能构成回文字符串。
-
-```java
-public boolean validPalindrome(String s) {
- int i = -1, j = s.length();
- while (++i < --j) {
- if (s.charAt(i) != s.charAt(j)) {
- return isPalindrome(s, i, j - 1) || isPalindrome(s, i + 1, j);
- }
- }
- return true;
-}
-
-private boolean isPalindrome(String s, int i, int j) {
- while (i < j) {
- if (s.charAt(i++) != s.charAt(j--)) {
- return false;
- }
- }
- return true;
-}
-```
-
-**归并两个有序数组**
-
-[88. Merge Sorted Array (Easy)](https://leetcode.com/problems/merge-sorted-array/description/)
-
-```html
-Input:
-nums1 = [1,2,3,0,0,0], m = 3
-nums2 = [2,5,6], n = 3
-
-Output: [1,2,2,3,5,6]
-```
-
-题目描述:把归并结果存到第一个数组上。
-
-需要从尾开始遍历,否则在 nums1 上归并得到的值会覆盖还未进行归并比较的值
-
-```java
-public void merge(int[] nums1, int m, int[] nums2, int n) {
- int index1 = m - 1, index2 = n - 1;
- int indexMerge = m + n - 1;
- while (index1 >= 0 || index2 >= 0) {
- if (index1 < 0) {
- nums1[indexMerge--] = nums2[index2--];
- } else if (index2 < 0) {
- nums1[indexMerge--] = nums1[index1--];
- } else if (nums1[index1] > nums2[index2]) {
- nums1[indexMerge--] = nums1[index1--];
- } else {
- nums1[indexMerge--] = nums2[index2--];
- }
- }
-}
-```
-
-**判断链表是否存在环**
-
-[141. Linked List Cycle (Easy)](https://leetcode.com/problems/linked-list-cycle/description/)
-
-使用双指针,一个指针每次移动一个节点,一个指针每次移动两个节点,如果存在环,那么这两个指针一定会相遇。
-
-```java
-public boolean hasCycle(ListNode head) {
- if (head == null) {
- return false;
- }
- ListNode l1 = head, l2 = head.next;
- while (l1 != null && l2 != null && l2.next != null) {
- if (l1 == l2) {
- return true;
- }
- l1 = l1.next;
- l2 = l2.next.next;
- }
- return false;
-}
-```
-
-**最长子序列**
-
-[524. Longest Word in Dictionary through Deleting (Medium)](https://leetcode.com/problems/longest-word-in-dictionary-through-deleting/description/)
-
-```
-Input:
-s = "abpcplea", d = ["ale","apple","monkey","plea"]
-
-Output:
-"apple"
-```
-
-题目描述:删除 s 中的一些字符,使得它构成字符串列表 d 中的一个字符串,找出能构成的最长字符串。如果有多个相同长度的结果,返回按字典序排序的最大字符串。
-
-```java
-public String findLongestWord(String s, List d) {
- String longestWord = "";
- for (String target : d) {
- int l1 = longestWord.length(), l2 = target.length();
- if (l1 > l2 || (l1 == l2 && longestWord.compareTo(target) < 0)) {
- continue;
- }
- if (isValid(s, target)) {
- longestWord = target;
- }
- }
- return longestWord;
-}
-
-private boolean isValid(String s, String target) {
- int i = 0, j = 0;
- while (i < s.length() && j < target.length()) {
- if (s.charAt(i) == target.charAt(j)) {
- j++;
- }
- i++;
- }
- return j == target.length();
-}
-```
-
-## 排序
-
-### 快速选择
-
-一般用于求解 **Kth Element** 问题,可以在 O(N) 时间复杂度,O(1) 空间复杂度完成求解工作。
-
-与快速排序一样,快速选择一般需要先打乱数组,否则最坏情况下时间复杂度为 O(N2)。
-
-### 堆排序
-
-堆排序用于求解 **TopK Elements** 问题,通过维护一个大小为 K 的堆,堆中的元素就是 TopK Elements。当然它也可以用于求解 Kth Element 问题,堆顶元素就是 Kth Element。快速选择也可以求解 TopK Elements 问题,因为找到 Kth Element 之后,再遍历一次数组,所有小于等于 Kth Element 的元素都是 TopK Elements。可以看到,快速选择和堆排序都可以求解 Kth Element 和 TopK Elements 问题。
-
-**Kth Element**
-
-[215. Kth Largest Element in an Array (Medium)](https://leetcode.com/problems/kth-largest-element-in-an-array/description/)
-
-**排序** :时间复杂度 O(NlogN),空间复杂度 O(1)
-
-```java
-public int findKthLargest(int[] nums, int k) {
- Arrays.sort(nums);
- return nums[nums.length - k];
-}
-```
-
-**堆排序** :时间复杂度 O(NlogK),空间复杂度 O(K)。
-
-```java
-public int findKthLargest(int[] nums, int k) {
- PriorityQueue pq = new PriorityQueue<>(); // 小顶堆
- for (int val : nums) {
- pq.add(val);
- if (pq.size() > k) // 维护堆的大小为 K
- pq.poll();
- }
- return pq.peek();
-}
-```
-
-**快速选择** :时间复杂度 O(N),空间复杂度 O(1)
-
-```java
-public int findKthLargest(int[] nums, int k) {
- k = nums.length - k;
- int l = 0, h = nums.length - 1;
- while (l < h) {
- int j = partition(nums, l, h);
- if (j == k) {
- break;
- } else if (j < k) {
- l = j + 1;
- } else {
- h = j - 1;
- }
- }
- return nums[k];
-}
-
-private int partition(int[] a, int l, int h) {
- int i = l, j = h + 1;
- while (true) {
- while (a[++i] < a[l] && i < h) ;
- while (a[--j] > a[l] && j > l) ;
- if (i >= j) {
- break;
- }
- swap(a, i, j);
- }
- swap(a, l, j);
- return j;
-}
-
-private void swap(int[] a, int i, int j) {
- int t = a[i];
- a[i] = a[j];
- a[j] = t;
-}
-```
-
-### 桶排序
-
-**出现频率最多的 k 个数**
-
-[347. Top K Frequent Elements (Medium)](https://leetcode.com/problems/top-k-frequent-elements/description/)
-
-```html
-Given [1,1,1,2,2,3] and k = 2, return [1,2].
-```
-
-设置若干个桶,每个桶存储出现频率相同的数,并且桶的下标代表桶中数出现的频率,即第 i 个桶中存储的数出现的频率为 i。把数都放到桶之后,从后向前遍历桶,最先得到的 k 个数就是出现频率最多的的 k 个数。
-
-```java
-public List topKFrequent(int[] nums, int k) {
- Map frequencyForNum = new HashMap<>();
- for (int num : nums) {
- frequencyForNum.put(num, frequencyForNum.getOrDefault(num, 0) + 1);
- }
- List[] buckets = new ArrayList[nums.length + 1];
- for (int key : frequencyForNum.keySet()) {
- int frequency = frequencyForNum.get(key);
- if (buckets[frequency] == null) {
- buckets[frequency] = new ArrayList<>();
- }
- buckets[frequency].add(key);
- }
- List topK = new ArrayList<>();
- for (int i = buckets.length - 1; i >= 0 && topK.size() < k; i--) {
- if (buckets[i] != null) {
- topK.addAll(buckets[i]);
- }
- }
- return topK;
-}
-```
-
-**按照字符出现次数对字符串排序**
-
-[451. Sort Characters By Frequency (Medium)](https://leetcode.com/problems/sort-characters-by-frequency/description/)
-
-```html
-Input:
-"tree"
-
-Output:
-"eert"
-
-Explanation:
-'e' appears twice while 'r' and 't' both appear once.
-So 'e' must appear before both 'r' and 't'. Therefore "eetr" is also a valid answer.
-```
-
-```java
-public String frequencySort(String s) {
- Map frequencyForNum = new HashMap<>();
- for (char c : s.toCharArray())
- frequencyForNum.put(c, frequencyForNum.getOrDefault(c, 0) + 1);
-
- List[] frequencyBucket = new ArrayList[s.length() + 1];
- for (char c : frequencyForNum.keySet()) {
- int f = frequencyForNum.get(c);
- if (frequencyBucket[f] == null) {
- frequencyBucket[f] = new ArrayList<>();
- }
- frequencyBucket[f].add(c);
- }
- StringBuilder str = new StringBuilder();
- for (int i = frequencyBucket.length - 1; i >= 0; i--) {
- if (frequencyBucket[i] == null) {
- continue;
- }
- for (char c : frequencyBucket[i]) {
- for (int j = 0; j < i; j++) {
- str.append(c);
- }
- }
- }
- return str.toString();
-}
-```
-
-### 荷兰国旗问题
-
-荷兰国旗包含三种颜色:红、白、蓝。有这三种颜色的球,算法的目标是将这三种球按颜色顺序正确地排列。
-
-它其实是三向切分快速排序的一种变种,在三向切分快速排序中,每次切分都将数组分成三个区间:小于切分元素、等于切分元素、大于切分元素,而该算法是将数组分成三个区间:等于红色、等于白色、等于蓝色。
-
-
-
-**按颜色进行排序**
-
-[75. Sort Colors (Medium)](https://leetcode.com/problems/sort-colors/description/)
-
-```html
-Input: [2,0,2,1,1,0]
-Output: [0,0,1,1,2,2]
-```
-
-题目描述:只有 0/1/2 三种颜色。
-
-```java
-public void sortColors(int[] nums) {
- int zero = -1, one = 0, two = nums.length;
- while (one < two) {
- if (nums[one] == 0) {
- swap(nums, ++zero, one++);
- } else if (nums[one] == 2) {
- swap(nums, --two, one);
- } else {
- ++one;
- }
- }
-}
-
-private void swap(int[] nums, int i, int j) {
- int t = nums[i];
- nums[i] = nums[j];
- nums[j] = t;
-}
-```
-
## 二分查找
**正常实现**
@@ -1065,6 +1077,53 @@ private int binarySearch(int[] nums, int target) {
}
```
+## 分治
+
+**给表达式加括号**
+
+[241. Different Ways to Add Parentheses (Medium)](https://leetcode.com/problems/different-ways-to-add-parentheses/description/)
+
+```html
+Input: "2-1-1".
+
+((2-1)-1) = 0
+(2-(1-1)) = 2
+
+Output : [0, 2]
+```
+
+```java
+public List diffWaysToCompute(String input) {
+ List ways = new ArrayList<>();
+ for (int i = 0; i < input.length(); i++) {
+ char c = input.charAt(i);
+ if (c == '+' || c == '-' || c == '*') {
+ List left = diffWaysToCompute(input.substring(0, i));
+ List right = diffWaysToCompute(input.substring(i + 1));
+ for (int l : left) {
+ for (int r : right) {
+ switch (c) {
+ case '+':
+ ways.add(l + r);
+ break;
+ case '-':
+ ways.add(l - r);
+ break;
+ case '*':
+ ways.add(l * r);
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (ways.size() == 0) {
+ ways.add(Integer.valueOf(input));
+ }
+ return ways;
+}
+```
+
## 搜索
深度优先搜索和广度优先搜索广泛运用于树和图中,但是它们的应用远远不止如此。
@@ -2312,53 +2371,6 @@ private void backtracking(int row) {
}
```
-## 分治
-
-**给表达式加括号**
-
-[241. Different Ways to Add Parentheses (Medium)](https://leetcode.com/problems/different-ways-to-add-parentheses/description/)
-
-```html
-Input: "2-1-1".
-
-((2-1)-1) = 0
-(2-(1-1)) = 2
-
-Output : [0, 2]
-```
-
-```java
-public List diffWaysToCompute(String input) {
- List ways = new ArrayList<>();
- for (int i = 0; i < input.length(); i++) {
- char c = input.charAt(i);
- if (c == '+' || c == '-' || c == '*') {
- List left = diffWaysToCompute(input.substring(0, i));
- List right = diffWaysToCompute(input.substring(i + 1));
- for (int l : left) {
- for (int r : right) {
- switch (c) {
- case '+':
- ways.add(l + r);
- break;
- case '-':
- ways.add(l - r);
- break;
- case '*':
- ways.add(l * r);
- break;
- }
- }
- }
- }
- }
- if (ways.size() == 0) {
- ways.add(Integer.valueOf(input));
- }
- return ways;
-}
-```
-
## 动态规划
递归和动态规划都是将原问题拆成多个子问题然后求解,他们之间最本质的区别是,动态规划保存了子问题的解,避免重复计算。
diff --git a/notes/Leetcode-Database 题解.md b/notes/Leetcode-Database 题解.md
index 5ab726c5..5b4a3d00 100644
--- a/notes/Leetcode-Database 题解.md
+++ b/notes/Leetcode-Database 题解.md
@@ -461,7 +461,7 @@ Employee 表:
+----+-------+--------+-----------+
```
-查找所有员工,他们的薪资大于其经理薪资。
+查找薪资大于其经理薪资的员工信息。
## SQL Schema
@@ -924,27 +924,27 @@ VALUES
```sql
SELECT
s1.id - 1 AS id,
- s1.student
+ s1.student
FROM
- seat s1
+ seat s1
WHERE
s1.id MOD 2 = 0 UNION
SELECT
s2.id + 1 AS id,
- s2.student
+ s2.student
FROM
- seat s2
+ seat s2
WHERE
- s2.id MOD 2 = 1
+ s2.id MOD 2 = 1
AND s2.id != ( SELECT max( s3.id ) FROM seat s3 ) UNION
SELECT
s4.id AS id,
- s4.student
+ s4.student
FROM
- seat s4
+ seat s4
WHERE
- s4.id MOD 2 = 1
- AND s4.id = ( SELECT max( s5.id ) FROM seat s5 )
+ s4.id MOD 2 = 1
+ AND s4.id = ( SELECT max( s5.id ) FROM seat s5 )
ORDER BY
id;
```
diff --git a/notes/设计模式.md b/notes/设计模式.md
index d48d7b36..b8f4dc9b 100644
--- a/notes/设计模式.md
+++ b/notes/设计模式.md
@@ -98,7 +98,9 @@ public static synchronized Singleton getUniqueInstance() {
(三)饿汉式-线程安全
-线程不安全问题主要是由于 uniqueInstance 被实例化了多次,如果 uniqueInstance 采用直接实例化的话,就不会被实例化多次,也就不会产生线程不安全问题。但是直接实例化的方式也丢失了延迟实例化带来的节约资源的优势。
+线程不安全问题主要是由于 uniqueInstance 被实例化了多次,如果 uniqueInstance 采用直接实例化的话,就不会被实例化多次,也就不会产生线程不安全问题。
+
+但是直接实例化的方式也丢失了延迟实例化带来的节约资源的好处。
```java
private static Singleton uniqueInstance = new Singleton();
@@ -106,7 +108,7 @@ private static Singleton uniqueInstance = new Singleton();
(四)双重校验锁-线程安全
-uniqueInstance 只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行。也就是说,只有当 uniqueInstance 没有被实例化时,才需要进行加锁。
+uniqueInstance 只需要被实例化一次,之后就可以直接使用了。加锁操作只需要对实例化那部分的代码进行,只有当 uniqueInstance 没有被实例化时,才需要进行加锁。
双重校验锁先判断 uniqueInstance 是否已经被实例化,如果没有被实例化,那么才对实例化语句进行加锁。
@@ -131,7 +133,7 @@ public class Singleton {
}
```
-考虑下面的实现,也就是只使用了一个 if 语句。在 uniqueInstance == null 的情况下,如果两个线程同时执行 if 语句,那么两个线程就会同时进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行 `uniqueInstance = new Singleton();` 这条语句,只是先后的问题,也就是说会进行两次实例化,从而产生了两个实例。因此必须使用双重校验锁,也就是需要使用两个 if 语句。
+考虑下面的实现,也就是只使用了一个 if 语句。在 uniqueInstance == null 的情况下,如果两个线程同时执行 if 语句,那么两个线程就会同时进入 if 语句块内。虽然在 if 语句块内有加锁操作,但是两个线程都会执行 `uniqueInstance = new Singleton();` 这条语句,只是先后的问题,那么就会进行两次实例化,从而产生了两个实例。因此必须使用双重校验锁,也就是需要使用两个 if 语句。
```java
if (uniqueInstance == null) {
@@ -157,7 +159,7 @@ uniqueInstance 采用 volatile 关键字修饰也是很有必要的。`uniqueIns
这种方式不仅具有延迟初始化的好处,而且由虚拟机提供了对线程安全的支持。
-```source-java
+```java
public class Singleton {
private Singleton() {
@@ -299,7 +301,7 @@ public class Client {
### 意图
-定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化推迟到子类。
+定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化操作推迟到子类。
### 类图