題目:在兩個排序數組中尋找第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 }
參考:《程序員代碼面試指南》左程雲