题目: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)).
有两个排好序的数组nums1和nums2,大小分别为m和n。现在要找到两个数组合并之后的中位数,
要求时间复杂度是O(log (m+n)).
思路一:最简单的方法首先想到的是直接合并两个数组,然后排序,直接就能取出中位数。
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int m = nums1.size();
int n = nums2.size();
vector<int> vec;
vec.insert(vec.end(), nums1.begin(), nums1.end());
vec.insert(vec.end(), nums2.begin(), nums2.end());
sort(vec.begin(), vec.begin() + m + n);
int k = (m + n) / 2;
if ((m + n) % 2 == 0)
return (vec.at(k-1) + vec.at(k)) / 2.0;
else
return vec.at(k);
}
};
思路二:看题目要求寻找的是中位数,百度百科定义的中位数如下
中位数是按顺序排列在一起的一组数据中居于中间位置的数,即在这组数据中,有一半的数据比他大,有一半的数据比他小。
注意最后一句话,有一半的数据比他小,从这个点出发可以从头开始数一半的数,就找到中位数了。
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int m = nums1.size();
int n = nums2.size();
int k = (m + n) / 2;
if ((m + n) % 2)
return findK(nums1, nums2, m,n,k);
else
return (findK(nums1, nums2, m, n, k-1) + findK(nums1, nums2, m, n, k)) / 2.0;
}
int findK(vector<int>& nums1, vector<int>& nums2, int m, int n, int k)
{
int p1 = 0;
int p2 = 0;
int p = 0;
while (p < k)
{
if (p1 >= m && p2 < n)
p2++;
else if (p2 >= n && p1 < m)
p1++;
else if (nums1[p1] < nums2[p2])
p1++;
else
p2++;
p++;
}
if (p1 >= m && p2 < n)
return nums2[p2];
else if (p2 >= n && p1 < m)
return nums1[p1];
else if (nums1[p1] < nums2[p2])
return nums1[p1];
else
return nums2[p2];
}
};
思路三:其实第二种解法给了我们启发,找一半的数的时候,两两比较两个数组最小的元素,就是在做归并排序。经过老师课上反复强调,突然就领悟到这道题可以用分治的解法。我们最终的目标是找中间的数,已知两个数组的大小分别为m和n,那么就是找排在k=(m+n)/2的数,在大小为2K的区间 内找到第k个数的任务可以变成在大小为K的区间内找到第K/2个数的任务,以此类推,在大小为1的区间内找到的数就是自身。但是这道题有两个数组,并没有合并成一个数组,不能每次都很准确的去掉K/2的区间,因此一次分治只能对某一个数组去除前K/2个数,另一个数组不动。分别找到两个数组排在第K/2的数(注意不要超过数组的大小),比较大小,去掉小的那个数组前面的K/2个数。这样数组的区间不断减小,要找的第K个数也离数组的头越来越近。大约经过O(log(m+n))次操作,就能找到第K个数。
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int a = nums1.size();
int b = nums2.size();
int k = (a + b) / 2;
if ((a + b) % 2) //总大小为奇数的时候,中位数只有一个
return findK(nums1, nums2, 0, 0, a, b, k+1);
else//总大小为偶数的时候,中位数是中间两个数的平均数
return (findK(nums1, nums2, 0, 0, a, b, k) + findK(nums1, nums2, 0, 0, a, b, k + 1)) / 2;
}
double findK(vector<int>& nums1, vector<int>& nums2, int start1, int start2, int size1, int size2, int k)
{
if (size1 == 0)//特殊情况处理
return nums2[start2 + k - 1];
if (size2 == 0)//特殊情况处理
return nums1[start1 + k - 1];
if (k == 1)//当K=1的时候,要找到数就在数组的头
return min(nums1[start1], nums2[start2]);
int step1, step2;
if (size1 < size2)//分别找到两个数组K/2位置的数(注意边界处理)
{
step1 = min(size1, k / 2);
step2 = k-step1;
}
else
{
step2 = min(size2, k / 2);
step1 = k - step2;
}
if (nums1[start1 + step1 - 1] < nums2[start2 + step2 - 1])//去除一个数组的一部分,变成新的子任务
return findK(nums1, nums2, start1 + step1, start2, size1 - step1, size2, k - step1);
else
return findK(nums1, nums2, start1, start2 + step2, size1, size2 - step2, k - step2);
}
};
可以看出,代码非常简洁,而且效率也很高。在最好情况下,每次都有k一半的元素被删除,所以算法复杂度为logk,由于求中位数时k为(m+n)/2,所以算法复杂度为log(m+n)。