一、题目描述
给定两个大小分别为 m
和 n
的正序(从小到大)数组 nums1
和 nums2
。请你找出并返回这两个正序数组的 中位数 。
算法的时间复杂度应该为 O(log (m+n))
。
示例 1:
输入:nums1 = [1,3], nums2 = [2] 输出:2.00000 解释:合并数组 = [1,2,3] ,中位数 2
示例 2:
输入:nums1 = [1,2], nums2 = [3,4] 输出:2.50000 解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5
二、暴力做法
对两个数组统计个数,计算出中位数的位置,两个迭代器(类似指针)对两个数组进行遍历,并记录步数。
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int size1=nums1.size();
int size2=nums2.size();//获取两个容器的大小
int index1=0,index2=0;//记录中位数索引位置 ,index1记录大的值
int mid1=0,mid2=0; //分别记录中位数
int isD=0;//是否偶数个
if((size1+size2)%2==0){ //偶数个
isD=1;
index2=(size1+size2)/2;
index1=index2+1; //index1更大
}else index1=(size1+size2)/2+1; //奇数个
vector<int>::iterator it1=nums1.begin(); //第一个迭代器
vector<int>::iterator it2=nums2.begin();
int pace1=0,pace2=0;
int pace3=0;//记录总步数
while(pace1<size1&&pace2<size2&&pace3<index1){
while(*it1<=*it2&&pace1<size1&&pace3<index1){//小的往前走
pace1++;
pace3++;
if(pace3==(index1-1))mid2=*it1;
if(pace3==index1) mid1=*it1;
it1++;
if(pace1==size1){it1--;} //如果是数组最后一个,迭代器前移,防止越界。
}
while(*it1>=*it2&&pace2<size2&&pace3<index1){
pace2++;
pace3++;
if(pace3==index1-1)mid2=*it2;
if(pace3==index1) mid1=*it2;
it2++;
if(pace2==size2)it2--;
}
}
while(pace1<size1&&pace3<index1){ //容器1还有剩余
pace1++;
pace3++;
if(pace3==index1-1)mid2=*it1;
if(pace3==index1) mid1=*it1;
it1++;
if(pace1==size1){it1--;}
}
while(pace2<size2&&pace3<index1){ //容器1还有剩余
pace2++;
pace3++;
if(pace3==index1-1)mid2=*it2;
if(pace3==index1) mid1=*it2;
it2++;
if(pace2==size2){it2--;}
}
double res=0; //中位数
if(isD){ //双数
return (mid1+mid2)/2.0;
}else{
return mid1;
}
}
三、问题与优化
1.vector 的size和capacity并不相等,容易出现迭代器越界
对每个数组进行步数计算,防止出现越界
2.在下面例子中,循环条件pace3<index1。而在循环体中对index1的值进行更改,同样会导致越界
采用 mid_value来记录循环体中的值
while(pace1<size1&&pace2<size2&&pace3<index1){
while(*it1<=*it2&&pace1<size1&&pace3<index1){//小的往前走
if(pace1==size1){it1--;}
it1++;
pace1++;
pace3++;
if(pace3==(index1-1))index2=*(it1-1);
if(pace3==index1) index1=*(it1-1);
}
四、二分查找,递归
因为题目要求时间复杂度O(log(m+n)
很容易想到二分法。
找中位数,本质找第k小的数,又因为两个数组有序,每次比较两个数列中[k/2]位置的数据,因为第k个数一定大于或者等于[k/2],所以大胆排除较小的前[k/2]个数据。
此时k=k-淘汰的数。继续循环。
利用递归,整合奇数与偶数,不用迭代器,采用数组的形式输出,len1始终指向短的,且在改变
这些思路确实厉害
class Solution {
public:
double getKey(vector<int>& nums1, int start1,int end1,vector<int>& nums2,int start2,int end2,int k);
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int n=nums1.size();
int m=nums2.size();
int left=(n+m+1)/2; //2
int right=(n+m+2)/2; //将奇偶合并同样都是求两次k 3
int left_value=getKey(nums1,0,n-1,nums2,0,m-1,left);
int right_value=getKey(nums1,0,n-1,nums2,0,m-1,right);
return (right_value+left_value)/2.0;
}
};
double Solution::getKey(vector<int>& nums1, int start1,int end1,vector<int>& nums2,int start2,int end2,int k){ //递归
//计算数组长度
int len1=end1-start1+1;
int len2=end2-start2+1;
//终止条件
/*
* [1,2][-1,3]会出现偶数个中位数都在同一个数组中 if(k==0) return nums2[start+k] 就不成立
* */
//想办法让长度上nums1<nums2
if(len1>len2){
return getKey(nums2,start2,end2,nums1,start1,end1,k);
}
if(len1==0) return nums2[start2+k-1]; //短的已经遍历完成
if(k==1){ //倒数第一小,返回小的值
if(nums1[start1]<nums2[start2]) return nums1[start1];
else return nums2[start2];
}
//递归主体,要判断两数组中[k/2]位置值的大小关系
//小心越界,当k/2>len时 让其指向最后一位数
int i=start1+std::min(len1,k/2)-1;
int j=start2+std::min(len2,k/2)-1;
if(nums1[i]<nums2[j]){ //如果数组一中更小,丢弃小的
return getKey(nums1,i+1,end1,nums2,start2,end2,k-(i-start1+1)); //新的k等于 原来的k减去被抛弃的小的数,并不等于k/2,因为数组不一定够长
}else{
return getKey(nums1,start1,end1,nums2,j+1,end2,k-(j-start2+1));
}
}
我自己写的,很多问题,最终也没跑通,指针不能越界,等等问题。
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int size1=nums1.size();
int size2=nums2.size();//获取两个容器的大小
int index1=0,index2=0;//记录中位数索引位置 ,index1记录大的值
int isD=0;//是否偶数个
if((size1+size2)%2==0){ //偶数个
isD=1;
index2=(size1+size2)/2;
index1=index2+1; //index1更大
}else index1=(size1+size2)/2; //奇数个
int pace1=0,pace2=0;
int k=size1+size2; //中位数的一半
if(size1==0||size2==0){ //特护情况,有一个数组为空
if(size1==0){ //数组1为空
if(isD){
return (nums2[index1-1]+nums2[index2-1])/2.0;
}else return nums2[index1-1];
}else{ //数组二为空
if(isD){
return (nums1[index1-1]+nums1[index2-1])/2.0;
}else return nums1[index1-1];
}
}
while(k!=1){
k=k/2;
if(size1>=k&&size2>=k){ //存在足够的长度
if(nums1[pace1+k-1]<=nums2[pace2+k-1]){ //小的往前移动
if(pace1+k<size1)
pace1+=k;
}else{
if(pace2+k<size2)
pace2+=k;
}
}
}
if(isD){ //偶
return (nums1[pace1]+nums2[pace2])/2.0;
}else{ //奇数
if(nums1[pace1]<=nums2[pace2]){return nums1[pace1];
}else{return nums2[pace2];}
}
}
};
五、划分数组的方法
这就是官方给出的方法
a[99]和b[1]
中位数50,51如果在a中进行j 的调整,显然当j==1时 ,i会越界,所以要在短的数组中进行调整
i 是nums1的中位线,j是nums2的中位线
起始的i满足i=(left+right+1)/2 第一个有序数组的中位数。
i+j==(m+n+1)/2一定满足
此时可以在nums1中进行left和right遍历,来调整i的位置i(小心越界),同时j的位置也会相应的发生改变。当满足
- 总数组左边个数<=右边个数
- i和j分界左右满足特定的大小关系时
即找到了两个数组中位数分界的位置,再对分界线两边的数进行比较即可
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;
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;
}
};
作者:力扣官方题解
链接:https://leetcode.cn/problems/median-of-two-sorted-arrays/solutions/258842/xun-zhao-liang-ge-you-xu-shu-zu-de-zhong-wei-s-114/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。