题目
There are two sorted arrays nums1 and nums2 of size m and n respectively.
Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).
You may assume nums1 and nums2 cannot be both empty.
Example 1:
nums1 = [1, 3]
nums2 = [2]
The median is 2.0
Example 2:
nums1 = [1, 2]
nums2 = [3, 4]
The median is (2 + 3)/2 = 2.5
题目大意
给定两个大小为 m 和 n 的有序数组 nums1 和 nums2。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1 和 nums2 不会同时为空。
解题思路
给出两个有序数组,要求找出这两个数组合并以后的有序数组中的中位数。要求时间复杂度为 O(log (m+n))。
这一题最容易想到的办法是把两个数组合并,然后取出中位数。但是合并有序数组的操作是 O(m+n)
的,不符合题意。看到题目给的 log
的时间复杂度,很容易联想到二分搜索。
由于要找到最终合并以后数组的中位数,两个数组的总大小也知道,所以中间这个位置也是知道的。只需要二分搜索一个数组中切分的位置,另一个数组中切分的位置也能得到。为了使得时间复杂度最小,所以二分搜索两个数组中长度较小的那个数组。
关键的问题是如何切分数组 1 和数组 2 。其实就是如何切分数组 1 。先随便二分产生一个 midA
,切分的线何时算满足了中位数的条件呢?即,线左边的数都小于右边的数,即,nums1[midA-1] ≤ nums2[midB] && nums2[midB-1] ≤ nums1[midA]
。如果这些条件都不满足,切分线就需要调整。如果 nums1[midA] < nums2[midB-1]
,说明 midA
这条线划分出来左边的数小了,切分线应该右移;如果 nums1[midA-1] > nums2[midB]
,说明 midA 这条线划分出来左边的数大了,切分线应该左移。经过多次调整以后,切分线总能找到满足条件的解。
假设现在找到了切分的两条线了,数组 1
在切分线两边的下标分别是 midA - 1
和 midA
。数组 2
在切分线两边的下标分别是 midB - 1
和 midB
。最终合并成最终数组,如果数组长度是奇数,那么中位数就是 max(nums1[midA-1], nums2[midB-1])
。如果数组长度是偶数,那么中间位置的两个数依次是:max(nums1[midA-1], nums2[midB-1])
和 min(nums1[midA], nums2[midB])
,那么中位数就是 (max(nums1[midA-1], nums2[midB-1]) + min(nums1[midA], nums2[midB])) / 2
。图示见下图:

