力扣4.寻找两个正序数组的中位数(不写你就损失了一个亿的题)

题目:传送门
**题意:**这个题题意很好理解,就是给了两个正序数组 nums1 和 nums2 ,要求得出两个正序数组的中位数,最简单的思路就是把两个数组合并,然后就可以简单的求的中位数,但是这样就会浪费时间和空间,首先你要开一个大的数组用来存数据,然后你存数据的时候要遍历两个数组,这个时候的时间复杂度就是O(m+n)。那有没有其他办法呢?我的思路是定义两个指针,分别指向nums1 和nums2,因为两个数组给的是正序,那么我就只需要移动指针将前 (m+n-1)/2个数据移出去,这个时候指针指向的数据就是我们需要的中位数。
如果这样有没有什么特殊情况要考虑呢?有,我让 x=(m+n-1)/2,用x来控制循环,那么如果x==0,也就是我现在的指针已经指向了我需要的数据,那么如果一开始x就等于0呢,是由这种情况的,例如nums1=[],nums2=[1]、nums1=[2],nums2=[1]、nums1=[],nums2=[1,2],例如这三中情况,这个时候x=0,为什么把这种情况单独拿出来呢,因为我使用了指针,这时候我必须考虑指针是否越界问题,因为在移动指针的时候,很有可能把指针移出到数组外,造成数组越界的问题。所以这个思路的问题,也就是最需要注意的点,考虑指针是否越界,一定要考虑。

class Solution {
    
    
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
    
    
        int flag=(nums1.size()+nums2.size())%2;
        int x=(nums1.size()+nums2.size()-1)/2;
        int len1=0,len2=0;
        double ans=0;
        if(x==0){
    
    
            if(nums1.size()==2&&nums2.size()==0){
    
    
                ans=1.0*(nums1[0]+nums1[1])/2;
                return ans;
            }
            else if(nums1.size()==1&&nums2.size()==1){
    
    
                return 1.0*(nums2[0]+nums1[0])/2;
            }
            else if(nums1.size()==1&&nums2.size()==0){
    
    
                return 1.0*nums1[0];
            }
            else if(nums2.size()==2)
            {
    
    
                return 1.0*(nums2[0]+nums2[1])/2;
            }
            else {
    
    
                return 1.0*nums2[0];
            }
        }
        while(x){
    
    
            if(len1>=nums1.size()){
    
    
                len2++;
                x--;
                continue;
            }
            if(len2>=nums2.size()){
    
    
                len1++;
                x--;
                continue;
            }
            if(nums1[len1]>=nums2[len2]){
    
    
                len2++;
            }else{
    
    
                len1++;
            }
            x--;
        }
        if(len1>=nums1.size()){
    
    
             ans+=nums2[len2];
             len2++;
        }
        if(len2>=nums2.size()){
    
    
            ans+=nums1[len1];
            len1++;
        }
        if(len1<nums1.size()&&len2<nums2.size()){
    
    
            if(nums1[len1]>=nums2[len2]){
    
    
                ans+=nums2[len2];
                len2++;
            }else{
    
    
                ans+=nums1[len1];
                len1++;
            }
        }
        
        if(!flag){
    
    
            if(len1>=nums1.size()){
    
    
              ans+=nums2[len2];
                ans/=2;
        }
        if(len2>=nums2.size()){
    
    
             ans+=nums1[len1];
                ans/=2;
        }
         if(len1<nums1.size()&&len2<nums2.size()){
    
    
             if(nums1[len1]>=nums2[len2]){
    
    
                ans+=nums2[len2];
                ans/=2;
            }else{
    
    
                ans+=nums1[len1];
                ans/=2;
            }
         }
            
        }
        return ans;
    }
};

