From 06b9804ec7f79f2340d9e97b4e4fc9b8a9adb726 Mon Sep 17 00:00:00 2001
From: CyC2018 <1029579233@qq.com>
Date: Fri, 10 Aug 2018 21:58:11 +0800
Subject: [PATCH] auto commit
---
"notes/Leetcode \351\242\230\350\247\243.md" | 1070 +++++++++--------
...code-Database \351\242\230\350\247\243.md" | 20 +-
...76\350\256\241\346\250\241\345\274\217.md" | 12 +-
3 files changed, 558 insertions(+), 544 deletions(-)
diff --git "a/notes/Leetcode \351\242\230\350\247\243.md" "b/notes/Leetcode \351\242\230\350\247\243.md"
index 102fe9b6..44f3d9dc 100644
--- "a/notes/Leetcode \351\242\230\350\247\243.md"
+++ "b/notes/Leetcode \351\242\230\350\247\243.md"
@@ -1,18 +1,18 @@
* [算法思想](#算法思想)
- * [贪心思想](#贪心思想)
* [双指针](#双指针)
* [排序](#排序)
* [快速选择](#快速选择)
* [堆排序](#堆排序)
* [桶排序](#桶排序)
* [荷兰国旗问题](#荷兰国旗问题)
+ * [贪心思想](#贪心思想)
* [二分查找](#二分查找)
+ * [分治](#分治)
* [搜索](#搜索)
* [BFS](#bfs)
* [DFS](#dfs)
* [Backtracking](#backtracking)
- * [分治](#分治)
* [动态规划](#动态规划)
* [斐波那契数列](#斐波那契数列)
* [矩阵路径](#矩阵路径)
@@ -55,738 +55,750 @@
# 算法思想
-## 贪心思想
+## 双指针
-贪心思想保证每次操作都是局部最优的,并且最后得到的结果是全局最优的。
+双指针主要用于遍历数组,两个指针指向不同的元素,从而协同完成任务。
-**分配饼干**
+**有序数组的 Two Sum**
-[455. Assign Cookies (Easy)](https://leetcode.com/problems/assign-cookies/description/)
+[Leetcode :167. Two Sum II - Input array is sorted (Easy)](https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/description/)
```html
-Input: [1,2], [1,2,3]
-Output: 2
-
-Explanation: You have 2 children and 3 cookies. The greed factors of 2 children are 1, 2.
-You have 3 cookies and their sizes are big enough to gratify all of the children,
-You need to output 2.
+Input: numbers={2, 7, 11, 15}, target=9
+Output: index1=1, index2=2
```
-题目描述:每个孩子都有一个满足度,每个饼干都有一个大小,只有饼干的大小大于等于一个孩子的满足度,该孩子才会获得满足。求解最多可以获得满足的孩子数量。
+题目描述:在有序数组中找出两个数,使它们的和为 target。
-因为最小的孩子最容易得到满足,因此先满足最小孩子。给一个孩子的饼干应当尽量小又能满足该孩子,这样大饼干就能拿来给满足度比较大的孩子。因此贪心策略
+使用双指针,一个指针指向值较小的元素,一个指针指向值较大的元素。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。
-证明:假设在某次选择中,贪心策略选择给当前满足度最小的孩子分配第 m 个饼干,第 m 个饼干为可以满足该孩子的最小饼干。假设存在一种最优策略,给该孩子分配第 n 个饼干,并且 m < n。我们可以发现,经过这一轮分配,贪心策略分配后剩下的饼干一定有一个比最优策略来得大。因此在后续的分配中,贪心策略一定能满足更多的孩子。也就是说不存在比贪心策略更优的策略,即贪心策略就是最优策略。
+- 如果两个指针指向元素的和 sum == target,那么得到要求的结果;
+- 如果 sum > target,移动较大的元素,使 sum 变小一些;
+- 如果 sum < target,移动较小的元素,使 sum 变大一些。
```java
-public int findContentChildren(int[] g, int[] s) {
- Arrays.sort(g);
- Arrays.sort(s);
- int gi = 0, si = 0;
- while (gi < g.length && si < s.length) {
- if (g[gi] <= s[si]) {
- gi++;
+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--;
}
- si++;
}
- return gi;
+ return null;
}
```
-**不重叠的区间个数**
+**两数平方和**
-[435. Non-overlapping Intervals (Medium)](https://leetcode.com/problems/non-overlapping-intervals/description/)
+[633. Sum of Square Numbers (Easy)](https://leetcode.com/problems/sum-of-square-numbers/description/)
```html
-Input: [ [1,2], [1,2], [1,2] ]
-
-Output: 2
-
-Explanation: You need to remove two [1,2] to make the rest of intervals non-overlapping.
+Input: 5
+Output: True
+Explanation: 1 * 1 + 2 * 2 = 5
```
-```html
-Input: [ [1,2], [2,3] ]
-
-Output: 0
+题目描述:判断一个数是否为两个数的平方和,例如 5 = 12 + 22。
-Explanation: You don't need to remove any of the intervals since they're already non-overlapping.
+```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
-public int eraseOverlapIntervals(Interval[] intervals) {
- if (intervals.length == 0) {
- return 0;
- }
- Arrays.sort(intervals, Comparator.comparingInt(o -> o.end));
- int cnt = 1;
- int end = intervals[0].end;
- for (int i = 1; i < intervals.length; i++) {
- if (intervals[i].start < end) {
- continue;
+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;
}
- end = intervals[i].end;
- cnt++;
}
- return intervals.length - cnt;
+ return new String(result);
}
```
-使用 lambda 表示式创建 Comparator 会导致算法运行时间过长,如果注重运行时间,可以修改为普通创建 Comparator 语句:
-
-```java
-Arrays.sort(intervals, new Comparator() {
- @Override
- public int compare(Interval o1, Interval o2) {
- return o1.end - o2.end;
- }
-});
-```
-
-**投飞镖刺破气球**
-
-[452. Minimum Number of Arrows to Burst Balloons (Medium)](https://leetcode.com/problems/minimum-number-of-arrows-to-burst-balloons/description/)
+**回文字符串**
-```
-Input:
-[[10,16], [2,8], [1,6], [7,12]]
+[680. Valid Palindrome II (Easy)](https://leetcode.com/problems/valid-palindrome-ii/description/)
-Output:
-2
+```html
+Input: "abca"
+Output: True
+Explanation: You could delete the character 'c'.
```
-题目描述:气球在一个水平数轴上摆放,可以重叠,飞镖垂直投向坐标轴,使得路径上的气球都会刺破。求解最小的投飞镖次数使所有气球都被刺破。
-
-也是计算不重叠的区间个数,不过和 Non-overlapping Intervals 的区别在于,[1, 2] 和 [2, 3] 在本题中算是重叠区间。
+题目描述:可以删除一个字符,判断是否能构成回文字符串。
```java
-public int findMinArrowShots(int[][] points) {
- if (points.length == 0) {
- return 0;
+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);
+ }
}
- Arrays.sort(points, Comparator.comparingInt(o -> o[1]));
- int cnt = 1, end = points[0][1];
- for (int i = 1; i < points.length; i++) {
- if (points[i][0] <= end) {
- continue;
+ return true;
+}
+
+private boolean isPalindrome(String s, int i, int j) {
+ while (i < j) {
+ if (s.charAt(i++) != s.charAt(j--)) {
+ return false;
}
- cnt++;
- end = points[i][1];
}
- return cnt;
+ return true;
}
```
-**根据身高和序号重组队列**
+**归并两个有序数组**
-[406. Queue Reconstruction by Height(Medium)](https://leetcode.com/problems/queue-reconstruction-by-height/description/)
+[88. Merge Sorted Array (Easy)](https://leetcode.com/problems/merge-sorted-array/description/)
```html
Input:
-[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
+nums1 = [1,2,3,0,0,0], m = 3
+nums2 = [2,5,6], n = 3
-Output:
-[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]
+Output: [1,2,2,3,5,6]
```
-题目描述:一个学生用两个分量 (h, k) 描述,h 表示身高,k 表示排在前面的有 k 个学生的身高比他高或者和他一样高。
-
-为了在每次插入操作时不影响后续的操作,身高较高的学生应该先做插入操作,否则身高较小的学生原先正确插入第 k 个位置可能会变成第 k+1 个位置。
+题目描述:把归并结果存到第一个数组上。
-身高降序、k 值升序,然后按排好序的顺序插入队列的第 k 个位置中。
+需要从尾开始遍历,否则在 nums1 上归并得到的值会覆盖还未进行归并比较的值。
```java
-public int[][] reconstructQueue(int[][] people) {
- if (people == null || people.length == 0 || people[0].length == 0) {
- return new int[0][0];
- }
- Arrays.sort(people, (a, b) -> (a[0] == b[0] ? a[1] - b[1] : b[0] - a[0]));
- List queue = new ArrayList<>();
- for (int[] p : people) {
- queue.add(p[1], p);
+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--];
+ }
}
- return queue.toArray(new int[queue.size()][]);
}
```
-**分隔字符串使同种字符出现在一起**
+**判断链表是否存在环**
-[763. Partition Labels (Medium)](https://leetcode.com/problems/partition-labels/description/)
+[141. Linked List Cycle (Easy)](https://leetcode.com/problems/linked-list-cycle/description/)
-```html
-Input: S = "ababcbacadefegdehijhklij"
-Output: [9,7,8]
-Explanation:
-The partition is "ababcbaca", "defegde", "hijhklij".
-This is a partition so that each letter appears in at most one part.
-A partition like "ababcbacadefegde", "hijhklij" is incorrect, because it splits S into less parts.
-```
+使用双指针,一个指针每次移动一个节点,一个指针每次移动两个节点,如果存在环,那么这两个指针一定会相遇。
```java
-public List partitionLabels(String S) {
- int[] lastIndexsOfChar = new int[26];
- for (int i = 0; i < S.length(); i++) {
- lastIndexsOfChar[char2Index(S.charAt(i))] = i;
+public boolean hasCycle(ListNode head) {
+ if (head == null) {
+ return false;
}
- List partitions = new ArrayList<>();
- int firstIndex = 0;
- while (firstIndex < S.length()) {
- int lastIndex = firstIndex;
- for (int i = firstIndex; i < S.length() && i <= lastIndex; i++) {
- int index = lastIndexsOfChar[char2Index(S.charAt(i))];
- if (index > lastIndex) {
- lastIndex = index;
- }
+ ListNode l1 = head, l2 = head.next;
+ while (l1 != null && l2 != null && l2.next != null) {
+ if (l1 == l2) {
+ return true;
}
- partitions.add(lastIndex - firstIndex + 1);
- firstIndex = lastIndex + 1;
+ l1 = l1.next;
+ l2 = l2.next.next;
}
- return partitions;
-}
-
-private int char2Index(char c) {
- return c - 'a';
+ return false;
}
```
+**最长子序列**
-**种植花朵**
+[524. Longest Word in Dictionary through Deleting (Medium)](https://leetcode.com/problems/longest-word-in-dictionary-through-deleting/description/)
-[605. Can Place Flowers (Easy)](https://leetcode.com/problems/can-place-flowers/description/)
+```
+Input:
+s = "abpcplea", d = ["ale","apple","monkey","plea"]
-```html
-Input: flowerbed = [1,0,0,0,1], n = 1
-Output: True
+Output:
+"apple"
```
-题目描述:花朵之间至少需要一个单位的间隔,求解是否能种下 n 朵花。
+题目描述:删除 s 中的一些字符,使得它构成字符串列表 d 中的一个字符串,找出能构成的最长字符串。如果有多个相同长度的结果,返回字典序的最大字符串。
```java
-public boolean canPlaceFlowers(int[] flowerbed, int n) {
- int len = flowerbed.length;
- int cnt = 0;
- for (int i = 0; i < len && cnt < n; i++) {
- if (flowerbed[i] == 1) {
+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;
}
- int pre = i == 0 ? 0 : flowerbed[i - 1];
- int next = i == len - 1 ? 0 : flowerbed[i + 1];
- if (pre == 0 && next == 0) {
- cnt++;
- flowerbed[i] = 1;
+ if (isValid(s, target)) {
+ longestWord = target;
}
}
- return cnt >= n;
+ return longestWord;
}
-```
-
-**判断是否为子序列**
-
-[392. Is Subsequence (Medium)](https://leetcode.com/problems/is-subsequence/description/)
-
-```html
-s = "abc", t = "ahbgdc"
-Return true.
-```
-```java
-public boolean isSubsequence(String s, String t) {
- int index = -1;
- for (char c : s.toCharArray()) {
- index = t.indexOf(c, index + 1);
- if (index == -1) {
- return false;
+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 true;
+ return j == target.length();
}
```
-**修改一个数成为非递减数组**
+## 排序
-[665. Non-decreasing Array (Easy)](https://leetcode.com/problems/non-decreasing-array/description/)
+### 快速选择
-```html
-Input: [4,2,3]
-Output: True
-Explanation: You could modify the first 4 to 1 to get a non-decreasing array.
-```
+用于求解 **Kth Element** 问题,使用快速排序的 partition() 进行实现。
-题目描述:判断一个数组能不能只修改一个数就成为非递减数组。
+需要先打乱数组,否则最坏情况下时间复杂度为 O(N2)。
-在出现 nums[i] < nums[i - 1] 时,需要考虑的是应该修改数组的哪个数,使得本次修改能使 i 之前的数组成为非递减数组,并且 **不影响后续的操作** 。优先考虑令 nums[i - 1] = nums[i],因为如果修改 nums[i] = nums[i - 1] 的话,那么 nums[i] 这个数会变大,就有可能比 nums[i + 1] 大,从而影响了后续操作。还有一个比较特别的情况就是 nums[i] < nums[i - 2],只修改 nums[i - 1] = nums[i] 不能使数组成为非递减数组,只能修改 nums[i] = nums[i - 1]。
+### 堆排序
+
+用于求解 **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 boolean checkPossibility(int[] nums) {
- int cnt = 0;
- for (int i = 1; i < nums.length && cnt < 2; i++) {
- if (nums[i] >= nums[i - 1]) {
- continue;
- }
- cnt++;
- if (i - 2 >= 0 && nums[i - 2] > nums[i]) {
- nums[i] = nums[i - 1];
- } else {
- nums[i - 1] = nums[i];
- }
- }
- return cnt <= 1;
+public int findKthLargest(int[] nums, int k) {
+ Arrays.sort(nums);
+ return nums[nums.length - k];
}
```
-**股票的最大收益**
-
-[122. Best Time to Buy and Sell Stock II (Easy)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/description/)
+**堆排序** :时间复杂度 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();
+}
+```
-对于 [a, b, c, d],如果有 a <= b <= c <= d ,那么最大收益为 d - a。而 d - a = (d - c) + (c - b) + (b - a) ,因此当访问到一个 prices[i] 且 prices[i] - prices[i-1] > 0,那么就把 prices[i] - prices[i-1] 添加到收益中,从而在局部最优的情况下也保证全局最优。
+**快速选择** :时间复杂度 O(N),空间复杂度 O(1)
```java
-public int maxProfit(int[] prices) {
- int profit = 0;
- for (int i = 1; i < prices.length; i++) {
- if (prices[i] > prices[i - 1]) {
- profit += (prices[i] - prices[i - 1]);
+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 profit;
+ 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;
+}
+```
-**有序数组的 Two Sum**
+### 桶排序
-[Leetcode :167. Two Sum II - Input array is sorted (Easy)](https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/description/)
+**出现频率最多的 k 个数**
+
+[347. Top K Frequent Elements (Medium)](https://leetcode.com/problems/top-k-frequent-elements/description/)
```html
-Input: numbers={2, 7, 11, 15}, target=9
-Output: index1=1, index2=2
+Given [1,1,1,2,2,3] and k = 2, return [1,2].
```
-题目描述:在有序数组中找出两个数,使它们的和为 target。
-
-使用双指针,一个指针指向值较小的元素,一个指针指向值较大的元素。指向较小元素的指针从头向尾遍历,指向较大元素的指针从尾向头遍历。
+设置若干个桶,每个桶存储出现频率相同的数,并且桶的下标代表桶中数出现的频率,即第 i 个桶中存储的数出现的频率为 i。
-如果两个指针指向元素的和 sum == target,那么得到要求的结果;如果 sum > target,移动较大的元素,使 sum 变小一些;如果 sum < target,移动较小的元素,使 sum 变大一些。
+把数都放到桶之后,从后向前遍历桶,最先得到的 k 个数就是出现频率最多的的 k 个数。
```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--;
+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);
}
- return null;
+ 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;
}
```
-**两数平方和**
+**按照字符出现次数对字符串排序**
-[633. Sum of Square Numbers (Easy)](https://leetcode.com/problems/sum-of-square-numbers/description/)
+[451. Sort Characters By Frequency (Medium)](https://leetcode.com/problems/sort-characters-by-frequency/description/)
```html
-Input: 5
-Output: True
-Explanation: 1 * 1 + 2 * 2 = 5
-```
+Input:
+"tree"
-题目描述:判断一个数是否为两个数的平方和,例如 5 = 12 + 22。
+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 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++;
+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);
}
- return false;
+ 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();
}
```
-**反转字符串中的元音字符**
+### 荷兰国旗问题
-[345. Reverse Vowels of a String (Easy)](https://leetcode.com/problems/reverse-vowels-of-a-string/description/)
+荷兰国旗包含三种颜色:红、白、蓝。
+
+有三种颜色的球,算法的目标是将这三种球按颜色顺序正确地排列。
+
+它其实是三向切分快速排序的一种变种,在三向切分快速排序中,每次切分都将数组分成三个区间:小于切分元素、等于切分元素、大于切分元素,而该算法是将数组分成三个区间:等于红色、等于白色、等于蓝色。
+
+
+
+**按颜色进行排序**
+
+[75. Sort Colors (Medium)](https://leetcode.com/problems/sort-colors/description/)
```html
-Given s = "leetcode", return "leotcede".
+Input: [2,0,2,1,1,0]
+Output: [0,0,1,1,2,2]
```
-使用双指针,指向待反转的两个元音字符,一个指针从头向尾遍历,一个指针从尾到头遍历。
+题目描述:只有 0/1/2 三种颜色。
```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;
+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 {
- result[i++] = cj;
- result[j--] = ci;
+ ++one;
}
}
- return new String(result);
+}
+
+private void swap(int[] nums, int i, int j) {
+ int t = nums[i];
+ nums[i] = nums[j];
+ nums[j] = t;
}
```
-**回文字符串**
+## 贪心思想
-[680. Valid Palindrome II (Easy)](https://leetcode.com/problems/valid-palindrome-ii/description/)
+保证每次操作都是局部最优的,并且最后得到的结果是全局最优的。
+
+**分配饼干**
+
+[455. Assign Cookies (Easy)](https://leetcode.com/problems/assign-cookies/description/)
```html
-Input: "abca"
-Output: True
-Explanation: You could delete the character 'c'.
+Input: [1,2], [1,2,3]
+Output: 2
+
+Explanation: You have 2 children and 3 cookies. The greed factors of 2 children are 1, 2.
+You have 3 cookies and their sizes are big enough to gratify all of the children,
+You need to output 2.
```
-题目描述:可以删除一个字符,判断是否能构成回文字符串。
+题目描述:每个孩子都有一个满足度,每个饼干都有一个大小,只有饼干的大小大于等于一个孩子的满足度,该孩子才会获得满足。求解最多可以获得满足的孩子数量。
-```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;
+证明:假设在某次选择中,贪心策略选择给当前满足度最小的孩子分配第 m 个饼干,第 m 个饼干为可以满足该孩子的最小饼干。假设存在一种最优策略,给该孩子分配第 n 个饼干,并且 m < n。我们可以发现,经过这一轮分配,贪心策略分配后剩下的饼干一定有一个比最优策略来得大。因此在后续的分配中,贪心策略一定能满足更多的孩子。也就是说不存在比贪心策略更优的策略,即贪心策略就是最优策略。
+
+```java
+public int findContentChildren(int[] g, int[] s) {
+ Arrays.sort(g);
+ Arrays.sort(s);
+ int gi = 0, si = 0;
+ while (gi < g.length && si < s.length) {
+ if (g[gi] <= s[si]) {
+ gi++;
}
+ si++;
}
- return true;
+ return gi;
}
```
-**归并两个有序数组**
+**不重叠的区间个数**
-[88. Merge Sorted Array (Easy)](https://leetcode.com/problems/merge-sorted-array/description/)
+[435. Non-overlapping Intervals (Medium)](https://leetcode.com/problems/non-overlapping-intervals/description/)
```html
-Input:
-nums1 = [1,2,3,0,0,0], m = 3
-nums2 = [2,5,6], n = 3
+Input: [ [1,2], [1,2], [1,2] ]
-Output: [1,2,2,3,5,6]
+Output: 2
+
+Explanation: You need to remove two [1,2] to make the rest of intervals non-overlapping.
```
-题目描述:把归并结果存到第一个数组上。
+```html
+Input: [ [1,2], [2,3] ]
-需要从尾开始遍历,否则在 nums1 上归并得到的值会覆盖还未进行归并比较的值
+Output: 0
-```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--];
- }
- }
-}
+Explanation: You don't need to remove any of the intervals since they're already non-overlapping.
```
-**判断链表是否存在环**
+题目描述:计算让一组区间不重叠所需要移除的区间个数。
-[141. Linked List Cycle (Easy)](https://leetcode.com/problems/linked-list-cycle/description/)
+计算最多能组成的不重叠区间个数,然后用区间总个数减去不重叠区间的个数。
-使用双指针,一个指针每次移动一个节点,一个指针每次移动两个节点,如果存在环,那么这两个指针一定会相遇。
+在每次选择中,区间的结尾最为重要,选择的区间结尾越小,留给后面的区间的空间越大,那么后面能够选择的区间个数也就越大。
+
+按区间的结尾进行排序,每次选择结尾最小,并且和前一个区间不重叠的区间。
```java
-public boolean hasCycle(ListNode head) {
- if (head == null) {
- return false;
+public int eraseOverlapIntervals(Interval[] intervals) {
+ if (intervals.length == 0) {
+ return 0;
}
- ListNode l1 = head, l2 = head.next;
- while (l1 != null && l2 != null && l2.next != null) {
- if (l1 == l2) {
- return true;
+ Arrays.sort(intervals, Comparator.comparingInt(o -> o.end));
+ int cnt = 1;
+ int end = intervals[0].end;
+ for (int i = 1; i < intervals.length; i++) {
+ if (intervals[i].start < end) {
+ continue;
}
- l1 = l1.next;
- l2 = l2.next.next;
+ end = intervals[i].end;
+ cnt++;
}
- return false;
+ return intervals.length - cnt;
}
```
-**最长子序列**
+使用 lambda 表示式创建 Comparator 会导致算法运行时间过长,如果注重运行时间,可以修改为普通创建 Comparator 语句:
-[524. Longest Word in Dictionary through Deleting (Medium)](https://leetcode.com/problems/longest-word-in-dictionary-through-deleting/description/)
+```java
+Arrays.sort(intervals, new Comparator() {
+ @Override
+ public int compare(Interval o1, Interval o2) {
+ return o1.end - o2.end;
+ }
+});
+```
+
+**投飞镖刺破气球**
+
+[452. Minimum Number of Arrows to Burst Balloons (Medium)](https://leetcode.com/problems/minimum-number-of-arrows-to-burst-balloons/description/)
```
Input:
-s = "abpcplea", d = ["ale","apple","monkey","plea"]
+[[10,16], [2,8], [1,6], [7,12]]
Output:
-"apple"
+2
```
-题目描述:删除 s 中的一些字符,使得它构成字符串列表 d 中的一个字符串,找出能构成的最长字符串。如果有多个相同长度的结果,返回按字典序排序的最大字符串。
+题目描述:气球在一个水平数轴上摆放,可以重叠,飞镖垂直投向坐标轴,使得路径上的气球都会刺破。求解最小的投飞镖次数使所有气球都被刺破。
+
+也是计算不重叠的区间个数,不过和 Non-overlapping Intervals 的区别在于,[1, 2] 和 [2, 3] 在本题中算是重叠区间。
```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;
- }
+public int findMinArrowShots(int[][] points) {
+ if (points.length == 0) {
+ return 0;
}
- 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++;
+ Arrays.sort(points, Comparator.comparingInt(o -> o[1]));
+ int cnt = 1, end = points[0][1];
+ for (int i = 1; i < points.length; i++) {
+ if (points[i][0] <= end) {
+ continue;
}
- i++;
+ cnt++;
+ end = points[i][1];
}
- return j == target.length();
+ return cnt;
}
```
-## 排序
-
-### 快速选择
-
-一般用于求解 **Kth Element** 问题,可以在 O(N) 时间复杂度,O(1) 空间复杂度完成求解工作。
+**根据身高和序号重组队列**
-与快速排序一样,快速选择一般需要先打乱数组,否则最坏情况下时间复杂度为 O(N2)。
+[406. Queue Reconstruction by Height(Medium)](https://leetcode.com/problems/queue-reconstruction-by-height/description/)
-### 堆排序
+```html
+Input:
+[[7,0], [4,4], [7,1], [5,0], [6,1], [5,2]]
-堆排序用于求解 **TopK Elements** 问题,通过维护一个大小为 K 的堆,堆中的元素就是 TopK Elements。当然它也可以用于求解 Kth Element 问题,堆顶元素就是 Kth Element。快速选择也可以求解 TopK Elements 问题,因为找到 Kth Element 之后,再遍历一次数组,所有小于等于 Kth Element 的元素都是 TopK Elements。可以看到,快速选择和堆排序都可以求解 Kth Element 和 TopK Elements 问题。
+Output:
+[[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]
+```
-**Kth Element**
+题目描述:一个学生用两个分量 (h, k) 描述,h 表示身高,k 表示排在前面的有 k 个学生的身高比他高或者和他一样高。
-[215. Kth Largest Element in an Array (Medium)](https://leetcode.com/problems/kth-largest-element-in-an-array/description/)
+为了在每次插入操作时不影响后续的操作,身高较高的学生应该先做插入操作,否则身高较小的学生原先正确插入第 k 个位置可能会变成第 k+1 个位置。
-**排序** :时间复杂度 O(NlogN),空间复杂度 O(1)
+身高降序、k 值升序,然后按排好序的顺序插入队列的第 k 个位置中。
```java
-public int findKthLargest(int[] nums, int k) {
- Arrays.sort(nums);
- return nums[nums.length - k];
+public int[][] reconstructQueue(int[][] people) {
+ if (people == null || people.length == 0 || people[0].length == 0) {
+ return new int[0][0];
+ }
+ Arrays.sort(people, (a, b) -> (a[0] == b[0] ? a[1] - b[1] : b[0] - a[0]));
+ List queue = new ArrayList<>();
+ for (int[] p : people) {
+ queue.add(p[1], p);
+ }
+ return queue.toArray(new int[queue.size()][]);
}
```
-**堆排序** :时间复杂度 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();
-}
-```
+[763. Partition Labels (Medium)](https://leetcode.com/problems/partition-labels/description/)
-**快速选择** :时间复杂度 O(N),空间复杂度 O(1)
+```html
+Input: S = "ababcbacadefegdehijhklij"
+Output: [9,7,8]
+Explanation:
+The partition is "ababcbaca", "defegde", "hijhklij".
+This is a partition so that each letter appears in at most one part.
+A partition like "ababcbacadefegde", "hijhklij" is incorrect, because it splits S into less parts.
+```
```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;
- }
+public List partitionLabels(String S) {
+ int[] lastIndexsOfChar = new int[26];
+ for (int i = 0; i < S.length(); i++) {
+ lastIndexsOfChar[char2Index(S.charAt(i))] = i;
}
- 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;
+ List partitions = new ArrayList<>();
+ int firstIndex = 0;
+ while (firstIndex < S.length()) {
+ int lastIndex = firstIndex;
+ for (int i = firstIndex; i < S.length() && i <= lastIndex; i++) {
+ int index = lastIndexsOfChar[char2Index(S.charAt(i))];
+ if (index > lastIndex) {
+ lastIndex = index;
+ }
}
- swap(a, i, j);
+ partitions.add(lastIndex - firstIndex + 1);
+ firstIndex = lastIndex + 1;
}
- swap(a, l, j);
- return j;
+ return partitions;
}
-private void swap(int[] a, int i, int j) {
- int t = a[i];
- a[i] = a[j];
- a[j] = t;
+private int char2Index(char c) {
+ return c - 'a';
}
```
-### 桶排序
-**出现频率最多的 k 个数**
+**种植花朵**
-[347. Top K Frequent Elements (Medium)](https://leetcode.com/problems/top-k-frequent-elements/description/)
+[605. Can Place Flowers (Easy)](https://leetcode.com/problems/can-place-flowers/description/)
```html
-Given [1,1,1,2,2,3] and k = 2, return [1,2].
+Input: flowerbed = [1,0,0,0,1], n = 1
+Output: True
```
-设置若干个桶,每个桶存储出现频率相同的数,并且桶的下标代表桶中数出现的频率,即第 i 个桶中存储的数出现的频率为 i。把数都放到桶之后,从后向前遍历桶,最先得到的 k 个数就是出现频率最多的的 k 个数。
+题目描述:花朵之间至少需要一个单位的间隔,求解是否能种下 n 朵花。
```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<>();
+public boolean canPlaceFlowers(int[] flowerbed, int n) {
+ int len = flowerbed.length;
+ int cnt = 0;
+ for (int i = 0; i < len && cnt < n; i++) {
+ if (flowerbed[i] == 1) {
+ continue;
}
- 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]);
+ int pre = i == 0 ? 0 : flowerbed[i - 1];
+ int next = i == len - 1 ? 0 : flowerbed[i + 1];
+ if (pre == 0 && next == 0) {
+ cnt++;
+ flowerbed[i] = 1;
}
}
- return topK;
+ return cnt >= n;
}
```
-**按照字符出现次数对字符串排序**
+**判断是否为子序列**
-[451. Sort Characters By Frequency (Medium)](https://leetcode.com/problems/sort-characters-by-frequency/description/)
+[392. Is Subsequence (Medium)](https://leetcode.com/problems/is-subsequence/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.
+s = "abc", t = "ahbgdc"
+Return true.
```
```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);
- }
+public boolean isSubsequence(String s, String t) {
+ int index = -1;
+ for (char c : s.toCharArray()) {
+ index = t.indexOf(c, index + 1);
+ if (index == -1) {
+ return false;
}
}
- return str.toString();
+ return true;
}
```
-### 荷兰国旗问题
-
-荷兰国旗包含三种颜色:红、白、蓝。有这三种颜色的球,算法的目标是将这三种球按颜色顺序正确地排列。
-
-它其实是三向切分快速排序的一种变种,在三向切分快速排序中,每次切分都将数组分成三个区间:小于切分元素、等于切分元素、大于切分元素,而该算法是将数组分成三个区间:等于红色、等于白色、等于蓝色。
-
-
-
-**按颜色进行排序**
+**修改一个数成为非递减数组**
-[75. Sort Colors (Medium)](https://leetcode.com/problems/sort-colors/description/)
+[665. Non-decreasing Array (Easy)](https://leetcode.com/problems/non-decreasing-array/description/)
```html
-Input: [2,0,2,1,1,0]
-Output: [0,0,1,1,2,2]
+Input: [4,2,3]
+Output: True
+Explanation: You could modify the first 4 to 1 to get a non-decreasing array.
```
-题目描述:只有 0/1/2 三种颜色。
+题目描述:判断一个数组能不能只修改一个数就成为非递减数组。
+
+在出现 nums[i] < nums[i - 1] 时,需要考虑的是应该修改数组的哪个数,使得本次修改能使 i 之前的数组成为非递减数组,并且 **不影响后续的操作** 。优先考虑令 nums[i - 1] = nums[i],因为如果修改 nums[i] = nums[i - 1] 的话,那么 nums[i] 这个数会变大,就有可能比 nums[i + 1] 大,从而影响了后续操作。还有一个比较特别的情况就是 nums[i] < nums[i - 2],只修改 nums[i - 1] = nums[i] 不能使数组成为非递减数组,只能修改 nums[i] = nums[i - 1]。
```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);
+public boolean checkPossibility(int[] nums) {
+ int cnt = 0;
+ for (int i = 1; i < nums.length && cnt < 2; i++) {
+ if (nums[i] >= nums[i - 1]) {
+ continue;
+ }
+ cnt++;
+ if (i - 2 >= 0 && nums[i - 2] > nums[i]) {
+ nums[i] = nums[i - 1];
} else {
- ++one;
+ nums[i - 1] = nums[i];
}
}
+ return cnt <= 1;
}
+```
-private void swap(int[] nums, int i, int j) {
- int t = nums[i];
- nums[i] = nums[j];
- nums[j] = t;
+**股票的最大收益**
+
+[122. Best Time to Buy and Sell Stock II (Easy)](https://leetcode.com/problems/best-time-to-buy-and-sell-stock-ii/description/)
+
+题目描述:一次股票交易包含买入和卖出,多个交易之间不能交叉进行。
+
+对于 [a, b, c, d],如果有 a <= b <= c <= d ,那么最大收益为 d - a。而 d - a = (d - c) + (c - b) + (b - a) ,因此当访问到一个 prices[i] 且 prices[i] - prices[i-1] > 0,那么就把 prices[i] - prices[i-1] 添加到收益中,从而在局部最优的情况下也保证全局最优。
+
+```java
+public int maxProfit(int[] prices) {
+ int profit = 0;
+ for (int i = 1; i < prices.length; i++) {
+ if (prices[i] > prices[i - 1]) {
+ profit += (prices[i] - prices[i - 1]);
+ }
+ }
+ return profit;
}
```
@@ -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 \351\242\230\350\247\243.md" "b/notes/Leetcode-Database \351\242\230\350\247\243.md"
index 5ab726c5..5b4a3d00 100644
--- "a/notes/Leetcode-Database \351\242\230\350\247\243.md"
+++ "b/notes/Leetcode-Database \351\242\230\350\247\243.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/\350\256\276\350\256\241\346\250\241\345\274\217.md" "b/notes/\350\256\276\350\256\241\346\250\241\345\274\217.md"
index d48d7b36..b8f4dc9b 100644
--- "a/notes/\350\256\276\350\256\241\346\250\241\345\274\217.md"
+++ "b/notes/\350\256\276\350\256\241\346\250\241\345\274\217.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 {
### 意图
-定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化推迟到子类。
+定义了一个创建对象的接口,但由子类决定要实例化哪个类。工厂方法把实例化操作推迟到子类。
### 类图
--
GitLab