[LeetCode] 4. Median of Two Sorted Arrays
Given two sorted arrays nums1
and nums2
of size m
and n
respectively, return the median of the two sorted arrays.
The overall run time complexity should be O(log (m+n))
.
Example 1:
Input: nums1 = [1,3], nums2 = [2] Output: 2.00000 Explanation: merged array = [1,2,3] and median is 2.
Example 2:
Input: nums1 = [1,2], nums2 = [3,4] Output: 2.50000 Explanation: merged array = [1,2,3,4] and median is (2 + 3) / 2 = 2.5.
Constraints:
nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
-106 <= nums1[i], nums2[i] <= 106
寻找两个正序数组的中位数。
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/median-of-two-sorted-arrays
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
这个题是有时间复杂度的要求的,O(log (m+n))。如果没有这个要求,因为两个数组都是各自有序的,所以就可以把两个数组合并然后找中位数就行了。我这里给出最优解,思路参见这个帖子。时间复杂度更好,是O(log(min(m, n)))。
让我们先回到比较直观的做法。我们可以先将两个数组合并,根据合并后的数组长度 m + n(很容易知道)返回中间的那个元素或者中间两个元素的平均值(这个也很容易知道)。如果只是一般情况,那么就是 nums1 里面拿出来几个元素,nums2 里面也拿出来几个元素凑成合并数组的左半边,同样的,合并数组的右半边也会是这样组成的。如果我知道 nums1 里面拿出来几个元素就能凑成合并数组的左半边的话,我同样也就能知道 nums2 需要贡献几个元素。为什么呢?因为中位数的 index = (len1 + len2) / 2。nums2 需要贡献的元素个数是 index - nums1 贡献的元素个数。所以这个题的时间复杂度可以被控制在 O(log(min(m, n))),找到短的那个数组的切分位置,你也就找到了另一个数组的切分位置。
找到切分位置的时候,如何知道这一刀切分的对不对呢?可以记录四个元素L1, R1, L2, R2分别表示在数组1和数组2的切分位置左边和右边的元素都是什么。如果这一刀的位置是正确的,则应该有的结果是
- L1<=R2
- L2<=R1
蓝色部分的数字不会大于黄色部分的数字。这样就能确保,左边的元素都小于右边的元素了。下图帮助理解。
时间O(log(min(m, n)))
空间O(1)
Java实现
1 class Solution { 2 public double findMedianSortedArrays(int[] nums1, int[] nums2) { 3 int lenA = nums1.length; 4 int lenB = nums2.length; 5 // make sure the first array is shorter 6 if (lenA > lenB) { 7 return findMedianSortedArrays(nums2, nums1); 8 } 9 10 // 如果一个数组为空,直接计算另一个数组的中位数 11 if (lenA == 0) { 12 return ((double) nums2[(lenB - 1) / 2] + (double) nums2[lenB / 2]) / 2; 13 } 14 15 // normal case 16 int len = lenA + lenB; 17 // 对nums1做二分的范围 18 int aLeft = 0; 19 int aRight = lenA; 20 int cutA; 21 int cutB; 22 while (aLeft <= aRight) { 23 cutA = aLeft + (aRight - aLeft) / 2; 24 cutB = (len + 1) / 2 - cutA; 25 double L1 = (cutA == 0) ? Integer.MIN_VALUE : nums1[cutA - 1]; 26 double R1 = (cutA == lenA) ? Integer.MAX_VALUE : nums1[cutA]; 27 double L2 = (cutB == 0) ? Integer.MIN_VALUE : nums2[cutB - 1]; 28 double R2 = (cutB == lenB) ? Integer.MAX_VALUE : nums2[cutB]; 29 if (L1 > R2) { 30 aRight = cutA - 1; 31 } else if (L2 > R1) { 32 aLeft = cutA + 1; 33 } else { 34 if (len % 2 == 0) { 35 return (Math.max(L1, L2) + Math.min(R1, R2)) / 2; 36 } else { 37 return Math.max(L1, L2); 38 } 39 } 40 } 41 return -1; 42 } 43 }
JavaScript实现
1 /** 2 * @param {number[]} nums1 3 * @param {number[]} nums2 4 * @return {number} 5 */ 6 var findMedianSortedArrays = function(nums1, nums2) { 7 /* 8 nums3 => nums1 + nums2 => nums1 9 只需考慮 nums1 為最短邊,則計算最少 10 */ 11 if (nums1.length > nums2.length) { 12 return findMedianSortedArrays(nums2, nums1); 13 } 14 let len = nums1.length + nums2.length; 15 let cut1 = 0; 16 let cut2 = 0; 17 let cutL = 0; 18 let cutR = nums1.length; 19 while (cut1 <= nums1.length) { 20 /* 21 * L1 R1 22 * (cut1)-1 cut1 23 * nums1 = 3 5 | 8 9 24 * 25 * L2 R2 26 * (cut2)-1 cut2 27 * nums2 = 1 2 7 | 10 11 12 28 * 29 * L1 + L2 = L3 R3 = R1 + R2 30 * nums3 = 1 2 3 5 7 | 8 9 10 11 12 31 * 32 * cut 1 為 L3 提供 [3, 5] 所以 cut2 則提供 5 - 2 = 3([1, 2, 7]) 元素 33 */ 34 cut1 = parseInt(cutL + (cutR - cutL) / 2); 35 cut2 = parseInt((len / 2) - cut1); 36 // 不可使用 MAX_VALUE 要用 Number.MIN_SAFE_INTEGER,否則會超過時間 37 // 若超出界線 則使用最小值 38 let L1 = (cut1 === 0) ? Number.MIN_SAFE_INTEGER : nums1[cut1-1]; 39 let L2 = (cut2 === 0) ? Number.MIN_SAFE_INTEGER : nums2[cut2-1]; 40 // 不可使用 MIN_VALUE 要用 Number.MIN_SAFE_INTEGER,否則會超過時間 41 // 若超出界線 則使用最大值 42 let R1 = (cut1 === nums1.length) ? Number.MAX_SAFE_INTEGER : nums1[cut1]; 43 let R2 = (cut2 === nums2.length) ? Number.MAX_SAFE_INTEGER : nums2[cut2]; 44 /* 45 假如情況為: 46 nums1 = 3 5 8 | 9 (<<) 47 nums2 = 1 2 | 7 11 12 48 8 > 7 49 則 nums1 必須 在往上一格移動 << 所以為 (cut1 - 1) 50 */ 51 if(L1 > R2) { 52 cutR = cut1 - 1; 53 /* 54 假如情況為: 55 nums1 = 3 | 5 8 9 (>>) 56 nums2 = 1 2 7 | 11 12 57 7 > 5 58 則 nums1 必須 在往下一格移動 >> 所以為 (cut1 + 1) 59 */ 60 } else if (L2 > R1) { 61 cutL = cut1 + 1; 62 /* 63 滿足 以下條件 64 R1 >= L2 65 R2 >= L1 66 */ 67 } else { 68 // 假如 nums3 為偶數 69 // 如果是偶數 L 選擇最大的部分, R 選擇最小的部分 除以 2 70 /* 71 * L1 + L2 = L3 R3 = R1 + R2 72 * nums3 = 1 2 3 5 7 | 8 9 10 11 12 73 * */ 74 if ((len % 2) === 0) { 75 L1 = (L1 > L2) ? L1 : L2; 76 R1 = (R1 < R2) ? R1 : R2; 77 return (L1+R1) / 2; 78 } else{ 79 // 假如 nums3 為奇數 80 /* nums1 = 3 5 8 | 9 15 81 * nums2 = 1 2 7 | 10 11 12 82 * 如果是奇數選小的 83 * nums3 = 1 2 3 5 7 8 | 9 10 11 12 15 84 * */ 85 console.log(R1, R2); 86 R1 = (R1 < R2) ? R1 : R2; 87 return R1; 88 } 89 } 90 } 91 return -1; 92 }; 93 console.log(findMedianSortedArrays([3, 5, 8, 9], [1 , 2, 7, 10, 11, 12]))
我在二刷的时候看到另一个讲解,也非常好。思路和时间复杂度是一样的。我整合了一下两个讲解的代码,感觉清晰了很多。
Java实现
1 class Solution { 2 public double findMedianSortedArrays(int[] nums1, int[] nums2) { 3 // corner case 4 if (nums1.length > nums2.length) { 5 return findMedianSortedArrays(nums2, nums1); 6 } 7 8 // normal case 9 int m = nums1.length; 10 int n = nums2.length; 11 // 在短的数组里面进行二分法 12 int left = 0; 13 int right = m; 14 while (left <= right) { 15 int i = left + (right - left) / 2; // nums1分割点 16 int j = (m + n + 1) / 2 - i; // nums2分割点 17 int L1 = i == 0 ? Integer.MIN_VALUE : nums1[i - 1]; 18 int R1 = i == m ? Integer.MAX_VALUE : nums1[i]; 19 int L2 = j == 0 ? Integer.MIN_VALUE : nums2[j - 1]; 20 int R2 = j == n ? Integer.MAX_VALUE : nums2[j]; 21 if (L1 <= R2 && L2 <= R1) { 22 // 如果数字总个数是奇数,中位数就是这样算 23 if (((m + n) % 2) == 1) { 24 return Math.max(L1, L2); 25 } 26 // 否则中位数就是这样算 27 else { 28 return (double) (Math.max(L1, L2) + Math.min(R1, R2)) / 2; 29 } 30 } else if (L2 > R1) { 31 left = i + 1; 32 } else { 33 right = i - 1; 34 } 35 } 36 return -1; 37 } 38 }