[H递归] lc4. 寻找两个正序数组的中位数(递归+边界情况)

1. 题目来源

链接:lc4. 寻找两个正序数组的中位数

2. 题目解析

方法一:递归+边界情况

两个有序数组求中位数。其实,本题还可以拓展为两个有序数组求第 k 小的数。排序、二路归并都稍稍靠边站。采用递归能做到 O ( l o g ( n + m ) ) O(log(n +m)) O(log(n+m)) 的复杂度。思路如下:

  • 首先在两个数组中各取前 k 2 \frac k 2 2k 个元素,记第一个数组为 A,第二个数组为 B。两个数组中的这两个元素,即有三种大小关系,即:
    • A[k/2] = B[k/2],很好理解,A[k/2]B[k/2] 都是可以作为第 k 大数的。
    • A[k/2] > B[k/2]B[k/2] 前面的这 k/2 个数字不可能成为第 k 小的数。因为 B[k/2] 最多只贡献 k/2 个,而 A[k/2] 却没贡献满 k/2 个,故还凑不齐 k 个数,第 k 小的数只能是 B 数组第后半段或者是 A 数组中的某个元素。则,可以将 B[k/2] 前面这些部分舍去。则问题变成子问题为:从 A[1, n],B[k/2 + 1, m] 这两个有序数组中寻找第 k - k / 2 小的数。
    • A[k/2] < B[k/2],同理可得。去掉 A[k/2] 这段元素。

所以,就可以拿递归来做。也很容易发现,每次我们都会去除 k/2 个元素。当在求中位数时,即 k=(m+n)/2,则整个时间复杂度就是 O ( l o g ( m + n ) ) O(log(m+n)) O(log(m+n)) 的。

边界问题就是每次在划去 k/2 元素后,是否可能导致越界的问题?这个可以将 A 数组定义为较短数组,B 数组定义为较长数组。由于第一个数组比较短,可能会加 k/2 后导致其越界。故每次更新区间的时候需要和数组元素个数取 min 保证其合法。且由于第二个数组长,则一定不会越界。因为 k=(n+m)/2sisj 不可能同时小于 k/2

很难的一道题…

时间复杂度: O ( l o g ( m + n ) ) O(log(m+n)) O(log(m+n))
空间复杂度: O ( 1 ) O(1) O(1)

代码:

class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int tot = nums1.size() + nums2.size();
        if (tot % 2 == 0) {
            int l = find(nums1, 0, nums2, 0, tot / 2);
            int r = find(nums1, 0, nums2, 0, tot / 2 + 1);
            return (r + l) / 2.0;
        } else {
            return find(nums1, 0, nums2, 0, tot / 2 + 1);
        }
    }

    // 从i到nums1最后一个数,从j到nums2最后一个数,找第k小的数
    int find(vector<int>& nums1, int i, vector<int>& nums2, int j, int k) {
        // 为了方便边界情况判断,假定第一个数组比较短
        // 如果第一个数组比较长的话,就先处理第二个数组。交换一下
        if (nums1.size() - i > nums2.size() - j) return find(nums2, j, nums1, i, k);
        if (k == 1) {       							// 每次都是k/2,则k最终一定可以等于1。若k==1即仅有一个数了
            if (nums1.size() == i) return nums2[j];     // 在此由于nums1比较短,考虑其边界情况。如果i越界,且k为1,则直接输出nums[j]
            else return min(nums1[i], nums2[j]);
        }
        // 如果nums1为空,那么直接返回第二个数组的第k个数,
        // 在此k从1开始,当k=1时应当返回nums2[j],所以需要减一
        if (nums1.size() == i) return nums2[j + k - 1];

        // 递归主体
        // si sj 分别为nums1, nums2数组中第k/2个数据的下一个位置,用以更新i, j位置
        // 由于第一个数组比较短,可能会加k/2后导致其越界。
        // 且由于第二个数组长,则一定不会越界。因为 k=(n+m)/2,则si和sj不可能同时小于k/2
        int si = min((int)nums1.size(), i + k / 2), sj = j + k - k / 2;
        if (nums1[si - 1] > nums2[sj - 1]) return find(nums1, i, nums2, sj, k - (sj - j));	// 写成k/2也行
        else return find(nums1, si, nums2, j, k - (si - i)); 	// 必须写成 k-(si-i)
    }
};


class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int tot = nums1.size() + nums2.size();
        if (tot % 2 == 0) {
            int left = find(nums1, 0, nums2, 0, tot / 2);
            int right = find(nums1, 0, nums2, 0, tot / 2 + 1);
            return (left + right) / 2.0;
        }
        else return find(nums1, 0, nums2, 0, tot / 2 + 1);
    }

    int find(vector<int> &nums1, int i, vector<int> &nums2, int j, int k) {
        if (nums1.size() - i > nums2.size() - j) return find(nums2, j, nums1, i, k);
        if (k == 1) {
            if (nums1.size() == i) return nums2[j];
            else return min(nums1[i], nums2[j]);
        }
        if (nums1.size() == i) return nums2[j + k - 1];
        int si = min((int)nums1.size(), i + k / 2), sj = j + k - k / 2;
        if (nums1[si - 1] > nums2[sj - 1]) return find(nums1, i, nums2, sj, k - (sj - j));
        else return find(nums1, si, nums2, j, k - (si - i));
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值