题目:在两个排序数组中寻找第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 }
参考:《程序员代码面试指南》左程云