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