Leetcode 4 寻找两个正序数组的中位数
题目描述
给定两个大小为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的中位数。
进阶:你能设计一个时间复杂度为 O(log (m+n)) 的算法解决此问题吗?
来源:力扣(LeetCode)题目链接
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
题解一
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
auto size1 = nums1.size();
auto size2 = nums2.size();
//有空数组的情况,避免访问出错
if(size1 > 0 && size2 > 0){
//两数组可以直接相连的情况(注意-size1还是size2)
if(nums1.back() <= nums2.front() || nums1.front() >= nums2.back() ){
//num2中的数值大于num1
if(nums1.back() <= nums2.front()){
//偶数个
if(((size1 + size2) & 1) == 0){
//如果num1的元素数大于num2,说明
// num1+num2 < 2*num1,即两个待求数在num1中
//这里证明: 一奇一偶加和为奇 所以如果加和是偶数必然是两个奇数或两个偶数,所以size1和size2 至少差2,所以能确定在某一个数组中,而不是分散开
//如果size1 > size2,则在num1中
//如果size1 = size2 则在num1和num2中
//如果size1 < size2 则在num2中
if(size1 > size2){
return (double)((double)nums1[(size1+size2)/2]+(double)nums1[(size1+size2)/2-1])/(double)2;
}else if(size1 == size2){
return (double)((double)nums1[size1-1]+(float)nums2[0])/(double)2;
}else{
return (double)((double)nums2[(size1+size2)/2-size1]+(double)nums2[(size1+size2)/2-1-size1])/(double)2;
}
}else{
//奇数比较简单,所求数在size大的数组中
if(size1 > size2){
return (double)nums1[(size1+size2)/2];
}else if(size1 < size2){
return (double)nums2[(size1+size2)/2-size1];
}
}
}
if(nums2.back() <= nums1.front()){
if(((size1 + size2) & 1) == 0){
if(size1 > size2){
return (double)((double)nums1[(size1+size2)/2-size2]+(double)nums1[(size1+size2)/2-1-size2])/(double)2;
}else if(size1 == size2){
return (double)((double)nums2[size2-1]+(double)nums1[0])/(double)2;
}else{
return (double)((double)nums2[(size1+size2)/2]+(double)nums2[(size1+size2)/2-1])/(double)2;
}
}else{
if(size1 > size2){
return (double)nums1[(size1+size2)/2-size2];
}else if(size1 < size2){
return (double)nums2[(size1+size2)/2];
}
}
}
}else{
//如果是两数组穿插合并
double temp = 0, maxx = 0;
int j = 0, f = 0;
for(int i = 0; i <= (size1+size2)/2; i++){
//temp是倒数第二个数
//max是倒数第一个数
//注意越界
temp = maxx;
if(j < size1 && f < size2)
maxx = nums1[j] <= nums2[f] ? nums1[j++]:nums2[f++];
else if(j >= size1)
maxx = nums2[f++];
else maxx = nums1[j++];
}
//如果是偶数
if(((size1+size2) & 1) == 0) return (double)(temp+maxx)/(double)2;
else return maxx;
}
//如果有一方无元素
}else if (size1 == 0){
if(! (size2 & 1)){
return ((double)nums2[size2/2] + (double)nums2[size2/2-1]) /(double) 2;
}else return (double)nums2[size2/2];
}else if (size2 == 0){
if(! (size1 & 1)){
return ((double)nums1[size1/2] + (double)nums1[size1/2-1]) /(double) 2;
}else return (double)nums1[size1/2];
}
//leetcode强制return
return 0.00000;
}
};
题解二(二分法)(参考代码)
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2)
{
int m = nums1.size();
int n = nums2.size();
//中位数 = (left + right)/2
int left = (m + n + 1) / 2;
int right = (m + n + 2) / 2;
return (findKth(nums1, 0, nums2, 0, left) + findKth(nums1, 0, nums2, 0, right)) / 2.0;
}
//在两个有序数组中找到第k个元素(例如找第一个元素,k=1,即nums[0])
//i: nums1的起始位置 j: nums2的起始位置(i,j都是从0开始)
int findKth(vector<int>& nums1, int i, vector<int>& nums2, int j, int k)
{
//若nums1为空(或是说其中数字全被淘汰了)
//在nums2中找第k个元素,此时nums2起始位置是j,所以是j+k-1
if(i >= nums1.size()) return nums2[j + k - 1];
//nums2同理
if(j >= nums2.size()) return nums1[i + k - 1];
//递归出口
if(k == 1) return std::min(nums1[i], nums2[j]);
//这两个数组的第K/2小的数字,若不足k/2个数字则赋值整型最大值,以便淘汰另一数组的前k/2个数字
int midVal1 = (i + k/2 - 1 < nums1.size()) ? nums1[i + k/2 - 1] : INT_MAX;
int midVal2 = (j + k/2 - 1 < nums2.size()) ? nums2[j + k/2 - 1] : INT_MAX;
//二分法核心部分
if(midVal1 < midVal2)
return findKth(nums1, i + k/2, nums2, j, k - k/2);
else
return findKth(nums1, i, nums2, j + k/2, k - k/2);
}
题解2相当于每次缩小了一半的查找规模:
eg:1 2 3 4 5 6
7 8 9 10 11 12
第一次比较时,选定了k = 6, 7
以6为例
首先确定了 3 < 9 所以 3之前的数也都小于 9 (正序数组性质)
故只需要比较3之后和9之前的数 去找剩下的3个
时空复杂度为O(log(m+n))
思路不好想,但是想清楚之后,很容易写出递归式
题解三(直接思路稍作改进)
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
const int m = nums1.size();
const int n = nums2.size();
// 常量优化1
if(!m && n){
if(n%2)
return nums2[n/2];
else return (nums2[n/2]+nums2[(n-1)/2])/2.0;
}
if(!n && m){
if(m%2)
return nums1[m/2];
else return (nums1[m/2]+nums1[(m-1)/2])/2.0;
}
// 中间数(做参考)
int a = nums1[m/2];
int b = nums2[n/2];
// 常量优化2
if(1==m && 1==n){
return (a+b)/2.0;
}
// 左右op(插值)数
int left = 0;
int right = 0;
// 先找到较长的数组,以此为基准放到新构建的数组里
int t_s = n>=m ? n : m;
// debug才发现的问题:vector可增长,所以如果初始化比较多会在copy后存在多余的0参与排序,所以需要先定准
vector<int> newarray(t_s);
// 这里可以重写一个Func
if(m <= n){
copy(nums2.begin(), nums2.end(), newarray.begin());
// 从后往前插入
for(int i=m-1; i >= 0; i--){
if(nums1[i] <= b){
newarray.insert(newarray.begin()+n/2, nums1[i]);
left++;
}else{
newarray.insert(newarray.begin()+n/2+1+left, nums1[i]);
right++;
}
}
if(left >= right){
//左边插的值多,所以中位数在前面
sort(newarray.begin(), newarray.begin()+n/2+left);
}else{
//右边插的值多,所以中位数在后面
sort(newarray.begin()+n/2+left+1, newarray.end());
}
}else{
copy(nums1.begin(), nums1.end(), newarray.begin());
for(int i=n-1; i >= 0; i--){
if(nums2[i] <= a){
newarray.insert(newarray.begin()+m/2, nums2[i]);
left++;
}
else{
newarray.insert(newarray.begin()+m/2+1+left, nums2[i]);
right++;
}
}
if(left >= right){
sort(newarray.begin(), newarray.begin()+m/2+left);
}else{
sort(newarray.begin()+m/2+left+1, newarray.end());
}
}
// 根据奇偶返回中位数
if((n+m)%2) return (double)newarray[(n+m-1)/2];
else return (newarray[(n+m)/2-1] + newarray[(n+m)/2])/2.0;
}
};
这个结果着实不怎么样,果然是没啥改进的稍作改进!
题解四 划分数组(有用,记得看)
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
if (nums1.size() > nums2.size()) {
return findMedianSortedArrays(nums2, nums1);
}
int m = nums1.size();
int n = nums2.size();
int left = 0, right = m;
// median1:前一部分的最大值
// median2:后一部分的最小值
int median1 = 0, median2 = 0;
while (left <= right) {
// 前一部分包含 nums1[0 .. i-1] 和 nums2[0 .. j-1]
// 后一部分包含 nums1[i .. m-1] 和 nums2[j .. n-1]
int i = (left + right) / 2;
int j = (m + n + 1) / 2 - i;
// nums_im1, nums_i, nums_jm1, nums_j 分别表示 nums1[i-1], nums1[i], nums2[j-1], nums2[j]
int nums_im1 = (i == 0 ? INT_MIN : nums1[i - 1]);
int nums_i = (i == m ? INT_MAX : nums1[i]);
int nums_jm1 = (j == 0 ? INT_MIN : nums2[j - 1]);
int nums_j = (j == n ? INT_MAX : nums2[j]);
if (nums_im1 <= nums_j) {
median1 = max(nums_im1, nums_jm1);
median2 = min(nums_i, nums_j);
left = i + 1;
} else {
right = i - 1;
}
}
return (m + n) % 2 == 0 ? (median1 + median2) / 2.0 : median1;
}
};