• 在两排序数组中寻找第K小的数


    题目:在两个排序数组中寻找第K小的数

    举例:

    arr1=[1,2,3,4,5],arr2=[3,4,5],k=1

    1是所有数中第一小的数,所以返回1

    arr1=[1,2,3],arr2=[3,4,5,6],k=4

    3是所有数中第4小的数,所以返回3

    要求:如果arr1的长度为N,arr2的长度为M,时间复杂度请达到O(log(min{M,N}),额外空间复杂度为O(1)

    思路:暴力解法坑定是将两个数组放到一起再进行排序,然后再找出第K个,但这样的时间复杂度肯定超了,一看到logN,就想起肯定与二分查找有关

    看到这个题,我们先来看一个稍简单的同类的题,如下:

    题目:在两个长度相同的排序数组中找到上中位数
    
    给定两个有序数组arr1和arr2,已知两个数组的长度都为N,求两个数组中的所有数的上中位数
    
    举例:
    
    arr1=[1,2,3,4].arr2=[3,4,5,6]
    
    总共有8个数,那么上中位数是第4小的数,所以返回3
    
    arr1=[0,1,2],arr2=[3,4,5]
    
    总共有6个数,那么上中位数是第3小的数,所以返回2
    
    要求:时间复杂度O(logN),额外空间复杂度为O(1)

    先来分析一下这个题:根据时间复杂度的要求,我们首先利用二分的方式来寻找上中位数

     1.假定两数组分别为arr1[start1,end1] 、arr2[start2,end2]

     初始时,start1=0,end1=N-1;start2=0,end2=N-1.

    2.如果srart1==end1,那么也有start2==end2;

      表明每个数组内此时各只有一个元素,总元素数为2,上中位数为其中较小的那个

      所以直接返回 min(arr1[start1],arr2[start2]);

    3.如果srart1!=end1,说明此时两个数组的长度均大于1,

    则令 mid1=(start1+end1)/2 ;mid2=(start2+end2)/2 .来表示两个数组的中间位置

    这个时候需要分情况讨论了;

    a.如果arr1[mid1]==arr2[mid2]时,                  直接返回arr1[mid1]或arr2[mid2]

      举个例子来说明一下:

      (1).当两个数组的长度都为奇数时

        arr1={a1,a2,a3,a4,a5}  、其中的a1表示第一个数,a5表示第5个数,(不表示值)下同

        arr2={b1,b2,b3,b4,b5}

        此时a3==b3,由于两个数组本身是有序的,在a3前面压着2个数,在b3前面压着2个数,所以a3,b3前面共压了4个数,现在要求第5小的数(总共有10个数,故上中位数为5),必然是a3或是b3,而a3==b3,所以直接返回这两个数中任一个即可,即返回arr1[mid1]

      (2).当两个数组的长度都为偶数时

        arr1={a1,a2,a3,a4}  

        arr2={b1,b2,b3,b4}

        此时a2==b2,由于两个数组本身是有序的,在a2前面压着1个数,在b2前面压着1个数,所以a2,b2前面共压了2个数,现在要求第4小的数(总共有8个数,故上中位数为4),必然是a2或是b2,而a2==b2,所以直接返回这两个数中任一个即可,即返回arr1[mid1]

     b.如果arr1[mid1] > arr2[mid2]时,                  

      举个例子来说明一下:

      (1).当两个数组的长度都为奇数时

       arr1={a1,a2,a3,a4,a5}  、其中的a1表示第一个数,a5表示第5个数,(不表示值)下同

       arr2={b1,b2,b3,b4,b5}

      此时a3>b3,由于两个数组本身是有序的,在b3前面必然至少压着2个数,而在a3前面至少压着5个数(a1,a2,b1,b2,b3),所以a3至少应该是第6个数起(因为已经知道前面有5个数肯定比它要小),后面的a4最好情况下也是第7个数起,再后面的a5也必然是大于5的,(因为此时数组总长度为10,要寻找第5小的数),故此时对于arr1数组,第5小数必然要在{a1,a2}里面找,而对于arr2数组,b2 可能是第5小数吗?不可能,因为在arr2数组中b2 前只压了1个数,在ar1数组中,b2最多只能把2个数(a1,a2)压在底下,所以b2最好情况下也只能是第4小数,而对于b1 ,由于压得数更少所以跟不可能,所以从b3 开始才有可能是第5小数

    由于两数组长度要保持一致,现在来看一下两数组中第5小数可能会出现的位置

    {a1,a2,a3}

    {b3,b4,b5}

    现在我们来找一下这两个新数组的共同的上中位数,也就是这6个数中第3小的数记为a,这个a 代表啥?就是a在这两段数组中,会把2个数压在下面,同时也自然会把原来的arr2数组中的b1,b2压在下面,所以a 正好就是第5小的数,也就是我们要求的结果,所以解决问题的方法就是对新的两个数组继续求上中位数,具体做法就是,直接令 end1=mid1,start2=mid2,然后重复求解上中位数就行

     (2).当两个数组的长度都为偶数时

          arr1={a1,a2,a3,a4}  

        arr2={b1,b2,b3,b4}

      此时a2>b2,由于两个数组本身是有序的,a2前面至少压着3个数,所以a2可能是第4小的数,而对于后面的a3,前面都至少压了4个数了,必然不是,后面的a4更不用看了,对于数组arr2,b2最好前面也是只压了2个数(a1,b1),所以第4小数不可能是b2,更不可能是b1,

    由于两数组长度要保持一致,现在来看一下两数组中第5小数可能会出现的位置

    {a1,a2}

    {b3,b4}

    问题同样转化为了寻找新数组的上中位数,所以令end1=mid1,start2=mid2+1.

    c.如果arr1[mid1] < arr2[mid2]时,                  

    分析方法与b是一样的(就像b中两数组互换了下)

    所以,数组长为奇数时,令start1=mid1,end2=mid2

        数组长度为偶数时,令start1=mid1+1,end2=mid2,重复寻找上中位数就行

    所以,我们可以给出整个算法的代码:

     1 public int getUpMedian(int[] arr1,int[] arr2){
     2         if(arr1==null||arr2==null||arr1.length!=arr2.length){
     3             throw new RuntimeException("Your arr is invalid");
     4         }
     5         int start1=0;
     6         int end1=arr1.length-1;
     7         int start2=0;
     8         int end2=arr2.length-1;
     9         int mid1=0,mid2=0;
    10         int offset=0;//用于判断过程中数组的长度的奇偶
    11         while(start1<end1){
    12             mid1=(start1+end1)/2;
    13             mid2=(start2+end2)/2;
    14             offset=((end1-start1+1)&1)^1;
    15             //元素个数为奇数,offset为0,元素个数为偶数,offset为1
    16             if (arr1[mid1] > arr2[mid2]){
    17                  end1=mid1;
    18                  start2=mid2+offset;
    19             }else if(arr1[mid1]<arr2[mid2]){
    20                 end2=mid2;
    21                 start1=mid1+offset;
    22             }else{
    23                 return arr1[mid1];
    24             }
    25         }
    26         return Math.min(arr1[start1],arr2[start2]);
    27     }

    现在我们来看一下头先的那个题,即寻找第K小数

    思路:我们先记

      长度较短的数组为shortArr,长度记为lenS

      长度较长的数组记为longArr,长度记为lenL

      假设shortArr长度为10,{a1,a2,a3,...a10}表示第一个数、第2个数、...

           假设longArr长度为27,{b1,b2,...,b27}表示第一个数,...

    1.当k<1或k>lenS+lenL,则k无效

    2.如果k<=lenS.

      那么在shortArr中选前面k个数,在longArr中也选前面k个数

      则两段数组的上中位数就是第k 小数(等价于转化成了两个长度相同的数组的形式)

    3.如果k>lenL

    如一共有37个数,求第33小的数(33>lenL==27)

    在{a1,a2,..a10}中a5及a5以前的数都不可能是第33小的数,因为就算a5比b27都大,此时a5==32,所以不可能,a5前面的也不可能,对于a6,如果a6>b27,则a6必然是第33小的数,直接返回a6,否则a6不是。同理在{b1,b2,...,b27}中{b1,b2,...,b22}也必然不可能是第33小的数,因为b22最大也只能为22+10=32,所以应从b23开始找,只要b23>a10,则b23必然是第33小,否则b23也不是,如果a6和b23有一个满足条件,则可以直接返回,否则说明{a1,a2,..,a6},{b1,b2,..,b23}都不可能是,应在{a7,..,a10},{b24,..,b27}这两个数组里找他们的上中位数

    4.lenS<k<=lenL时

    如求第17小的数

    在{a1,a2,..,a10}中每个数都有可能

    在{b1,..b27}中b6以前的数必然是不可能的,因为对于b6,最大也只为6+10=16,b18以后的也不可能是,因为他本身就是长数组中的第18个了

    所以长数组变成了{b7,...,b17}这11个数,如果此时b7>a10,则可以直接返回b7,否则b7不是

    再求{b8,...,b17}和{a1,...,a10}上中位数,则为答案

    实现代码为:

     1  public  int getUpMedian(int[] arr1,int start1,int end1,int[] arr2,int start2,int end2){
     2         int mid1=0,mid2=0;
     3         int offset=0;//用于判断过程中数组的长度的奇偶
     4         while(start1<end1){
     5             mid1=(start1+end1)/2;
     6             mid2=(start2+end2)/2;
     7             offset=((end1-start1+1)&1)^1;
     8             //元素个数为奇数,offset为0,元素个数为偶数,offset为1
     9             if (arr1[mid1] > arr2[mid2]){
    10                  end1=mid1;
    11                  start2=mid2+offset;
    12             }else if(arr1[mid1]<arr2[mid2]){
    13                 end2=mid2;
    14                 start1=mid1+offset;
    15             }else{
    16                 return arr1[mid1];
    17             }
    18         }
    19         return Math.min(arr1[start1],arr2[start2]);
    20     }
    21     public  int findKthNum(int[]arr1,int[]arr2,int kth){
    22         if(arr1==null||arr2==null){
    23             throw new RuntimeException("Your arr is invalind");
    24         }
    25         if(kth<1||kth>arr1.length+arr2.length){
    26             throw new RuntimeException("k is invalid");
    27         }
    28         int[]longs=arr1.length>=arr2.length?arr1:arr2;
    29         int[]shorts=arr1.length<arr2.length?arr1:arr2;
    30         int l=longs.length;
    31         int s=shorts.length;
    32         if(kth<=s){
    33             return getUpMedian(shorts,0,kth-1,longs,0,kth-1);
    34         }
    35         if(kth>l){
    36             if(shorts[kth-l-1]>=longs[l-1])
    37                 return shorts[kth-l-1];
    38             if(longs[kth-s-1]>=shorts[s-1])
    39                 return longs[kth-s-1];
    40             return getUpMedian(shorts,kth-1,s-1,longs,kth-s,l-1);
    41         }
    42         if(longs[kth-s-1]>=shorts[s-1]){
    43             return longs[kth-s-1];
    44         }
    45         return getUpMedian(shorts,0,s-l,longs,kth-s,kth-1);
    46     }

    参考:《程序员代码面试指南》左程云

       

  • 相关阅读:
    laravel excel 导入
    linux 怎么解压
    mysql分表和表分区详解
    mysql主从复制windows-》linux
    Redis和Memcache的区别
    mysql group by 用法解析(详细)
    [置顶] mysql常用函数
    mysql测试数据库employees一些sql语句
    session入mysql
    session入库
  • 原文地址:https://www.cnblogs.com/pathjh/p/9096652.html
Copyright © 2020-2023  润新知