声明:题目解法使用c++和Python两种,重点侧重在于解题思路和如何将c++代码转换为python代码。
Median of Two Sorted Arrays
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)).
Example 1:
nums1 = [1, 3]
nums2 = [2]
The median is 2.0
Example 2:
nums1 = [1, 2]
nums2 = [3, 4]
The median is (2 + 3)/2 = 2.5
题意
给你两个有序数组,让你找出两数组并集后的中位数。要求时间复杂度为O(log(m+n))
思路一(最笨的方法) :合并数组+sort排序
这是最笨的方法,先把数组合并为一个数组,就需要O(n+m)的时间,然后再sort排序,首先我们知道,C++中的sort()则是改进的快排算法,时间复杂度是nlog(n),对于我们题意来说则是(n+m)log(n+m)。代码很简单就不写了。
思路二:归并排序(加优化)
从题意中我们可以得到的信息为:有序的数组,想要得出中位数,肯定要把两个有序的数组合成一个有序的数组。这时候很容易想到归并排序(Merge Sort)的定义:
- 归并:将两个有序数列合并成一个有序数列。
- 归并排序:利用归并思想对数列进行排序。
归并排序的时间复杂度为:O(n)
考虑题目中要求的是中位数,我们只需归并到第(n+m)/2个元素。
时间复杂度为O((n+m)/2)。
归并排序代码:
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
//归并排序
int m = nums1.size(), n = nums2.size(),mid;
int i = 0, j = 0, k = 0;//合并后的数组
mid =(m+n)/2;
vector<int>res(m+n+10,0);
while(i<m || j<n)
{
if(i==m)
{
res[k++] = nums2[j];
j++;
}
else if(j==n)
{
res[k++] = nums1[i];
i++;
}
else if(nums1[i]<=nums2[j])
{
res[k++] = nums1[i];
i++;
}
else if(nums1[i]>nums2[j])
{
res[k++] = nums2[j];
j++;
}
//遍历到数组的m+n/2的
if(k>mid)
break;
}
if((m+n)&1)
return (res[mid-1]+res[mid])*1.0/2;
else
return res[mid];
}
};
虽然在leetcode上提交成功了,但是还是没有达到题目的要求的时间复杂度O(log(m+n))。(怪不得第一道题目我暴力也能过,果然leetcode里面的时间复杂度卡的不严),
思路三:第k小元素(二分的思想)
最近在网上看到了一种非常好的方法:
这里将问题化为一般性:如何找到两个有序数组中第k大的元素?
首先要知道二分查找的基本思想:
二分查找的基本思想是将n个元素分成大致相等的两部分,去a[n/2]与x做比较,如果x=a[n/2],则找到x,算法中止;如果x
大意:
假设A和B是两个元素个数都超过k/2的数组,如果我们要比较A和B数组中的第k/2个元素的值,则会得出三种结果(因为k可能是偶数或者奇数,在这里为了简化问题,我们假设是奇数):
A[K/2-1] > B[K/2-1]
A[K/2-1] < B[K/2-1]
A[K/2-1] = B[K/2-1]
如果 A[k/2-1] < B[k/2-1],意味着从A[0]~A[k/2-1]都小于A和B数组合并后的第k小数,换句话说A[k/2-1]不可能大于两数组合并后的第k小数。
为什么呢?
我们可以用反证法证明:假设A[k/2-1]大于合并后的第k小数,就假设是第k+1小数,因为A[k/2-1]
我们也应该考虑边界的问题:
- A与B其中一个为空,返回A[k-1]或者B[k-1]。
- k=1时(A和B都不为空),返回min(A[0],B[0])。
- A[k/2-1]=B[k/2-1],返回其中一个。
实现的代码:
class Solution {
public:
double findkth(vector<int>::iterator a, int m, vector<int>::iterator b, int n, int k)
{
//始终保持a数组的元素个数最少
if(m > n)
return findkth(b, n, a, m, k);
//其中一个为0
if(m == 0)
return b[k-1];
//元素都为1
if(k == 1)
return min(*a, *b);
//因为a数组是元素少的数组,元素可能没有k/2个,所以不能用
//int pa = k / 2 可能导致数组溢出
int pa = min(k/2, m), pb = k-pa;
//丢弃a
if(*(a + pa - 1) < *(b + pb-1))
return findkth(a + pa, m - pa, b, n, k-pa);
//丢弃b
else if(*(a + pa-1) > *(b + pb - 1))
return findkth(a, m, b + pb, n - pb, k - pb);
//相等直接返回
else
return *(a + pa-1);
}
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
vector<int>::iterator a = nums1.begin();
vector<int>::iterator b = nums2.begin();
int m = nums1.size(), n = nums2.size();
int len = m + n;
if(len & 1)//偶数
return findkth(a, m, b, n, len/2 + 1);
else//奇数第k小元素+第k+1小元素/2
return (findkth(a, m, b, n, len/2 + 1) + findkth(a, m, b, n, len/2)) / 2;
}
};
我们知道二分查找的时间复杂度为O(logn)(以2为底),题目中我们要找的是第n+m/2小值,所以时间复杂度为O(log(n+m))达到题目的要求。