力扣算法日记(4.寻找两个正序数组的中位数)
4.寻找两个正序数组的中位数
来源:力扣(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
示例 3:
输入:nums1 = [0,0], nums2 = [0,0]
输出:0.00000
示例 4:
输入:nums1 = [], nums2 = [1]
输出:1.00000
示例 5:
输入:nums1 = [2], nums2 = []
输出:2.00000
提示:
nums1.length == m
nums2.length == n
0 <= m <= 1000
0 <= n <= 1000
1 <= m + n <= 2000
-106 <= nums1[i], nums2[i] <= 106
1、二分查找
思路:
因为题中所给的两个数组都是升序的数组,那么,我们只需要找到一个分界线。
①元素总数是偶数个:让左边的数组元素和右边的数组元素相等(分割线左边的最大值和右边的最小值相加÷2)
②奇数个:让左边的元素个数比右边的多一(左右都可以,左边多一就取左边最大的,右边多一就取右边最小的)
在这个分界线的左边元素的最大值,小于等于右边元素的最小值。
找到这个分界线以后,很明显,中位数只与这条分割线左右两侧的元素有关。
细节处理:
tips1:
两个数组,一个长度为m,另一个长度为n,确定左边元素个数。(可以具体带入数值来理解)
①奇数:
(m+n+1)/ 2
②偶数:
(m+n) / 2
可以统一处理为:(m+n+1)/ 2
因为整除,的结果是向下取整
例如:(4+1)/ 2 = 2 =4/2
tips2:
如何满足分割线左边的元素要小于等于分割线右边的元素的条件?
单看其中一个数组,由于数组有序,所以肯定能满足条件。
所以问题就在于第二个数组分割线左边的元素,要小于等于第一个数组分割线右边的元素,并且,第一个数组分割线左边的元素要小于等于第二个数组分割线右边的元素。(交叉小于等于)
tips3:
在满足left个数=right个数的时候,不满足交叉小于等于情况的时候,如何继续寻找分割线?
①如果2.left>1.right
说明,第一个的右边太小了,我们再往右边移一位,那这个时候左边的元素就多了一个了,所以我们要把第二个数组的分割线左移一位。
②1.left>2.right(同上,有点懒🤭)
tips4:
两个数组的分割线有什么联系?
分割线左边元素个数和右边元素个数相等(或者左边多1)(下标的数值,就是左边元素个数)
tips5:
当其中一个数组很短的时候,根据tips3不断移位置,访问越界怎么办?
先找短的数组的分界线,以短的数组为准,这样才不会越界。
代码展示
package com.zhuzhuyi;
public class MedianSolution {
public double findMedianSortedArrays(int[] nums1,int[] nums2) {
if (nums1.length > nums2.length) {
int[] temp = nums1;
nums1 = nums2;
nums2 = temp;
}//让短的数组作为nums1,可以保证在短的数组没有越界的情况下,长的也不会越界
int m = nums1.length;
int n =nums2.length;
int totalLeft = (m + n + 1) / 2; //最终应该得到的总的左边元素个数
int left = 0; //第一个数组的左边元素个数
int right = m;//第一个数组的右边元素个数
while (left < right) { //当第一个数组的左边元素个数少于右边的时候
int i = left + (right - left + 1) / 2;
//初始位置先确定在第一个数组的中间
// 当右边界左移一位的时候 i减小一位,j增大一位
int j = totalLeft -i; //j是第二个数组应有的左边元素个数
if (nums1[i - 1] > nums2[j]){ //如果第一个元素左边的最大值大于了第二个元素右边的最小值
//下轮搜索区间[left,i-1]
right = i - 1;//说明第一个元素的左边最大值大了,让右边界左移一位
} else {
//下轮搜索区间[i,right],如果是只有两个元素的数组,会进入死循环,所以取中位数的时候+1
left = i; //如果满足的话
}
}
int i = left;
int j = totalLeft-i;
//num1[i]是不是num的第一个元素?如果是,则给左边的最大值赋值为整型的最小值,使得在取左边最大值的时候不会被选中
// 防止左边没有元素的越界情况,其他同理
int nums1LeftMax = i == 0 ? Integer.MIN_VALUE : nums1[i - 1];
int nums1RightMin = i == m ? Integer.MAX_VALUE : nums1[i];
int nums2LeftMax = i == 0 ? Integer.MIN_VALUE : nums2[j - 1];
int nums2RightMin = i == 0 ? Integer.MAX_VALUE : nums2[j];
//返回:如果是奇数,返回左边最大值,如果是偶数,返回(double)((左边最大值+右边最小值)/ 2),根据示例二知道要返回浮点型
if ((m + n) % 2 == 1) {
return Math.max(nums1LeftMax, nums2LeftMax);
} else {
return (double) ((Math.max(nums1LeftMax, nums2LeftMax) + Math.min(nums1RightMin, nums2RightMin))) / 2;
}
}
}