4-median of two sorted arrays

来源:互联网 发布:天谕流光女捏脸数据 编辑:程序博客网 时间:2024/06/08 17:28

难度:hard
类别:divide and conquer

1.问题描述

There are two sorted arrays nums1 and nums2 of size m and n respectively.

Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).

2.测试样例

nums1 = [1, 3]
nums2 = [2]

The median is 2.0

3.问题分析和理解

这道题用到了分治的思想,将大问题分解为小问题,然后进行实现,从而减少了时间。
首先,最基本的想法是将两个数组进行合并然后进行排序,直接取中位数即可,这样实现非常简单,但是时间复杂度超过了log(m+n)。
基本的算法思路:
(1)将两个数组分别进行拆分,假设两个数组分别为nums1和nums2。对于nums1,进行对半分,可以得到:
i = (imin + imax)/2,最开始的时候imin=0, imax = m,
nums1的左半部分为:nums1[imin]、nums1[1]……nums1[i-1]
nums1的右半部分为:nums1[i]、nums1[i+1]……nums1[imax];
同理,j = (m+n+1)/2 - i;
j这样取值刚好保证了nums1的左半部分和nums2的左半部分相加的长度与nums1的右半部分和nums2的右半部分相加的长度相等。从而方便后面直接得到中位数。
(2)对于nums1Left + nums2Left和nums1Right + nums2Right,可能有下面的三种情况。

  1. nums1[i-1] <= nums2[j] && nums2[j-1] <= nums1[i]
    此时得到的i刚好满足nums1Left + nums2Left和nums1Right + nums2Right为两个数组合并后的左右两半部分,从而可以直接得到中位数。但是,在得到中位数的时候,需要注意下标越界的问题,因为涉及到i-1、j-1以及i和j
  2. nums1[i-1] > nums2[j]
    因为要得到nums1[i-1] <= nums2[j],所以需要将i减少,所以接下来[imin, i-1]内寻找合适的下标
  3. nums2[j-1] > nums1[i]
    同样地,因为要得到nums2[j-1] <= nums1[i],所以要讲i增加,所以接下来要在[i+1, imax]中寻找合适的下标,在2和3两种情况下实现了分治,将大问题分解为小问题,从而减少了实现的时间。

4.代码实现

(1)第一种实现方法:采用分治的思想

