在兩排序數組中尋找第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     }

參考:《程序員代碼面試指南》左程雲

   


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM