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

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

编号:0018

试题来源:leetcode

试题描述

给定两个大小为 m m m n n n正序(从小大大)数组nums1nums2。请找出这两个正序数组的中位数,并要求算法的算法的时间复杂度为 O ( l o g ( m + n ) ) O(log(m+n)) O(log(m+n))

假设num1nums2不会同时为空

解答算法

暴力解法

算法思路

没做出来这道题,用的是两个有序数组的归并排序,合并的时间复杂度为 O ( m + n − 1 ) O(m+n-1) O(m+n1)

暴力解法的思路就是将两个数组合并到一个数组中,因为我只关心中位数,所以当合并到 m + n 2 \frac{m+n}{2} 2m+n以及 m + n 2 + 1 \frac{m+n}{2} + 1 2m+n+1这两个数就结束,同时我也不关心合并后的数组到底是怎样,因此我只需要设置两个变量mid[2]来存储中间的两个数就好了。

如果m+n是奇数,那么中位数就为 m + n 2 + 1 \frac{m+n}{2} + 1 2m+n+1,如果是偶数,那么就是那两个数的平均数。

代码实现

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int sumSize = nums1.size() + nums2.size(), nums1Cur = 0, nums2Cur = 0, nums = 0;  //sumSize存储m+n,nums1Cur、nums2Cur存储当前遍历两个数组的位置,nums用来存储已经放到合并后数组的数目
        int mid[2] = {};  //mid用来存放中间的两个树
        while (nums1Cur < nums1.size() || nums2Cur < nums2.size())   //只要这两个数组没有都遍历到最后一个
        {
            nums++;  //每一次遍历存放的数都自增
            if (nums1Cur == nums1.size() || (nums2Cur != nums2.size() && nums1[nums1Cur] > nums2[nums2Cur])) //nums1遍历完,或者nums2当前数值更小
            {
                if (nums == sumSize / 2)    //如果当前是中间两个,那么存储进mid中
                    mid[0] = nums2[nums2Cur];
                if (nums == sumSize / 2 + 1)
                    mid[1] = nums2[nums2Cur];
                nums2Cur++;

            }
            else
            {
                if (nums == sumSize / 2)
                    mid[0] = nums1[nums1Cur];
                if (nums == sumSize / 2 + 1)
                    mid[1] = nums1[nums1Cur];
                nums1Cur++;
            }
        }
        if (sumSize & 1)     //奇数返回中间的
            return mid[1];
        else   //偶数返回中间两个的平均数
            return ((double)mid[1] + mid[0]) / 2;
    }
};

复杂度分析

  • 时间复杂度:整个过程中对两个数组都进行了遍历,因此总的时间复杂度为 O ( m + n ) O(m+n) O(m+n)。当然可以进行一定的优化,如在找到中间的两个数后,退出遍历。
  • 空间复杂度:整个过程只申请了常数的空间,因此空间复杂度为 O ( 1 ) O(1) O(1)

二分查找算法

算法思路

我们要寻找中位数,也就是寻找合并之后的数组中的第 m + n 2 \frac{m+n}{2} 2m+n小和第 m + n 2 + 1 \frac{m+n}{2}+1 2m+n+1小的,如果是奇数,只用后者,如果是偶数,那么要用到两者的平均数。因此这个问题可以变成求解数组中第 k k k小的数字。

如果我们想要求第 k k k个,那么可以找到 A [ k 2 − 1 ] A[\frac{k}{2} -1] A[2k1] B [ k 2 − 1 ] B[\frac{k}{2} -1] B[2k1],这两者前面分别由 k 2 − 1 \frac{k}{2}-1 2k1个数,因此总共有 2 ∗ ( k 2 − 1 ) < = k − 2 2*(\frac{k}{2} - 1) <= k -2 2(2k1)<=k2,因此如果 A [ k 2 − 1 ] < B [ k 2 − 1 ] A[\frac{k}{2} -1] < B[\frac{k}{2}-1] A[2k1]<B[2k1],那么前者最多是第 k − 1 k-1 k1小的数,因此 A [ 0 … k 2 − 1 ] A[0\dotso \frac{k}{2}-1] A[02k1]全部不可能是第 k k k小的数。

然后我们就得到了新的要比较的两个数组,我们已经排除了 k 2 \frac{k}{2} 2k个数,因此新的比较数目就是 k − k 2 k - \frac{k}{2} k2k

然后就相当于对新的数组继续这样的步骤

代码实现

class Solution {
public:
    int getKthElement(const vector<int>& nums1, const vector<int>& nums2, int k) {   //得到第k小的数字
        /* 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
         * 这里的 "/" 表示整除
         * nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个
         * nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个
         * 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个
         * 这样 pivot 本身最大也只能是第 k-1 小的元素
         * 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组
         * 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组
         * 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数
         */

        int m = nums1.size();   //得到量数组长度
        int n = nums2.size();
        int index1 = 0, index2 = 0;  //偏移坐标

        while (true) {
            // 边界情况
            if (index1 == m) {   //如果一个到达边界,那么直接取另一个中的数字就好
                return nums2[index2 + k - 1];
            }
            if (index2 == n) {
                return nums1[index1 + k - 1];
            }
            if (k == 1) {   //如果最小。那么只用前两个比较一下就行
                return min(nums1[index1], nums2[index2]);
            }

            // 正常情况
            int newIndex1 = min(index1 + k / 2 - 1, m - 1);  //新的坐标应该为原来的偏转坐标加上排除的数字,同时要注意不能越界
            int newIndex2 = min(index2 + k / 2 - 1, n - 1);
            int pivot1 = nums1[newIndex1];  //比较两个中间数的大小
            int pivot2 = nums2[newIndex2];
            if (pivot1 <= pivot2) {   //更新k,也就是排除掉了一部分
                k -= newIndex1 - index1 + 1;
                index1 = newIndex1 + 1;   //更新偏转
            }
            else {   //同上,情况不同
                k -= newIndex2 - index2 + 1;
                index2 = newIndex2 + 1;
            }
        }
    }

    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int totalLength = nums1.size() + nums2.size();
        if (totalLength % 2 == 1) {
            return getKthElement(nums1, nums2, (totalLength + 1) / 2);
        }
        else {
            return (getKthElement(nums1, nums2, totalLength / 2) + getKthElement(nums1, nums2, totalLength / 2 + 1)) / 2.0;
        }
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值