寻找两个正序数组的中位数

189 阅读2分钟

要求:

给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。 请你找出这两个正序数组的中位数,并且要求算法的时间复杂度为 O(log(m + n))。 假设 nums1 和 nums2 不会同时为空。

示例:

nums1 = [1, 3]
nums2 = [2]
中位数是 2.0

nums1 = [1, 2]
nums2 = [3, 4]
中位数是 (2 + 3)/2 = 2.5

思路:

这个哥们的思路太厉害了,分析的也很到位: windliang: leetcode-cn.com/problems/me… 求中位数也可以理解为求第k小数的一种特殊情况,精简一下求第k小数的算法。 已知两个数组均为有序数组,先不考虑特殊情况,比较两个数组里的第k/2个数字。奇数默认向下取整,注意是第k/2个数字,不是下标为k/2的数字。

  1. 如果A[k/2] < B[k/2],那么A[1],A[2],...A[k/2]都不会是第k小的数字。因为A数组中比A[k/2]小的数字有k/2-1个,假设B[k/2]之前的数字都比A[k/2]小,那最多也只有k/2-1个数字,加起来一共k-2个数字,所以A[k/2]最多是第k-1小的数字,所以比他小的都可以删除,至少A数组中在它前面的都比它小都可以排除。
  2. 如果A[k/2]=B[k/2],随便排除哪一个都可以,都是一样的效果。
  3. 有时候会遇到某个数组剩余的长度比k/2小,这时候就把某个数组全部删除掉了,把删掉的元素个数去掉,在剩下的另一个数组里按索引查找就行。对数组长度和k/2进行一个比较,Math.min(k/2, len)
  4. 为了统一设置,每次都保证数组1的长度小于数组2的长度。
  5. 数组长度可能为奇数也可能为偶书,那就求两次求均值。

代码:

public double findMedian(int[] nums1, int[] nums2) {
    int n = nums1.length;
    int m = nums2.length;
    int left = (n+m+1)/2;
    int right = (n+m+2)/2;
    //奇数和偶数的情况合并,若为奇数就是求两次
    return (getKth(nums1,0,n-1,nums2,0,m-1,left) + getKth(nums1,0,n-1,nums2,0,m-1,right))/2
}
public int getKth(int[] nums1, int start1, int end1, int[] nums2, int start2, int end2, int k) {
    //输入两个数组,限定好左边界和右边界,每次更新,k代表每次寻找的第K小的数字
    int len1 = end1 - start1 + 1;
    int len2 = end2 - start2 + 1;
    //保证len1<len2,这样有数组空了就一定是len1了
    if (len1 > len2) return getKth(nums2, start2, end2, nums1, start1, end1, k);
    if (len1 == 0) return nums2[start2+k-1];
    if (k == 1) return Math.min(nums1[start1], nums2[start2]);
    
    int i = start1 + Math.min(len1, k/2) - 1;
    int j = start2 + Math.min(len2, k/2) - 1;
    
    if (nums1[i] > nums[j]) {
        return getKth(nums1,start1,end1,nums2,j+1,end2, k-Math.min(len2, k/2));
    } else {
        return getKth(nums1,i+1,end1,nums2,start2,end2, k-Math.min(len1, k/2));
    }
}