diff --git a/notes/剑指 offer 题解.md b/notes/剑指 offer 题解.md index 8cce9cf6..232ce14e 100644 --- a/notes/剑指 offer 题解.md +++ b/notes/剑指 offer 题解.md @@ -57,6 +57,7 @@ * [48. 最长不含重复字符的子字符串](#48-最长不含重复字符的子字符串) * [49. 丑数](#49-丑数) * [50. 第一个只出现一次的字符位置](#50-第一个只出现一次的字符位置) +* [51. 数组中的逆序对](#51-数组中的逆序对) * [52. 两个链表的第一个公共结点](#52-两个链表的第一个公共结点) * [53 数字在排序数组中出现的次数](#53-数字在排序数组中出现的次数) * [54. 二叉搜索树的第 k 个结点](#54-二叉搜索树的第-k-个结点) @@ -1455,7 +1456,7 @@ private void dfs(TreeNode node, int target, ArrayList path) { ## 题目描述 -输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的 head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空) +输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的 head。

@@ -1519,19 +1520,21 @@ public RandomListNode Clone(RandomListNode pHead) { ```java private TreeNode pre = null; -public TreeNode Convert(TreeNode pRootOfTree) { - if(pRootOfTree == null) return null; - inOrder(pRootOfTree); - while(pRootOfTree.left != null) pRootOfTree = pRootOfTree.left; - return pRootOfTree; +private TreeNode head = null; + +public TreeNode Convert(TreeNode root) { + if (root == null) return null; + inOrder(root); + return head; } private void inOrder(TreeNode node) { - if(node == null) return; + if (node == null) return; inOrder(node.left); node.left = pre; - if(pre != null) pre.right = node; + if (pre != null) pre.right = node; pre = node; + if (head == null) head = node; inOrder(node.right); } ``` @@ -1578,7 +1581,7 @@ public class Solution { ## 题目描述 -输入一个字符串 , 按字典序打印出该字符串中字符的所有排列。例如输入字符串 abc, 则打印出由字符 a, b, c 所能排列出来的所有字符串 abc, acb, bac, bca, cab 和 cba。 +输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串 abc,则打印出由字符 a, b, c 所能排列出来的所有字符串 abc, acb, bac, bca, cab 和 cba。 ## 解题思路 @@ -1616,24 +1619,21 @@ private void backtracking(char[] chars, boolean[] hasUsed, StringBuffer s) { 多数投票问题,可以利用 Boyer-Moore Majority Vote Algorithm 来解决这个问题,使得时间复杂度为 O(n)。 -使用 cnt 来统计一个元素出现的次数,当遍历到的元素和统计元素不想等时,令 cnt--。如果前面查找了 i 个元素,且 cnt == 0 ,说明前 i 个元素没有 majority,或者有 majority,但是出现的次数少于 i / 2 ,因为如果多于 i / 2 的话 cnt 就一定不会为 0 。此时剩下的 n - i 个元素中,majority 的数目依然多于 (n - i) / 2,因此继续查找就能找出 majority。 +使用 cnt 来统计一个元素出现的次数,当遍历到的元素和统计元素不相等时,令 cnt--。如果前面查找了 i 个元素,且 cnt == 0 ,说明前 i 个元素没有 majority,或者有 majority,但是出现的次数少于 i / 2 ,因为如果多于 i / 2 的话 cnt 就一定不会为 0 。此时剩下的 n - i 个元素中,majority 的数目依然多于 (n - i) / 2,因此继续查找就能找出 majority。 ```java public int MoreThanHalfNum_Solution(int[] nums) { - int cnt = 1, num = nums[0]; - for (int i = 1; i < nums.length; i++) { - if (nums[i] == num) cnt++; - else cnt--; + int majority = nums[0]; + for (int i = 1, cnt = 1; i < nums.length; i++) { + cnt = nums[i] == majority ? cnt + 1 : cnt - 1; if (cnt == 0) { - num = nums[i]; + majority = nums[i]; cnt = 1; } } - cnt = 0; - for (int i = 0; i < nums.length; i++) { - if (num == nums[i]) cnt++; - } - return cnt > nums.length / 2 ? num : 0; + int cnt = 0; + for (int val : nums) if (val == majority) cnt++; + return cnt > nums.length / 2 ? majority : 0; } ``` @@ -1644,32 +1644,32 @@ public int MoreThanHalfNum_Solution(int[] nums) { ### 快速选择 - 复杂度:O(N) + O(1) -- 只有当可以修改数组元素时才可以使用 +- 只有当允许修改数组元素时才可以使用 -快速排序的 partition() 方法,会返回一个整数 j 使得 a[lo..j-1] 小于等于 a[j],且 a[j+1..hi] 大于等于 a[j],此时 a[j] 就是数组的第 j 大元素,可以利用这个特性找出数组的第 K 个元素,这种找第 K 个元素的算法称为快速选择算法。 +快速排序的 partition() 方法,会返回一个整数 j 使得 a[l..j-1] 小于等于 a[j],且 a[j+1..h] 大于等于 a[j],此时 a[j] 就是数组的第 j 大元素。可以利用这个特性找出数组的第 K 个元素,这种找第 K 个元素的算法称为快速选择算法。 -找到第 K 个元素之后,就可以再遍历一次数组,所有小于等于该元素的数组元素都在最小的 K 个数中。 +找到第 K 个元素之后,就可以再遍历一次数组,所有小于等于该元素的数组元素都是最小的 K 个数。 ```java public ArrayList GetLeastNumbers_Solution(int[] nums, int k) { if (k > nums.length || k <= 0) return new ArrayList<>(); int kthSmallest = findKthSmallest(nums, k - 1); ArrayList ret = new ArrayList<>(); - for (int num : nums) { - if (num <= kthSmallest && ret.size() < k) ret.add(num); + for (int val : nums) { + if (val <= kthSmallest && ret.size() < k) ret.add(val); } return ret; } public int findKthSmallest(int[] nums, int k) { - int lo = 0; - int hi = nums.length - 1; - while (lo < hi) { - int j = partition(nums, lo, hi); + int l = 0; + int h = nums.length - 1; + while (l < h) { + int j = partition(nums, l, h); if (j < k) { - lo = j + 1; + l = j + 1; } else if (j > k) { - hi = j - 1; + h = j - 1; } else { break; } @@ -1677,29 +1677,25 @@ public int findKthSmallest(int[] nums, int k) { return nums[k]; } -private int partition(int[] nums, int lo, int hi) { - int i = lo; - int j = hi + 1; +private int partition(int[] nums, int l, int h) { + int i = l; + int j = h + 1; while (true) { - while (i < hi && less(nums[++i], nums[lo])) ; - while (j > lo && less(nums[lo], nums[--j])) ; + while (i < h && nums[++i] < nums[l]) ; + while (j > l && nums[l] < nums[--j]) ; if (i >= j) { break; } - exch(nums, i, j); + swap(nums, i, j); } - exch(nums, lo, j); + swap(nums, l, j); return j; } -private void exch(int[] nums, int i, int j) { - final int tmp = nums[i]; +private void swap(int[] nums, int i, int j) { + int t = nums[i]; nums[i] = nums[j]; - nums[j] = tmp; -} - -private boolean less(int v, int w) { - return v < w; + nums[j] = t; } ``` @@ -1708,15 +1704,15 @@ private boolean less(int v, int w) { - 复杂度:O(NlogK) + O(K) - 特别适合处理海量数据 -应该注意的是,应该使用大顶堆来维护最小堆,而不能直接创建一个小顶堆并设置一个大小,企图让小顶堆中的元素都是最小元素。 +应该使用大顶堆来维护最小堆,而不能直接创建一个小顶堆并设置一个大小,企图让小顶堆中的元素都是最小元素。 -维护一个大小为 K 的最小堆过程如下:先添加一个元素,添加完之后如果大顶堆的大小大于 K,那么需要将大顶堆的堆顶元素去除。 +维护一个大小为 K 的最小堆过程如下:在添加一个元素之后,如果大顶堆的大小大于 K,那么需要将大顶堆的堆顶元素去除。 ```java -public ArrayList GetLeastNumbers_Solution(int[] input, int k) { - if (k > input.length || k <= 0) return new ArrayList<>(); +public ArrayList GetLeastNumbers_Solution(int[] nums, int k) { + if (k > nums.length || k <= 0) return new ArrayList<>(); PriorityQueue maxHeap = new PriorityQueue<>((o1, o2) -> o2 - o1); - for (int num : input) { + for (int num : nums) { maxHeap.add(num); if (maxHeap.size() > k) { maxHeap.poll(); @@ -1744,16 +1740,16 @@ public class Solution { // 当前数据流读入的元素个数 private int N = 0; - public void Insert(Integer num) { + public void Insert(Integer val) { // 插入要保证两个堆存于平衡状态 if (N % 2 == 0) { // N 为偶数的情况下插入到右半边。 // 因为右半边元素都要大于左半边,但是新插入的元素不一定比左半边元素来的大, // 因此需要先将元素插入左半边,然后利用左半边为大顶堆的特点,取出堆顶元素即为最大元素,此时插入右半边 - left.add(num); + left.add(val); right.add(left.poll()); } else { - right.add(num); + right.add(val); left.add(right.poll()); } N++; @@ -1801,7 +1797,7 @@ public class Solution { ## 题目描述 -{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为 8(从第 0 个开始,到第 3 个为止)。 +{6,-3,-2,7,-15,1,2,2},连续子数组的最大和为 8(从第 0 个开始,到第 3 个为止)。 ## 解题思路 @@ -1810,9 +1806,9 @@ public int FindGreatestSumOfSubArray(int[] nums) { if (nums.length == 0) return 0; int ret = Integer.MIN_VALUE; int sum = 0; - for (int num : nums) { - if (sum <= 0) sum = num; - else sum += num; + for (int val : nums) { + if (sum <= 0) sum = val; + else sum += val; ret = Math.max(ret, sum); } return ret; @@ -1821,7 +1817,9 @@ public int FindGreatestSumOfSubArray(int[] nums) { # 43. 从 1 到 n 整数中 1 出现的次数 -解题参考:[Leetcode : 233. Number of Digit One](https://leetcode.com/problems/number-of-digit-one/discuss/64381/4+-lines-O(log-n)-C++JavaPython) +## 解题思路 + +> [Leetcode : 233. Number of Digit One](https://leetcode.com/problems/number-of-digit-one/discuss/64381/4+-lines-O(log-n)-C++JavaPython) ```java public int NumberOf1Between1AndN_Solution(int n) { @@ -1859,7 +1857,7 @@ public int digitAtIndex(int index) { /** * digit 位数的数字组成的字符串长度 - * 例如 digit = 2, return 90 + * 例如 digit = 2,return 90 */ private int getAmountOfDigit(int digit) { if (digit == 1) return 10; @@ -1893,7 +1891,7 @@ private int beginNumber(int digit) { ## 解题思路 -可以看成是一个排序问题,在比较两个字符串 S1 和 S2 的大小时,应该比较的是 S1+S2 和 S2+S1 的大小,如果 S1+S2 >= S2+S1,那么应该把 S1 排在前面,否则应该把 S2 排在前面。 +可以看成是一个排序问题,在比较两个字符串 S1 和 S2 的大小时,应该比较的是 S1+S2 和 S2+S1 的大小,如果 S1+S2 < S2+S1,那么应该把 S1 排在前面,否则应该把 S2 排在前面。 ```java public String PrintMinNumber(int[] numbers) { @@ -1911,7 +1909,7 @@ public String PrintMinNumber(int[] numbers) { ## 题目描述 -给定一个数字,按照如下规则翻译成字符串:0 翻译成“a”,1 翻译成“b”...25 翻译成“z”。一个数字有多种翻译可能,例如 12258 一共有 5 种,分别是 bccfi,bwfi,bczi,mcfi,mzi。实现一个函数,用来计算一个数字有多少种不同的翻译方法。 +给定一个数字,按照如下规则翻译成字符串:0 翻译成“a”,1 翻译成“b”... 25 翻译成“z”。一个数字有多种翻译可能,例如 12258 一共有 5 种,分别是 bccfi,bwfi,bczi,mcfi,mzi。实现一个函数,用来计算一个数字有多少种不同的翻译方法。 ## 解题思路 @@ -2032,7 +2030,7 @@ public int FirstNotRepeatingChar(String str) { } ``` -## 51. 数组中的逆序对 +# 51. 数组中的逆序对 ## 题目描述 @@ -2050,19 +2048,19 @@ public int InversePairs(int[] nums) { return (int) (cnt % 1000000007); } -private void mergeSortUp2Down(int[] nums, int start, int end) { - if (end - start < 1) return; - int mid = start + (end - start) / 2; - mergeSortUp2Down(nums, start, mid); - mergeSortUp2Down(nums, mid + 1, end); - merge(nums, start, mid, end); +private void mergeSortUp2Down(int[] nums, int first, int last) { + if (last - first < 1) return; + int mid = first + (last - first) / 2; + mergeSortUp2Down(nums, first, mid); + mergeSortUp2Down(nums, mid + 1, last); + merge(nums, first, mid, last); } -private void merge(int[] nums, int start, int mid, int end) { - int i = start, j = mid + 1, k = start; - while (i <= mid || j <= end) { +private void merge(int[] nums, int first, int mid, int last) { + int i = first, j = mid + 1, k = first; + while (i <= mid || j <= last) { if (i > mid) tmp[k] = nums[j++]; - else if (j > end) tmp[k] = nums[i++]; + else if (j > last) tmp[k] = nums[i++]; else if (nums[i] < nums[j]) tmp[k] = nums[i++]; else { tmp[k] = nums[j++]; @@ -2070,7 +2068,7 @@ private void merge(int[] nums, int start, int mid, int end) { } k++; } - for (k = start; k <= end; k++) { + for (k = first; k <= last; k++) { nums[k] = tmp[k]; } } @@ -2115,7 +2113,7 @@ Input: 3 Output: 4 -```` +``` ## 解题思路 @@ -2158,16 +2156,17 @@ private int getLastK(int[] nums, int K) { 利用二叉搜索数中序遍历有序的特点。 ```java -TreeNode ret; -int cnt = 0; +private TreeNode ret; +private int cnt = 0; -TreeNode KthNode(TreeNode pRoot, int k) { +public TreeNode KthNode(TreeNode pRoot, int k) { inOrder(pRoot, k); return ret; } private void inOrder(TreeNode root, int k) { - if (root == null || cnt > k) return; + if (root == null) return; + if (cnt > k) return; inOrder(root.left, k); cnt++; if (cnt == k) ret = root;