class Solution {public:    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {        int m = nums1.size();        int n = nums2.size();        if (m > n) {            vector<int> tmp = nums1;            nums1 = nums2;            nums2 = tmp;        }        m = nums1.size();        n = nums2.size();        if (n == 0) {            return 0.0;        }         if (m == 0) {            if ((n % 2) == 0) {                int medianLeft = nums2[n/2 - 1];                int medianRight = nums2[n/2];                return (medianLeft + medianRight)/2.0;            } else {                return nums2[n/2];            }        }        int imin = 0, imax = m;        int i, j;        while (imin <= imax) {            i = (imin + imax)/2;            j = (m+n+1)/2 - i;            if (nums1[i-1] > nums2[j] && i > imin) {                //  here divide the problem into subproblem                imax = i - 1;            } else if (nums2[j-1] > nums1[i] && i < imax) {                imin = i + 1;            } else {                //  find the index i that satisfies, that is, nums1[i-1] <= nums2[j] && nums2[j-1] <= nums1[i]                int maxOne, minOne;                //  注意下标越界问题                 if (i == 0 && j == 0) maxOne = 0;                else if (i == 0) maxOne = nums2[j-1];                else if (j == 0) maxOne = nums1[i-1];                else maxOne = nums1[i-1] > nums2[j-1] ? nums1[i - 1] : nums2[j - 1];                if (i == m && j == n) minOne = 0;                else if (i == m) minOne = nums2[j];                else if (j == n) minOne = nums1[i];                else minOne = nums1[i] < nums2[j] ? nums1[i] : nums2[j];                int isOdd = (m+n)%2;                if (isOdd == 0) {                    return (maxOne + minOne)/2.0;                 } else {                    return maxOne;                }            }        }        return 0.0;    }};

(2)第二种实现方法:使用队列,时间复杂度同样不高,算法的实现相对于第一种方法要简单些

//  在时间的限制上同样是满足的,在leetcode上亲测通过 //  采用队列,每次比较对头的两个数字,将两个数组整合为一个排序的大数组,然后直接得到中位数 #include <iostream>#include <vector>#include <queue>using namespace std;//  return the double value but not the integerdouble findMedian(vector<int> nums1, vector<int> nums2);int main() {    int n, m, data;    vector<int> nums1, nums2;    cin >> m;    cin >> n;    for (int i = 0; i < m; ++i) {        cin >> data;        nums1.push_back(data);    }    for (int i = 0; i < n; ++i) {        cin >> data;        nums2.push_back(data);    }    cout << findMedian(nums1, nums2) << endl;    return 0;}double findMedian(vector<int> nums1, vector<int> nums2) {    int m = nums1.size();    int n = nums2.size();    if (m > n) {        vector<int> tmp = nums1;        nums1 = nums2;        nums2 = tmp;    }    m = nums1.size();    n = nums2.size();    if (n == 0) {        return 0.0;    }     if (m == 0) {        if ((n % 2) == 0) {            int medianLeft = nums2[n/2 - 1];            int medianRight = nums2[n/2];            return (medianLeft + medianRight)/2.0;        } else {            return nums2[n/2];        }    }    queue<int> data1, data2;    for (int i = 0; i < m; ++i) {        data1.push(nums1[i]);    }    for (int i = 0; i < n; ++i) {        data2.push(nums2[i]);    }    vector<int> result;    int i = 0, j = 0;    while (!(data1.empty() && data2.empty())) {        if (!data1.empty() && !data2.empty()) {            if (data1.front() < data2.front()) {                result.push_back(data1.front());                data1.pop();            } else if (data1.front() > data2.front()) {                result.push_back(data2.front());                data2.pop();            } else {                result.push_back(data2.front());                result.push_back(data1.front());                data1.pop();                data2.pop();            }        } else if (data1.empty() && !data2.empty()) {            while (!data2.empty()) {                result.push_back(data2.front());                data2.pop();            }        } else {            while (!data1.empty()) {                result.push_back(data1.front());                data1.pop();            }        }    }    int len = result.size();    if (len % 2 == 1) {        return result[len/2];    } else {        return (result[len/2 - 1] + result[len/2])/2.0;    }    return 0.0;}

注:在实现的过程中有一些小问题需要注意,包括数组下标越界的问题以及当某个数组的元素个数为0时应该如何特殊处理等问题的考虑。另外在分解子问题的时候,既可以将imin转换为imin+1.也可以转换为i + 1;但是转换为i+1可以减少运行的时间。

5.同类问题拓展

下面的题目同样是中位数的问题,但是不是使用分治算法实现的。
题目描述:
分金币问题:
圆桌旁边坐着n个人,每人有一定数量的金币,金币总数能被n整除。每个人可以给他左右相邻的人一些金币,最终使得每个人的金币数目相等。你的任务是求出被转手的金币数量的最小值。按逆时针方向。
样例输入:
3
100 100 100
样例输出:0
样例输入:
4 1 2 5 4
样例输出:4
题目分析:
假设有4个人,按顺序编号为1,2,3,4。假设1号给2号3枚金币,然后2号给1号5枚金币,相当于2号给1号2枚金币,而1号没有给2号。设x2表示的是2号给1号的金币数目,同理,有x1,x3,x4。
M表示的是最终每个人得到的平均金币的数目,即sum/n; A表示的是每个人初始拥有的金币的数目。
对于第一个人:A1 - x1 + x2 = M 推出 x2 = M - A1 + x1 = x1 - C1(C1=A1 - M)
对于第二个人:A2 - x2 + x3 = M 推出 x3 = M - A2 + x2 = 2M - A1 - A2 + x1 = x1 - C2
对于第三个人:A3 - x3 + x4 = M 推出 x4 = x1 - C3
……
要使得所有的xi的绝对值之和尽量小,即|x1| + |x1-C1| + |x2-C2| + … + |x1-Cn-1|要最小,这实际上就是中位数的问题。
代码实现:

//  实际上也是中位数问题 #include <iostream>#include <algorithm> #include <stdio.h>using namespace std;const int maxn = 1000001;long long A[maxn], C[maxn];long long total, average;int main() {    int n;    while (scanf("%d", &n) == 1) {        total = 0;        for (int i = 1; i <= n; i++) {            scanf("%lld", &A[i]);            total += A[i];        }        average = total / n;        C[0] = 0;        for (int i = 1; i < n; ++i) {            C[i] = C[i-1] + A[i] - average;        }        sort(C, C+n);        long long median = C[n/2];        long long result = 0;        for (int i = 0; i < n; ++i) {            result += abs(median - C[i]);        }        cout << result << endl;    }    return 0;} 

总结:

中位数在很多算法题目中都有巧妙的应用,分治算法思想最重要的是找到如何将大问题进行分解的方法,这道题目因为题目要求时间为log(m+n),所以可以从这个角度入手,考虑二分法,从而得到将两个数组分别进行半分然后合并的方法。

原创粉丝点击