- 寻找两个有序数组的中位数
给定两个大小为 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)
链接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
方法一:暴力求解,时间复杂度(m+n),合并数组,如果nums1.length+nums2.length的和为奇数,则中位数为下标为(nums1.length+nums2.length)/2的数,如果为偶数,则中位数为下标(nums1.length+nums2.length)/2和(nums1.length+nums2.length)/2-1的数的平均数。
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
//合并数组,
//如果nums1.length+nums2.length的和为奇数,则中位数为下标为(nums1.length+nums2.length)/2的数
//如果为偶数,则中位数为下标(nums1.length+nums2.length)/2和(nums1.length+nums2.length)/2-1的数的平均数
double median=0;
//如果nums1为空,则将nums2传入函数res求解
if(nums1.length==0)
{
median=res(nums2);
return median;
}
//如果nums2为空,则将nums1传入函数res求解
if(nums2.length==0)
{
median=res(nums1);
return median;
}
//新建一个数组用来存储合并出的新数组
int[] nums=new int[nums1.length+nums2.length];
int i=0;
int j=0;
int k=0;
while(k<nums.length)
{
if(i==nums1.length)//如果i到达nums1的尾部
{
while(j!=nums2.length)//而j没有到达nums2的尾部时
{
nums[k++]=nums2[j++];//将剩余的nums2加入新数组的尾部
}
break;
}
if(j==nums2.length)//如果j到达nums2的尾部
{
while(i!=nums1.length)//而i没有到达nums1的尾部时
{
nums[k++]=nums1[i++];//将剩余的nums1加入新数组的尾部
}
break;
}
if(nums1[i]<nums2[j])//如果nums1[i]小于nums2[j]
{
nums[k++]=nums1[i++];//则将nums1[i]存入新数组
}
else
{
nums[k++]=nums2[j++];//否则将nums2[j]存入新数组
}
}
//将新数组传入函数res求解
median=res(nums);
return median;
}
public double res(int[] num)
{
double number=num[0];
if(num.length%2==0)//如果数组长度为偶数
{
number=(num[num.length/2]+num[(num.length/2)-1])/2.0;//则中位数为中间两个数的平均数
}
else
{
number=num[num.length/2];//如果数组长度为奇数,则中位数为中间那个数
}
return number;
}
}
方法二:二分法,时间复杂度 O(log(m + n)),求中位数,其实就是求第 k 小数的一种特殊情况。采用递归的思路,每次排除有三种可能:(1)两个数组的长度都大于k/2,则每次递归排除掉k/2个元素,比较两个数组在第k/2位置的数字,谁的小,就把对应数组的前k/2个数字排除,然后将新生成的数组和没有改变的数组进入递归,k要减去排除的数字的个数也传入递归;(2)只要有一个数组的长度小于k/2,则比较这个数组尾部数字与另一个数组的第k/2个数字的大小,如果这个数组尾部数字小于另一个数组的第k/2个数字,则排除掉这个数组剩余的所有元素,将空数组和没改变的数组进入递归,k要减去排除的数字的个数也传入递归;否则,排除掉另一个数组的前k/2个元素,将新生成的数组和没有改变的数组进入递归,k要减去排除的数字的个数也传入递归。(3)如果两个数组的长度都小于k/2,则比较两个数组的尾部数字,哪个数组的尾部数字小,则排除该数字的剩余所有元素,将空数组和没改变的数组进入递归,k要减去排除的数字的个数也传入递归。
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int m=nums1.length;
int n=nums2.length;
int left=(m+n+1)/2;
int right=(m+n+2)/2;
//将数组长度为奇数和为偶数合并为一种情况,如果为奇数,则计算两次同样的k
return (FMSA(nums1,0,m-1,nums2,0,n-1,left)+FMSA(nums1,0,m-1,nums2,0,n-1,right))/2.0;
}
private int FMSA(int[] nums1,int start1,int ends1,int[] nums2,int start2,int ends2,int k)
{
int len1=ends1-start1+1;
int len2=ends2-start2+1;
//让len1为短数组,则以后若有数组长度为0,则一定是nums1
if(len1>len2)
{
return FMSA(nums2,start2,ends2,nums1,start1,ends1,k);
}
//如果数组长度为0,则返回nums2中第k小的数
if(len1==0)
{
return nums2[start2+k-1];
}
//如果k为1,则比较两个数组头的大小,取小的那个为结果
if(k==1)
{
return Math.min(nums1[start1],nums2[start2]);
}
//将i指向nums1的第k/2个元素,或者,如果nums1的长度小于k/2,则指向nums1的尾部
int i=start1+Math.min(len1,k/2)-1;
//将j指向nums2的第k/2个元素,或者,如果nums2的长度小于k/2,则指向nums2的尾部
int j=start2+Math.min(len2,k/2)-1;
//比较i和j所指向的两个数字的大小
if(nums1[i]>nums2[j])
{
return FMSA(nums1,start1,ends1,nums2,j+1,ends2,k-(j-start2+1));
}
else
{
return FMSA(nums1,i+1,ends1,nums2,start2,ends2,k-(i-start1+1));
}
}
}