LeetCode #4 寻找两个正序数组的中位数

本文详细介绍了如何寻找两个正序数组的中位数,包括合并方法、双指针法、二分法和分割法。通过各种方法的实现,探讨了各自的时间复杂度和空间复杂度,提供了代码示例和优化思路。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2

https://leetcode-cn.com/problems/median-of-two-sorted-arrays/

一、合并方法 

最简单的方法就是将两个数组合并到另一个数组当中,再在这个数组中找到中位数即可。使用for循环将两个数组合并的方法考虑的情况较复杂 ,可以直接使用while循环将其中一个数组合并,那么,接着再使用一个for循环将另一个数组中剩余的量存入即可。该方法空间复杂度为O(m+n),时间复杂度也是一样O(m+n)。


代码:

double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
    const int m = nums1.size(), n = nums2.size(), half = (n + m) >> 1;
    int n1 = 0, n2 = 0;
    vector<int> res(n + m);

    for (int k = 0, i = 0, j = 0; k < res.size(); ++k)
    {
        if (i < m && j < n)
        {
            n1 = nums1[i];
            n2 = nums2[j];

            if (n1 > n2)
            {
                res[k] = n2;
                if (j < n)
                    j++;
            }
            else if (n1 <= n2)
            {
                res[k] = n1;
                if (i < m)
                    i++;
            }
            continue;
        }

        if (n == j)
        {
            res[k] = nums1[i];
            i++;
        }
        else if (m == i)
        {
            res[k] = nums2[j];
            j++;
        }
    }

    if ((n + m) % 2 == 0)
    {
        return (res[half - 1] + res[half]) / 2.0f;
    }
    
    return res[half];
}

代码优化: 

double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
    const int m = nums1.size(), n = nums2.size(), half = (n + m) >> 1;
    int i = 0, j = 0, k = 0;
    vector<int> res(n + m);

    while (i < m && j < n)
    {
        res[k++] = nums1[i] > nums2[j] ? nums2[j++] : nums1[i++];
    }

    while (i < m)
    {
        res[k++] = nums1[i++];
    }

    while (j < n)
    {
        res[k++] = nums2[j++];
    }

    if ((n + m) % 2 == 0)
    {
        return (res[half - 1] + res[half]) / 2.0f;
    }
    
    return res[half];
}

二、双指针法 

使用两个"指针"分别指向两个数组,比较指向的值的大小,值小的指针向后移动一位,如此循环。何时停止这个问题至关重要,我们需要得到中位数,那么移动的次数只要和中位数所在的位置一致即可。当然,中位数还有数组长度是偶数还是奇数的情况。当是奇数时,那么中位数就是中间位置的数,那么循环len/2次就是中位数;若是偶数,那么需要记录前一个值,所以整个空间复杂度为O(1)。


代码:

double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
    const int n1 = nums1.size(), n2 = nums2.size(), len = n1 + n2;
    int lastVal = 0, curVal = 0;
    int p1 = 0, p2 = 0;

    for (int k = 0; k <= len / 2; k++)
    {
        lastVal = curVal;

        if (p1 < n1 && p2 < n2)
        {
            if (nums1[p1] < nums2[p2])
            {
                curVal = nums1[p1];
                p1++;
            }
            else
            {
                curVal = nums2[p2];
                p2++;
            }
            continue;
        }

        if (p1 == n1 && p2 < n2)
        {
            curVal = nums2[p2];
            p2++;
        }

        if (p2 == n2 && p1 < n1)
        {
            curVal = nums1[p1];
            p1++;
        }
    }

    return len % 2 == 0 ? (curVal + lastVal) / 2.0f : curVal;
}

代码优化,这里直接将p2 == n2 && p1 < n1的情况和p1 < n1 && p2 < n2进行合并,同时将其与nums1[p1] < nums2[p2]"或"在一起,这样即p2>=n2也不会执行后面大小比较导致越界了:

double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
    const int n1 = nums1.size(), n2 = nums2.size(), len = n1 + n2;
    int lastVal = 0, curVal = 0;
    int p1 = 0, p2 = 0;

    for (int k = 0; k <= len / 2; k++)
    {
        lastVal = curVal;

        if (p1 < n1 && (p2 >= n2 || nums1[p1] < nums2[p2]))
        {
            curVal = nums1[p1];
            p1++;
        }
        else
        {
            curVal = nums2[p2];
            p2++;
        }
    }

    return len % 2 == 0 ? (curVal + lastVal) / 2.0f : curVal;
}

