Bootstrap

C++算法实践04-寻找两个正序数组的中位数

一、题目:

给定两个大小分别为 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

二、解答:

1. 暴力破解但是算法的时间复杂度为O(m+n)。

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) 
    {
        int m = nums1.size();
        int n = nums2.size();
        vector<int>::iterator it1 = nums2.begin();
        //合并两个数组
        while(it1 != nums2.end())
        {
            //nums1.resize(m+n);
            nums1.push_back(*it1);
            it1++;
        }
        //对合并完成的数组进行排序
        sort(nums1.begin(),nums1.end());
        //偶数情况
        if((m+n)%2 == 0)
        {
            vector<int>::iterator pose1 = nums1.begin();
            int count = (m+n) / 2 - 1;
            pose1 += count;
            double avg = (*pose1 + *(pose1 + 1)) / 2.0;
            return (avg > 0.0) ? avg : 0.00000;
        }
        //奇数情况
        else
        {
            vector<int>::iterator pose2 = nums1.begin();
            int count = (m+n-1)/2;
            pose2 += count;
            return (double)*pose2;
        }
    }
};

2.利用二分法来缩减时间复杂度。

解题思路:

首先这是两个正序数组,我们想要找到中位数也就等价于找到第k小的数。

那么两个数组也是一样的思想,想要找到两个数组合并后的中位数也就等价于找到第k小的数。

假设两个数组的长度分别为m和n,那么中位数也就是第(m+n+1)/2小的数。当两个数组的长度之和为奇数时,我们只需要找到一个第k小的,它的值就是中位数的值。二当两个数组的长度之和为偶数时,那我们就需要找到第k小的数和第k+1小的数,把他们的和除以二就是中位数的值。这个算法的核心思路就是getkth函数,采用递归的思想去不断排除元素从而逼近中位数,递归的终止条件为找到第1小的数。

假设第一个数组为1、3、4、9

第二个数组为1、2、3、4、5、6、7、8、9、10

那么根据k = (m+n+1)/2 = 7

所以我们要找第7小的数。每次都取两个数组的前k/2个,也就是前三个。通过对比两个数组的前三个我们发现4是大于3的,也就是说第二个数组的3是不可能成为第7小的,那么由于数组时正序的,第二个数组的前三个元素就可以删除,从而减少了k/2个元素。

去除了第二个数组的前三个元素后任务也会发生改变,从原来的寻找第7小变成了(7-3=4)第4小的数,那么这一步需要对比的数位4/2 = 2。将两个数组的前两个数中最大的数进行对比,发现5是大于3的,那么第一个数组的前两个数不可能是第4小的数,就可以将第一个数组的前两个数删除。

去除了第一个数组的前两个元素后任务也会发生改变,从原来的寻找第4小变成了(4-2=2)第2小的数,那么这一步需要对比的数位2/2 =1。但此时出现了两个都是4,我们假设第一个的4大于第二个的4,将第二个数组的4删除。

最终我们需要找到第一小的数,也就是来到了递归的判定点,我们只需要取出两个数组的第一个元素进行比对,谁小那么他就是我们要寻找的中位数。

代码:

class Solution {
public:
    double findMedianSortedArrays(std::vector<int>& nums1, std::vector<int>& nums2) {
        int n = nums1.size();
        int m = nums2.size();
        //当n+m为偶数时,left=right
        //当n+m为奇数时,left+1=right
        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)) * 0.5;
    }
    
    int getKth(std::vector<int>& nums1, int start1, int end1, std::vector<int>& nums2, int start2, int end2, int k) {
        int len1 = end1 - start1 + 1;
        int len2 = end2 - start2 + 1;
        //当第一个数组的长度大于第二个数组的长度,将两个数组进行交换为了后续的运算
        if (len1 > len2) return getKth(nums2, start2, end2, nums1, start1, end1, k);
        //当第一个数组为空时,只需要返回第二个数组中第start2 + k - 1小的数
        if (len1 == 0) return nums2[start2 + k - 1];
        //递归的终止条件
        if (k == 1) return std::min(nums1[start1], nums2[start2]);
        
        int i = start1 + std::min(len1, k / 2) - 1;
        int j = start2 + std::min(len2, k / 2) - 1;
        
        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));
        }
    }
};

;