您的位置:首页 > 其它

4-median of two sorted arrays

2017-09-13 00:09 453 查看
难度: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,可能有下面的三种情况。

nums1[i-1] <= nums2[j] && nums2[j-1] <= nums1[i]

此时得到的i刚好满足nums1Left + nums2Left和nums1Right + nums2Right为两个数组合并后的左右两半部分,从而可以直接得到中位数。但是,在得到中位数的时候,需要注意下标越界的问题,因为涉及到i-1、j-1以及i和j

nums1[i-1] > nums2[j]

因为要得到nums1[i-1] <= nums2[j],所以需要将i减少,所以接下来[imin, i-1]内寻找合适的下标

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 integer
double 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),所以可以从这个角度入手,考虑二分法,从而得到将两个数组分别进行半分然后合并的方法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  median leetcode