方法一:暴力合并(基础思路)
思路
- 合并数组:直接将两个有序数组合并为一个新的有序数组。
- 计算中位数:根据合并后数组的长度奇偶性,计算中位数
复杂度分析
- 时间复杂度:O(m+n),需要遍历两个数组的所有元素。
- 空间复杂度:O(m+n),需要额外的数组存储合并结果。
方法二:二分查找法(优化核心)
思路
- 通过二分查找在较短的数组上确定分割点,使得两个数组的左半部分元素总数等于右半部分(或多一个),并且满足左半部分所有元素均小于等于右半部分的元素。
- 确保短数组优先:令
nums1
为较短的数组,减少二分查找的范围。
- 计算分割点:通过二分查找在
nums1
上确定分割点 i
,并计算 nums2
上的对应分割点 j
。
- 调整条件:确保
nums1[i-1] ≤ nums2[j]
且 nums2[j-1] ≤ nums1[i]
,即左半部分的最大值不超过右半部分的最小值。
- 处理边界:当分割点位于数组端点时,使用
INT_MIN
或 INT_MAX
作为边界值。
复杂度分析
- 时间复杂度:O(log(min(m,n))),通过二分查找在较短数组上快速定位分割点。
- 空间复杂度:O(1),仅使用常数级额外空间。
- 边界处理:使用
INT_MIN
和 INT_MAX
处理分割点在数组端点的情况,避免复杂的条件判断。
方法三:双指针法(进阶优化)
思路
- 通过双指针遍历两个数组,直接找到中位数位置,无需合并整个数组。
- 计算目标位置:根据总长度确定中位数所在的位置
k
。
- 指针移动:每次比较两个数组当前指针的值,移动较小值的指针,直到找到第
k
小的元素。
复杂度分析
- 时间复杂度:O(k),其中 k=(m+n+1)/2,最坏情况下需要遍历一半元素。
- 空间复杂度:O(1),仅使用常数级额外空间。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size) { int totalSize = nums1Size + nums2Size; int* merged = (int*)malloc(totalSize * sizeof(int)); int i = 0, j = 0, k = 0; while (i < nums1Size && j < nums2Size) { merged[k++] = (nums1[i] < nums2[j]) ? nums1[i++] : nums2[j++]; } while (i < nums1Size) merged[k++] = nums1[i++]; while (j < nums2Size) merged[k++] = nums2[j++]; double median; if (totalSize % 2 == 1) { median = (double)merged[totalSize / 2]; } else { median = (double)(merged[totalSize / 2 - 1] + merged[totalSize / 2]) / 2.0; } free(merged); return median; }
|
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
| #include <stdio.h> #include <limits.h>
int max(int a, int b) { return a > b ? a : b; }
int min(int a, int b) { return a < b ? a : b; }
double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size) { if (nums1Size > nums2Size) { return findMedianSortedArrays(nums2, nums2Size, nums1, nums1Size); } int m = nums1Size; int n = nums2Size; int left = 0; int right = m; while (left <= right) { int midA = (left + right) / 2; int midB = (m + n + 1) / 2 - midA; int leftA = (midA == 0) ? INT_MIN : nums1[midA - 1]; int rightA = (midA == m) ? INT_MAX : nums1[midA]; int leftB = (midB == 0) ? INT_MIN : nums2[midB - 1]; int rightB = (midB == n) ? INT_MAX : nums2[midB]; if (leftA <= rightB && leftB <= rightA) { if ((m + n) % 2 == 1) { return (double)max(leftA, leftB); } else { return (double)(max(leftA, leftB) + min(rightA, rightB)) / 2.0; } } else if (leftA > rightB) { right = midA - 1; } else { left = midA + 1; } } return 0.0; }
|
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
| double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size) { int total = nums1Size + nums2Size; if (total == 0) return 0.0; int k = total / 2; int i = 0, j = 0; int first = 0, second = 0; for (int count = 0; count <= k; count++) { first = second; if (i >= nums1Size) { second = nums2[j++]; } else if (j >= nums2Size) { second = nums1[i++]; } else if (nums1[i] < nums2[j]) { second = nums1[i++]; } else { second = nums2[j++]; } } if (total % 2 == 1) { return (double)second; } else { return (double)(first + second) / 2.0; } }
|
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
| int compFunc(void* a, void* b) { //排序算法: /*返回值需要注意 < 0 elem1将被排在elem2前面 0 elem1 等于 elem2 > 0 elem1 将被排在elem2后面 */ int* node1 = (int*)a; int* node2 = (int*)b;
return (*node1 - *node2); }
double findMedianSortedArrays(int* nums1, int nums1Size, int* nums2, int nums2Size) { int *merge = (int * )malloc ( sizeof(int) * (nums1Size +nums2Size) ) ; int i = 0; //复制数组nums1 for(i = 0; i <nums1Size; i++ ) { merge[i] = nums1[i]; } //复制数组nums2 for(i = 0; i <nums2Size; i++ ) { merge[nums1Size + i] = nums2[i]; }
//快速排序 qsort(merge,nums1Size + nums2Size,sizeof(int),compFunc);
double middleValue; if( (nums1Size+ nums2Size) % 2 == 1 ) { middleValue = (double)merge[ (nums1Size+ nums2Size) / 2]; } else { middleValue = ((double)merge[ (nums1Size+ nums2Size) / 2 - 1] + (double)merge[ (nums1Size+ nums2Size) / 2])/2; } return middleValue;
}
|