寻找两个正序数组的中位数-leetcode
题目描述
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n)) 。
示例 1:
输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2
示例 2:
输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
提示:
nums1.length == mnums2.length == n0 <= m <= 10000 <= n <= 10001 <= m + n <= 2000-106 <= nums1[i], nums2[i] <= 106
解法一
思路:
只需要给出两个有序数组的一个恰当的「分割线」,中位数的值就由位于这个「分割线」的两侧的数决定
如何确定「分割线」位置可以使用二分查找法。


所有左侧的元素个数为int totalLeft=(nums1.length+nums2.length+1)/2;
在第一个数组里确定分割线的位置,第二个数组中分割线位置自然就确定了。分割线满足的要求是左侧元素要小于右侧元素。利用二分法在第一个数组中寻找分割线。
代码:
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
if(nums1.length>nums2.length){
int[] temp = nums1;
nums1 = nums2;
nums2 = temp;
}
int m = nums1.length;
int n = nums2.length;
//在nums1的区间[0,m]里寻找适当的分割线,使得nums1[i-1]<=nums2[j] && nums1[j-1]<=nums1[i]
int totalLeft=(m+n+1)/2;
int left=0;
int right=m;
while (left<right){
int i=left+(right-left+1)/2;
int j=totalLeft-i;
//二分逻辑:左边界收敛法”。当 if(nums1[i-1] > nums2[j]) 成立时,说明 i 绝对不合法,必须在左半区间查找;否则,当前的 i 可能是正确的,我们在右半区间继续尝试寻找更大的 i。当循环结束时,left 就是满足条件的最大 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==m?Integer.MAX_VALUE:nums1[i];
int nums2LeftMax=j==0?Integer.MIN_VALUE:nums2[j-1];
int nums2RightMin=j==n?Integer.MAX_VALUE:nums2[j];
if((m+n)%2==1){
return Math.max(nums1LeftMax,nums2LeftMax);
}else{
return (double) ((Math.max(nums1LeftMax,nums2LeftMax)+Math.min(nums1RightMin,nums2RightMin)))/2;
}
}
}
代码执行过程:
设定数组: nums1 = [1, 2, 3, 4, 5, 6, 7] (长度 m=7) nums2 = [8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18] (长度 n=11)
初始步骤:
nums1.length(7) 没有> nums2.length(11),所以不交换。m = 7,n = 11.totalLeft = (m + n + 1) / 2 = (7 + 11 + 1) / 2 = 19 / 2 = 9(整数除法)。- 这意味着我们目标是找到左半部分总共包含 9 个元素。
left = 0,right = m = 7.
二分查找循环 (while (left < right)) 过程:
第一次循环: left = 0, right = 7
i = left + (right - left + 1) / 2 = 0 + (7 - 0 + 1) / 2 = 0 + 8 / 2 = 4j = totalLeft - i = 9 - 4 = 5- 检查条件:
nums1[i-1]vsnums2[j]nums1[3](即4)nums2[5](即13)
nums1[3] (4) > nums2[5] (13)为 假 (4 不大于 13)。- 进入
else分支:left = i = 4。 - 循环结束时:
left = 4,right = 7。
第二次循环: left = 4, right = 7
i = left + (right - left + 1) / 2 = 4 + (7 - 4 + 1) / 2 = 4 + 4 / 2 = 6j = totalLeft - i = 9 - 6 = 3- 检查条件:
nums1[i-1]vsnums2[j]nums1[5](即6)nums2[3](即11)
nums1[5] (6) > nums2[3] (11)为 假 (6 不大于 11)。- 进入
else分支:left = i = 6。 - 循环结束时:
left = 6,right = 7。
第三次循环: left = 6, right = 7
i = left + (right - left + 1) / 2 = 6 + (7 - 6 + 1) / 2 = 6 + 2 / 2 = 7j = totalLeft - i = 9 - 7 = 2- 检查条件:
nums1[i-1]vsnums2[j]nums1[6](即7)nums2[2](即10)
nums1[6] (7) > nums2[2] (10)为 假 (7 不大于 10)。- 进入
else分支:left = i = 7。 - 循环结束时:
left = 7,right = 7。
循环结束: left = 7 不再 < right = 7,循环终止。
确定最终分割线和计算中位数:
- 最终
i = left = 7 - 最终
j = totalLeft - i = 9 - 7 = 2
计算四个边界值:
nums1LeftMax:i = 7且i != 0,所以nums1[i-1]=nums1[6]=7。nums1RightMin:i = 7且i == m(m=7),所以为Integer.MAX_VALUE。nums2LeftMax:j = 2且j != 0,所以nums2[j-1]=nums2[1]=9。nums2RightMin:j = 2且j != n(n=11),所以nums2[j]=nums2[2]=10。
判断中位数类型:
m + n = 7 + 11 = 18(偶数)。- 所以,中位数是
(max(nums1LeftMax, nums2LeftMax) + min(nums1RightMin, nums2RightMin)) / 2.0。 - 中位数 =
(Math.max(7, 9) + Math.min(Integer.MAX_VALUE, 10)) / 2.0 - 中位数 =
(9 + 10) / 2.0 = 19 / 2.0 = 9.5

浙公网安备 33010602011771号