题目描述
给定两个大小为 m 和 n 的有序数组 nums1
和 nums2
。
请你找出这两个有序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。
你可以假设 nums1
和 nums2
不会同时为空。
示例 1:
nums1 = [1, 3] nums2 = [2] 则中位数是 2.0
示例 2:
nums1 = [1, 2] nums2 = [3, 4] 则中位数是 (2 + 3)/2 = 2.5
解题思路
这题实在是让人想哭,题解来自 LeetCode题解最好的大神
如果没有要求时间复杂度的话,直接merge就很清晰。
先说关键结论:1.找两个数组的分界线,分界线满足一定条件。
那中位数只可能与分界线两侧的数相关。
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
/*
注意前提是num1,nums2有序。有序数组又要求时间复杂度是log级别的,那只能是二分法
二分法就是在有序数组的找一个数,那问题就是我们要找一个什么样的数呢
问题转化:
在num1中找一个索引i作为分界线,那i左边的部分在合并之后也在合并数组的中位数左边。
同理,i以及i右边的部分会出现在合并数组的中位数右边。有了i那nums2中的j也就确定了,
因为中位数就是在中间的数啊 中位数两侧的元素个数要么相等,(中位数取中间两个数的平均值)
要么是差一个,(就当作是左边比右边多一个,中位数取左边的最后一个就行了)。
所以 那左半部分的元素个数totalLeft就是(m+n+1)/2,那j就是left_total - i;
要想i和j在合并 之后是中位数的位置,需要保证num1左侧最大值<=num2右侧最小值,
同理,还需要保证num2左侧最大值<=num1右侧最小值,同一个数组的左右无需比较了,
因为本身就是有序数组啊
我们只需要关注i 在nums1中用二分里去找i的位置 i是中间线右边的第一个数
那i的范围就是[0,m]闭区间
*/
int m = nums1.length;
int n = nums2.length;
//预处理一下,始终让num1是较短的数组
if (m > n){ //小技巧,交换一下它们的引用,就可以在更短的数组里做二分 即log(min(m,n))
int[] tmp = nums1;
nums1 = nums2;
nums2 = tmp;
}
m = nums1.length;
n = nums2.length;
int left = 0;
int right = m ; //可能整个nums1都在合并数组的左边呀
int totalLeft = (m + n + 1)/2; //如果m + n是偶数,那加不加一不影响结果,如果是奇数,左边比右边就正好多分一个
while (left < right){
int i = (left + right) / 2;
int j = totalLeft - i; //i先确定,j必然就是这么多
if(nums2[j - 1] > nums1[i]){
left = i + 1;
}else{
right = i;
}
}
//退出循环 我们已经确定了i和j的位置 那最后的结果就是在i和j 的两侧取得
int i = left;
int j = totalLeft - i;
// 那如果缺少一侧呢 那就根据需要取一个最大值或者最小值
//例如 左侧需要从两个数组各自的左侧选出最大值 如果缺少左侧就给各个minvalue就好了 肯定不会被选上
// 如果不缺少 那对于左侧来说肯定是最后一个元素
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 != 0)) { //总数是奇数
return Math.max(nums1LeftMax, nums2LeftMax);
} else {
return (double) ((Math.max(nums1LeftMax, nums2LeftMax) + Math.min(nums1RightMin, nums2RightMin))) / 2;
}
}
}