三、二分法

由于是有序的两个数组那么自然就可以使用二分法进行解决。每次两个数组中一个位移K/2个位置直至抵达返回条件。

https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/fen-zhi-jie-fa-duo-bu-zou-jian-dan-xiang-1e55/


递归方法代码:

int getKthElement(int k, int s1, int s2, const vector<int>& nums1, const vector<int>& nums2)
{
    const int n1 = nums1.size(), n2 = nums2.size();
    if (s1 >= n1)
    {
        return nums2[s2 + k - 1];
    }
    else if (s2 >= n2)
    {
        return nums1[s1 + k - 1];
    }

    if (k == 1)
    {
        return std::min(nums1[s1], nums2[s2]);
    }

    int preLen1 = std::min(k / 2, n1 - s1);
    int preLen2 = std::min(k / 2, n2 - s2);
    if (nums1[s1 + preLen1 - 1] < nums2[s2 + preLen2 - 1])
    { 
        return getKthElement(k - preLen1, s1 + preLen1, s2, nums1, nums2);
    }
    else
    {
        return getKthElement(k - preLen2, s1, s2 + preLen2, nums1, nums2);
    }
}


double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
    const int len = nums1.size() + nums2.size();
    if (len % 2 == 1)
    {
        return getKthElement((len + 1) / 2, 0, 0, nums1, nums2);
    }
    else
    {
        return (getKthElement(len / 2, 0, 0, nums1, nums2) + getKthElement(len / 2 + 1, 0, 0, nums1, nums2)) / 2.0f;
    }
}

使用while循环代码(建议使用该方法):

int getKthElement(int k, const vector<int>& nums1, const vector<int>& nums2)
{
    const int len1 = nums1.size(), len2 = nums2.size();
    int i = 0, j = 0;
    while(true) {
        if(i == len1) { // 边界条件是需要我们时时刻刻都需要注意的
            return nums2[j + k - 1];
        }

        if(j == len2) {
            return nums1[i + k - 1];
        }
        // k==1不在while()中的原因是当i==len1或者j==len2时会直接超出数组范围
        if(k == 1) {
            break;
        }
        // 防止每次向后移位的索引超出数组的范围
        int idx1 = std::min(i + k/2 - 1, len1 - 1);
        int idx2 = std::min(j + k/2 - 1, len2 - 1);
        if(nums1[idx1] < nums2[idx2]) { // 如果该条件成立说明idx1代表的k/2个数是可以丢弃的,因为第K个数不在其中
            k -= idx1 - i + 1; // k在前的原因是在i+=k/2前将k-=k/2,防止多减
            i = idx1 + 1; // 通常情况下是i+=k/2,当索引到达最后一位时idx1+1就恒为数组长度
        }
        else {
            k -= idx2 - j + 1;
            j = idx2 + 1;
        }
    }

    return std::min(nums1[i], nums2[j]);
}

double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
    const int len1 = nums1.size(), len2 = nums2.size();
    int k = (len1 + len2) / 2 + 1;
    return (len1 + len2) % 2 == 0 ? (getKthElement(k, nums1, nums2) + getKthElement(k - 1, nums1, nums2)) / 2.f : getKthElement(k, nums1, nums2);
    // 或者采取以下方法,虽然会使得函数看起来简单,但实际上增加了计算次数
    // int k1 = (len1 + len2 + 1) / 2, k2 = (len1 + len2 + 2) / 2;
    // return (getKthElement(k1, nums1, nums2) + getKthElement(k2, nums1, nums2)) / 2.f;
}

进行优化(需要多加理解):

