题目:给定两个一维int数组A和B. 其中:A是长度为m、元素从小到大排好序的有序数组。B是长度为n、元素从小到大排好序的有序数组。希望从A和B数组中,找出最大的k个数字,要求:使用尽量少的比较次数。
1、首先我们定义一个算法模型,然后形成一个函数 f
模型:两个等长的数组,我们得到这两个数组的上中位数
1.1 两数组的长度是偶数:假设长度为4
数组1: 1 2 3 4 1这些数字只是这个数组元素的称呼,并不是实际值
数组2: a b c d
那我们如果要取出这两个数组的上中位数,我们就有几种假设如下:
- 假设1: 数组1上中位数 [2] == [b] 数组2中上中位数 ,那么我们就可以得到 [2] 或 [b]就是这两个数组的上中位数
- 假设2: 数组1上中位数 [2] > [b] 数组2中上中位数 , 我们可以知道数组1中 [3] 、[4] 和 数组 2 中的 [a]、[b]不可能是上中位数,那么此时数组1和数组中可能为上中位数的数个数是一样的,循环调用我们这个函数f,得到的上中位数就是数组1 和 数组2 整体的上中位数
- 假设3: 数组1上中位数 [2] < [b] 数组2中上中位数,和假设2相似
1.2 两数组的长度是奇数 :假设长度为5
数组1: 1 2 3 4 5 1这些数字只是这个数组元素的称呼,并不是实际值
数组2: a b c d e
那我们如果要取出这两个数组的上中位数,我们就有几种假设如下:
- 假设1: 数组1上中位数 [3] == [c] 数组2中上中位数
那么我们就可以得到 [3] 或 [c]就是这两个数组的上中位数
- 假设2: 数组1上中位数 [3] > [c] 数组2中上中位数 ,
我们可以知道数组1中 [3] 、[4] 、[5]和 数组 2 中的 [a]、[b]不可能是上中位数。那么此时,数组1中可能为上中位数的数个数是2,数组2的个数是3,长度不相等,不能调用我们的函数 f 来获取上中位数。解决方式是验证下数组2中的 [c] 是否比数组1中的 [3] 大。如果相等,则说明 [2] 和 [c] 就是这两个数组的上中位数,如果 [2] < [c] ,那么 [c] 就是这两个数组的上中位数,如果 [2] > [c] ,那么我们就排除了 [c] 为上中位数的可能性,数组1和数组2中可能为上中位数的个数都变成2,我们又可调用我们函数 f 来得到上中位数。
- 假设3: 数组1上中位数 [3] < [c] 数组2中上中位数,和假设2相似
2、取出我们的数组A和数组B的第k大元素:假设数组A长度为10,数组B长度为15
我们分为三种情况来描述如何取这两个数组的第k大元素:
数组A: 1‘ 、2‘ 、3‘、4‘、5‘、6‘、7‘、8‘、9‘、10‘
数组B: 1 、2 、 3 、4 、5 、6 、7、8 、9 、10、11、12、13、14、15
- 情况 1 : 1 <= k <= 10 , 我们假设k为5
我们可知,只有数组A和数组B的前5个元素中才有可能是第5大的元素,那我们从数组A和数组B中分别取出前5个元素,然后调用我们的函数 f 就可以得到上中位数,这样也是我们的第 5 大 元素
- 情况 2 : 15 <= k <= 25 ,我们假设为 21
我们可知,数组A中 1‘~5‘不可能,可能个数为5,数组B中1~10不可能,可能个数5。此刻我们可能性个数是一样的,但是却不能像之前那样直接调用函数f ,因为数组A+数组B淘汰的可能性个数是5+10=15种,我们如果在数组A+数组B可能的数中取出中位数,也才是第15+5=20个数,不是第21大的。因此我们需要人为的再淘汰两个数,这样就是淘汰的个数是17,数组A+数组B可能的个数是4,这样取出的上中位数才是第21大的数。因为我们需要淘汰掉 6' 和 11。那我们就判断下6‘ 和 15的大小。如果 6 ‘> 15,那6‘就是上中位数,否则淘汰6‘这个可能性;其次我们在判断下11和10'的大小,如果11 > 10',那么11就是上中位数,否则我们就去掉11这个可能性。这样最终淘汰的个数是17,数组A+数组B可能的个数是4,这样取出的上中位数就是第21大的数。
- 情况3 : 10 < k < 15,我们假设为13
我们可知,数组A中数都有可能,可能的个数为10;数组B中1~2和14~15不可能,可能的个数为11;没法调用我们的函数f,那我们再在数组B中多淘汰一个数,这样就可以调用我们的函数f。因此我们需要再判断下 3 和 10‘ 的大小,如果3 > 10',那么3就是第13大的数,如果不是我们就淘汰了3这个可能性,再调用我们的函数f这样,就可以得到第13大的数。
具体代码如下:
1 public static int findKthNum(int[] arr1, int[] arr2, int kth) {
2 if (arr1 == null || arr2 == null) {
3 throw new RuntimeException("Your arr is invalid!");
4 }
5 if (kth < 1 || kth > arr1.length + arr2.length) {
6 throw new RuntimeException("K is invalid!");
7 }
8 int[] longs = arr1.length >= arr2.length ? arr1 : arr2;
9 int[] shorts = arr1.length < arr2.length ? arr1 : arr2;
10 int l = longs.length;
11 int s = shorts.length;
12 if (kth <= s) {
13 return getUpMedian(shorts, 0, kth - 1, longs, 0, kth - 1);
14 }
15 if (kth > l) {
16 if (shorts[kth - l - 1] >= longs[l - 1]) {
17 return shorts[kth - l - 1];
18 }
19 if (longs[kth - s - 1] >= shorts[s - 1]) {
20 return longs[kth - s - 1];
21 }
22 return getUpMedian(shorts, kth - l, s - 1, longs, kth - s, l - 1);
23 }
24 if (longs[kth - s - 1] >= shorts[s - 1]) {
25 return longs[kth - s - 1];
26 }
27 return getUpMedian(shorts, 0, s - 1, longs, kth - s, kth - 1);
28 }
29 //获取两个数组的上中位数
30 public static int getUpMedian(int[] a1, int s1, int e1, int[] a2, int s2,int e2) {
31 int mid1 = 0;
32 int mid2 = 0;
33 int offset = 0;//判断数组长度是奇数还是偶数
34 while (s1 < e1) {
35 mid1 = (s1 + e1) / 2;
36 mid2 = (s2 + e2) / 2;
37 offset = ((e1 - s1 + 1) & 1) ^ 1;
38 if (a1[mid1] > a2[mid2]) {
39 e1 = mid1;
40 s2 = mid2 + offset;
41 } else if (a1[mid1] < a2[mid2]) {
42 s1 = mid1 + offset;
43 e2 = mid2;
44 } else {
45 return a1[mid1];
46 }
47 }
48 return Math.min(a1[s1], a2[s2]);
49 }