4. 寻找两个正序数组的中位数(困难) 给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。
请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
示例1: 1 2 3 nums1 = [1, 3] nums2 = [2] 则中位数是 2.0
示例2: 1 2 3 nums1 = [1, 2] nums2 = [3, 4] 则中位数是 (2 + 3)/2 = 2.5
示例3: 1 2 3 nums1 = [] nums2 = [1] 则中位数是 1.0
示例4: 1 2 3 nums1 = [] nums2 = [1,2] 则中位数是 1.5
类似递归的二分法(java) 通过 1 2 执行用时:3 ms, 在所有 java 提交中击败了60.78%的用户 内存消耗:41 MB, 在所有 java 提交中击败了100.00%的用户
思路解析
代码如下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 class Solution { public double findMedianSortedArrays (int [] nums1, int [] nums2) { int leftLength = nums1.length; int rightLength = nums2.length; if (leftLength > rightLength) { return findMedianSortedArrays(nums2, nums1); } int totalLeft = (leftLength + rightLength + 1 ) / 2 ; int left = 0 ; int right = leftLength; while (left < right) { int i = left + (right - left + 1 ) / 2 ; int j = totalLeft - i; if (nums1[i - 1 ] > nums2[j]) { right = i - 1 ; } else { left = i; } } int i = left; int j = totalLeft - i; int nums1LeftMax = i == 0 ? Integer.MIN_VALUE : nums1[i - 1 ]; int nums1RightMin = i == leftLength ? Integer.MAX_VALUE : nums1[i]; int nums2LeftMax = j == 0 ? Integer.MIN_VALUE : nums2[j - 1 ]; int nums2RightMin = j == rightLength ? Integer.MAX_VALUE : nums2[j]; if (((leftLength + rightLength) % 2 ) == 1 ) { return Math.max(nums1LeftMax, nums2LeftMax); } else { return (double ) ((Math.max(nums1LeftMax, nums2LeftMax) + Math.min(nums1RightMin, nums2RightMin))) / 2 ; } } }
复杂度分析: 时间复杂度O(log(m+n)),空间复杂度O(1)。
建议先看懂上题的注解。
官方解法 出处: 1 2 3 作者:LeetCode-Solution 链接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/xun-zhao-liang-ge-you-xu-shu-zu-de-zhong-wei-s -114/ 来源:力扣(LeetCode)
方法一:二分查找(C++) 通过 1 2 执行用时:16 ms, 在所有 C++ 提交中击败了84.80%的用户 内存消耗:7.2 MB, 在所有 C++ 提交中击败了100.00%的用户
思路解析 1 2 3 4 5 6 7 8 9 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较 这里的 "/" 表示整除 nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个 nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个 这样 pivot 本身最大也只能是第 k-1 小的元素 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除" ,剩下的作为新的 nums1 数组 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除" ,剩下的作为新的 nums2 数组 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数
代码如下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 #include <iostream> #include <math.h> #include <iomanip> #include <unordered_map> #include <string> #include <stdlib.h> #include <stdio.h> using namespace std ;int getKthElement (const vector <int >& nums1, const vector <int >& nums2, int k) { int m = nums1.size(); int n = nums2.size(); int index1 = 0 , index2 = 0 ; while (true ) { if (index1 == m) { return nums2[index2 + k - 1 ]; } if (index2 == n) { return nums1[index1 + k - 1 ]; } if (k == 1 ) { return min(nums1[index1], nums2[index2]); } int newIndex1 = min(index1 + k / 2 - 1 , m - 1 ); int newIndex2 = min(index2 + k / 2 - 1 , n - 1 ); int pivot1 = nums1[newIndex1]; int pivot2 = nums2[newIndex2]; if (pivot1 <= pivot2) { k -= newIndex1 - index1 + 1 ; index1 = newIndex1 + 1 ; } else { k -= newIndex2 - index2 + 1 ; index2 = newIndex2 + 1 ; } } } double findMedianSortedArrays (vector <int >& nums1, vector <int >& nums2) { int totalLength = nums1.size() + nums2.size(); if (totalLength % 2 == 1 ) { return getKthElement(nums1, nums2, (totalLength + 1 ) / 2 ); } else { return (getKthElement(nums1, nums2, totalLength / 2 ) + getKthElement(nums1, nums2, totalLength / 2 + 1 )) / 2.0 ; } } int main () { vector <int > nums1 = {1 ,3 }; vector <int > nums2 = {2 }; double myanswer = findMedianSortedArrays(nums1,nums2); cout << myanswer << endl ; return 0 ; }
复杂度分析: 时间复杂度:O(log(m+n)),其中 m 和 n 分别是数组 nums1 和 nums2 的长度。初始时有 k=(m+n)/2 或 k=(m+n)/2+1,每一轮循环可以将查找范围减少一半,因此时间复杂度是 O(log(m+n))。
空间复杂度:O(1)。
方法二:划分数组(C++) 通过 1 2 执行用时:24 ms, 在所有 C++ 提交中击败了35.62%的用户 内存消耗:7.2 MB, 在所有 C++ 提交中击败了100.00%的用户
思路解析 1 2 3 在统计中,中位数被用来: 将一个集合划分为两个长度相等的子集,其中一个子集中的元素总是大于另一个子集中的元素。 当一个数组不出现在前一部分时,对应的值为负无穷,就不会对前一部分的最大值产生影响;当一个数组不出现在后一部分时,对应的值为正无穷,就不会对后一部分的最小值产生影响。
代码如下 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #include <iostream> #include <math.h> #include <iomanip> #include <unordered_map> #include <string> #include <stdlib.h> #include <stdio.h> using namespace std ;double findMedianSortedArrays (vector <int >& nums1, vector <int >& nums2) { if (nums1.size() > nums2.size()) { return findMedianSortedArrays(nums2, nums1); } int m = nums1.size(); int n = nums2.size(); int left = 0 , right = m, ansi = -1 ; int median1 = 0 , median2 = 0 ; while (left <= right) { int i = (left + right) / 2 ; int j = (m + n + 1 ) / 2 - i; int nums_im1 = (i == 0 ? INT_MIN : nums1[i - 1 ]); int nums_i = (i == m ? INT_MAX : nums1[i]); int nums_jm1 = (j == 0 ? INT_MIN : nums2[j - 1 ]); int nums_j = (j == n ? INT_MAX : nums2[j]); if (nums_im1 <= nums_j) { ansi = i; median1 = max(nums_im1, nums_jm1); median2 = min(nums_i, nums_j); left = i + 1 ; } else { right = i - 1 ; } } return (m + n) % 2 == 0 ? (median1 + median2) / 2.0 : median1; } int main () { vector <int > nums1 = {1 ,3 }; vector <int > nums2 = {2 }; double myanswer = findMedianSortedArrays(nums1,nums2); cout << myanswer << endl ; return 0 ; }
复杂度分析: 时间复杂度:O(log min(m,n))),其中 m 和 n 分别是数组 nums1 和 nums2 的长度。查找的区间是 [0,m],而该区间的长度在每次循环之后都会减少为原来的一半。所以,只需要执行 log m 次循环。由于每次循环中的操作次数是常数,所以时间复杂度为 O(log m)。由于我们可能需要交换 nums1 和 nums2 使得 m≤n,因此时间复杂度是 O(log min(m,n)))。
空间复杂度:O(1)。