反思:代码写的长乱,思考问题不全面,我是菜鸡。
在这里插入图片描述
这个问题还有进阶,看到log,我们应该想到的应该是二分查找,那么该如何使用二分呢?就像上面我们那个思路,我们把通过移动指针把两个数组的一半给切割,那如果是使用二分呢?
首先什么是二分查找,很简单,就是使用一个数组中间的值,我们给中间的值进行比较,从而切割数组,把数组分成一半,下次查找就从新的区间开始重复,这就是简单的二分查找的思路,那这个题呢?该如何应用二分呢?
首先,我们要知道,这个题的要求,我们要找到两个点,这两个点需要把两个数组切割,使得两个数组的左边正好是我们需要丢弃的数值,那么我们该如何使用二分的思路来寻找这两个切割的点呢?我们规定切割的两个点的下标是i和j,那么这两个点需要满足的条件是:nums1[i-1]<=nums2[j]&&nums2[j-1]<=nums1[i],如果这个条件满足,说明i和j左边的值都是在nums1[i]和nums2[j]的左边,如果两边的个数正好是两个数组和的一半,那么我们就求得了切割点。那么我们该如何和二分练习起来呢?我们可以先取一个短数组的中间点,为什么用短数组,因为这样可以使遍历的次数少,这样我们首先得到一个点i,那么我们该如何得到j呢,还有一个条件,那就是我希望i和j的左边的数值正好是两个数组和的一半。

  int i=(left+right)/2;
  int j=(n+m+1)/2-i;//i和j记录nums1和nums2的切割点

现在我们得到了切割点,现在我们就应该判断是否满足情况。

 if(lnum_i<num_j)

大家会发现,这样判断就只满足了一半的条件,那就是满足了nums1[i-1]<=nums2[j]这个条件,还有一半条件没有满足啊,但是大家要知道我们使用二分是递归查找的,所以我们不需要一下子就满足所有情况,但是我们可以使用递归来使所有情况的判别到,我们知道两个数组都是正序数组,那么这里面就含有了很多其他条件,就像如果nums1[i-1]<=nums2[j]为真,那么nums2[j]就大于i-1左边全部的数值,如果不成立,我们就需要继续向i-1左边的数组继续切割,这样就满足了二分的思想。
这就是二分的本质:

  if(lnum_i<num_j){
    
    
               lmedin=max(lnum_i,lnum_j);
               rmedin=min(num_i,num_j);
               left=i+1;

           }else{
    
    
               right=i-1;
           }

我们上完整代码:

class Solution {
    
    
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
    
    
        if(nums1.size()>nums2.size()){
    
    
            return findMedianSortedArrays(nums2,nums1);//需要将短的数组放到nums1
        } 
        int m=nums1.size(),n=nums2.size();
        int lmedin=0,rmedin=0;
        int left=0,right=m;
        while(left<=right){
    
    
           int i=(left+right)/2;
           int j=(n+m+1)/2-i;//i和j记录nums1和nums2的切割点
           
           int lnum_i= (i==0)?INT_MIN:nums1[i-1];
           int num_i=(i==m)?INT_MAX:nums1[i];
           int lnum_j=(j==0)?INT_MIN:nums2[j-1];
           int num_j=(j==n)?INT_MAX:nums2[j];

           if(lnum_i<num_j){
    
    
               lmedin=max(lnum_i,lnum_j);
               rmedin=min(num_i,num_j);
               left=i+1;

           }else{
    
    
               right=i-1;
           }

        }
        return ((m+n)%2==0)?(lmedin+rmedin*1.0)/2:lmedin*1.0;
    }
};

这个题我认为非常好,对二分的应用有了更好的理解,可能我说的并不是很通俗易懂,希望大家可以自己写一下这个题,如果没有思路,可以看一下我的代码好好理解理解,然后自己好好思考,自己敲一下这个题。
然后总结一下,我感觉写这种题最重要的就是找状态机制,认真分析,找准状态机制,如果找到就很简单了,不止这种题,感觉所有题都是这样,然后,希望大家加油

Guess you like

Origin blog.csdn.net/qq_43840681/article/details/117421969