寻找两个正序数组的中位数[困难][二分查找]

2,081 阅读1分钟

题目 4. 寻找两个正序数组的中位数 - 力扣(LeetCode) (leetcode-cn.com)

image.png

解法1

时间复杂度O(m+n),需要遍历(m+n)/2次

空间复杂度O(1),只需要声明几个常数变量

思路

当两个数组总共的数字个数为偶数的时候,需要获得下标为(m+n-1)/2 和 (m+n)/2位置的数字,然后得到平均值;当两个数组总共的数字个数为奇数的时候,(m+n-1)/2 和 (m+n)/2的结果一样,是在相同的位置。

因此从头开始遍历两个数组,通过index_1 和 index_2来记录下标,每次选择小的数字去右移,index表示总共移动的距离,如果index为(m+n-1)/2 或者是(m+n)/2的位置的时候,就记录当前位置的值。

代码:

var findMedianSortedArrays = function(nums1, nums2) {
    let index = -1, index_1 = 0, index_2 = 0, tmp = 0;
    let target1 = Math.floor((nums1.length + nums2.length - 1)/2);
    let target2 = Math.floor((nums1.length + nums2.length)/2);
    let num = [0, 0];
    let len1 = nums1.length, len2 = nums2.length;
    while(index_1 < len1 || index_2 < len2){
        if(index_1 < len1 && index_2 < len2){
            tmp = nums1[index_1] <= nums2[index_2]? nums1[index_1++]: nums2[index_2++];        
        }else if(index_1 < len1){
            tmp = nums1[index_1++];
        }else{
            tmp = nums2[index_2++];
        }
        index++;
        if(index == target1){
            num[0] = tmp;
        }
        if(index == target2){
            num[1] = tmp;
            break;
        }
    }
    return (num[0]+num[1])/2;
};

解法2

时间复杂度为O(m+n)的解法不难想到,但是题目要求时间复杂度为O(log(m+n)),意味着单纯的从头遍历无法满足时间复杂度的要求,从log(m+n)的复杂度出发,可以想到是通过二分法来进行查找。

基本思路

假设一共是m+n个数字, 寻找中位数, k = (m+n)/2, 即需要找到第k小的数字,。

在O(m+n)的算法中, 每次遍历都是去掉k之前的1个数字;通过二分法的设计,可以实现在每次遍历前都去掉k之前的k/2个数字,因此可以实现log级别的时间复杂度。

因此获得两个数组的k/2位置的数字,来比较大小, 小的数字及其前面的数字, 可以肯定, 他们可以被去掉。然后获得了新的数组长度 = k - 2/k, 将k设置为这个新的长度,然后进行递归调用。

var findMedianSortedArrays = function(nums1, nums2) {
    let n = nums1.length;
    let m = nums2.length;
    let left = Math.floor((n+m+1)/2), right = Math.floor((n+m+2)/2);
    // 获取nums1 和 nums2 的第left个
    return (getKth(nums1, 0, n-1, nums2, 0, m-1, left) + getKth(nums1, 0, n-1, nums2, 0, m-1, right))/2;
};

let getKth = (nums1, start1, end1, nums2, start2, end2, k)=>{
    let len1 = end1 - start1 + 1;  // nums1的长度
    let len2 = end2 - start2 + 1;  // nums2的长度

    // 第一个数组是短的
    if(len1 > len2) return getKth(nums2, start2, end2, nums1, start1, end1, k);
    if(len1 == 0) return nums2[start2 + k - 1];
    // 如果第一个数组的长度是0,直接返回第二个数组的长度

    if(k == 1) return Math.min(nums1[start1], nums2[start2]);
    // 如果长度是1, 就直接返回两个数组里面的最小数字

    let i = start1 + Math.min(len1, Math.floor(k/2)) - 1; 
    let j = start2 + Math.min(len2, Math.floor(k/2)) - 1;
    // 获取k/2的位置

    if(nums1[i] > nums2[j]){
        return getKth(nums1, start1, end1, nums2, j+1, end2, k - (j - start2 + 1));
    }else{
        return getKth(nums1, i+1, end1, nums2, start2, end2, k - (i - start1 + 1));
    }

}