方法一、首先直观能想到的是合并两个有序数组,然后找出中间位置的数字,合并两个有序数组有专门的一道题,不难。
方法二、接着我们可能会想到,求中位数,只需要找到第K(k为中位数位置)个元素就可以了,没必要合并整个数组。从第一个元素开始,分别对两个逐一遍历,找到第K大的元素,也不难。
前两种方法都是O(N)级别的时间复杂度。那么O(logN)级别的怎么计算呢?Log级别只能折半查找。于是就有了重点的方法三。
方法三、每次取两个数组各自的第k/2个元素进行比较,扔掉小的那部分,再进行查找。那么为什么不是左右分别找K呢,因为可能会错过。但是k/2为什么一定不会错过呢,因为较大的那个数组目前也才遍历到第k/2个,如果较小那个数组全部都是在小于的范围内,那也刚好到k位置而已,不会超过。
这个解法当时困住我的有这么几个点:
1)奇偶问题,如何确定K?
2)折半查找时除以2以后取哪边,边界问题一直是比较困扰我的问题,总是不能清晰计算,且不漏掉任何情况。
3)一个数组到头了怎么判断?
后来看了题解里一个特别清晰的方法,加上自己的理解,解答如下:
public class test {
public static void main(String[] args) {
int[] nums1={1,2,3,4};
int[] nums2={1,2,3,4,5,6,7,8};
System.out.println(findMedium(nums1,nums2));
}
public static double findMedium(int[] nums1,int[] nums2) {
int m=nums1.length;
int n=nums2.length;
int k1=(m+n+1)/2;
int k2=(m+n+2)/2;
return (findKthNum(nums1,0,m-1,nums2,0,n-1,k1)+findKthNum(nums1,0,m-1,nums2,0,n-1,k2))*0.5;
}
public static int findKthNum(int[] nums1,int left1,int right1, int[] nums2,int left2,int right2,int k) {
int width1=right1-left1;
int width2=right2-left2;
if(width1==0) return nums2[left2+k-1];
if(width2==0) return nums1[left1+k-1];
if(k==1) return nums1[left1]>nums2[left2]?nums2[left2]:nums1[left1];
int i=left1+Math.min(width1,k/2)-1;//防止一步跨出去了
int j=left2+Math.min(width2,k/2)-1;
if(nums1[i]<nums2[j]) {
return findKthNum(nums1,i+1,right1,nums2,left2,right2,k-Math.min(width1,k/2));
}
else {
return findKthNum(nums1,left1,right1,nums2,i+1,right2,k-Math.min(width2,k/2));
}
}
}
1,对于奇偶位,统一归并为求两遍值,然后取平均值,巧。
2,对于有一个空了的情况,直接取剩下另一个的第k个元素,大胆直观。
3,至于步长,每次在剩余长度和折半长度之间取最小值,解决了跨越出去的问题。
4,注意第k个元素是nums[left+k-1],而不是nums[left+k],一开始也犯了这个错误。