// https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/jiang-qi-zhuan-wei-zhao-liang-ge-you-xu-shu-zu-de-/
// https://leetcode-cn.com/problems/search-insert-position/solution/te-bie-hao-yong-de-er-fen-cha-fa-fa-mo-ban-python-/
int getKthElement(int k, const vector<int>& nums1, const vector<int>& nums2)
{
    const int len1 = nums1.size(), len2 = nums2.size();
    // 找到两个数组第K个小的数,需要满足以下两个条件,注意这里的i和j表示的是第i个数和第j个数:
    // 1、i + j = k
    // 2、nums1[i-1] <= nums2[j] && nums2[j-1] <= nums1[i]
    // 对于条件2,因为原本nums1[i-1]就小于nums1[i],所以这里就没有加上,
    // 而且两个数组的左边总共有k个数小于右边,这就意味着第k个小的数就是
    // max(nums1[i-1], nums2[j-1])。那么以此为条件可以得到i的范围,注意i=0表示数组为空:
    // 1、0 <= i <= len1
    // 2、0 <= j <= len2 && i + j = k ==> k - len2 <= i <= k 
    // 综合起来就是max(0, k - len2) <= i <= min(len1, k)
    int l = std::max(0, k - len2), r = std::min(len1, k);
    while(l < r) {
        int m = (l + r) / 2;
        // 只要条件2中有一个不等式不成立,就意味着这不是第K小数,继续移动
        // 左边的指针,注意这里满足该不等式的肯定不满足条件,因此直接+1移动,
        // 直到满足nums2[k-m-1] <= nums1[m]。当满足该条件下,持续左移右边
        // 的指针,注意这里的m是可能为正确值的,因此不能+1而是直接赋值
        if(nums1[m] < nums2[k-m-1]) {
            l = m + 1;
        }
        else {
            r = m;
        }
        // 为什么条件2中的只要其中一条满足即可?因为当i递增时,
        // nums1[i-1]递增,nums2[j]递减,一定存在一个最大的值i满足:
        // nums1[i-1] <= nums2[j],则此时i+1必定不满足条件,代入即
        // nums1[i+1-1] > nums2[j-1] ==> nums1[i] > nums2[j-1],这个
        // 其实就是条件2的第二个不等式,所以二者是等同的
    }
    // 跳出while时l==r,由于是按照nums2[j-1] <= nums1[i]该条件进行查找的,
    // 那么此时i的值为l,表示第k小数为nums1[l-1]和nums2[k-l-1]这两个数的其
    // 中一个,取二者其最大值,注意这里的l的范围是0~k,因此:
    int cond1 = 0 == l? INT_MIN : nums1[l-1];
    int cond2 = k == l? INT_MIN : nums2[k-l-1];
    return std::max(cond1, cond2);
}

double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
    const int len1 = nums1.size(), len2 = nums2.size();
    int k = (len1 + len2) / 2 + 1;
    return (len1 + len2) % 2 == 0 ? (getKthElement(k, nums1, nums2) + getKthElement(k - 1, nums1, nums2)) / 2.f : getKthElement(k, nums1, nums2);
    // 或者采取以下方法,虽然会使得函数看起来简单,但实际上增加了计算次数
    // int k1 = (len1 + len2 + 1) / 2, k2 = (len1 + len2 + 2) / 2;
    // return (getKthElement(k1, nums1, nums2) + getKthElement(k2, nums1, nums2)) / 2.f;
}

 四、分割法

每次在两个数组上切一刀,分别比较这刀左右两边大小。若左边大于右边说明还未抵达正确位置,左边的位置++,直至找到左边小于右边的位置即是中位数的位置。

https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/xun-zhao-liang-ge-you-xu-shu-zu-de-zhong-wei-s-114/

代码:

double getMedianValue(vector<int>& nums1, vector<int>& nums2)
{
    const int n1 = nums1.size(), n2 = nums2.size();
    int left = 0, right = n1, mid1 = 0, mid2 = 0;

    while (left <= right)
    {
        int i = (left + right) / 2;
        int j = (n1 + n2 + 1) / 2 - i;

        int lmaxi = i == 0 ? INT_MIN : nums1[i - 1];
        int rmaxi = i == n1 ? INT_MAX : nums1[i];
        int lmaxj = j == 0 ? INT_MIN : nums2[j - 1];
        int rmaxj = j == n2 ? INT_MAX : nums2[j];

        if (lmaxj > rmaxi)
        {
            left = i + 1;
        }
        else if (lmaxi > rmaxj)
        {
            right = i - 1;
        }
        else
        {
            mid1 = std::max(lmaxi, lmaxj);
            mid2 = std::min(rmaxi, rmaxj);
            break;
        }
    }

    return (n1 + n2) % 2 == 0 ? (mid1 + mid2) / 2.0f : mid1;
}

double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
    return nums1.size() > nums2.size() ? getMedianValue(nums2, nums1) : getMedianValue(nums1, nums2);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值