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

3是全部數中第4小的數,因此返回3code

要求:若是arr1的長度爲N,arr2的長度爲M,時間複雜度請達到O(log(min{M,N}),額外空間複雜度爲O(1)blog

 

思路:暴力解法坑定是將兩個數組放到一塊兒再進行排序,而後再找出第K個,但這樣的時間複雜度確定超了,一看到logN,就想起確定與二分查找有關排序

看到這個題,咱們先來看一個稍簡單的同類的題,以下:get

題目:在兩個長度相同的排序數組中找到上中位數

給定兩個有序數組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)

先來分析一下這個題:根據時間複雜度的要求,咱們首先利用二分的方式來尋找上中位數io

 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     }

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

相關文章
相關標籤/搜索