求第K大的问题
问题:现在有两个有序数组A和B,求这两个数组合并之后的第K大的元素。
方法一、使用两个指针的方式,归并排序当中合并两个数组的方式,这里不需要排序,只需要找到合并之后的第K个数即可,所以需要两个指针。时间复杂度为$O(K)$
方法二、使用折半搜索的方式将复杂度将为$O(log(K))$
算法的大体思想是:
假设要找第K个,那么我们考虑A中的前$\frac{K}{2}$个元素和B中的前$\frac{K}{2}$个元素。如果A[K/2]小于B[K/2]则将A的前K/2个元素去掉;反之,去掉B的前K/2个元素。然后在后面搜索第K/2小的元素。下一次迭代按照相同的思路来进行。知道直接确定要搜索的元素,例如其中一个数组大小为空,则直接在另外一个数组中搜索第K小。如果K为1,则直接返回A[0] B[0]中的最小者。
实际上,我们仔细想想,为什么要删除前K/2,这样一定对吗?我们可以举一个例子。
A: 1 3 6 9 11 12 35
B: 2 4 14 15 17 28 30
A+B:1 2 3 4 6 9 11 12 14 15 17
第K大为12,我们发小扔掉 13 6 9是安全的,为什么?我们只需要保证他们完全是在前K小里面即可。因为当他们是在前K小里面时,在我们丢掉他们之后再在剩下的元素当中找第K/2小,是不影响的。例如上面
1 2 3 4 6 9 11 12,丢掉 1 3 6 9
变为2 4 11 12, 在剩下的元素当中第4小就是原来的第8小。为什么会是这样?简单反正即可。
假设被删掉的元素当中有比第K小大的元素,假设为X,则在X之前至少有K个元素,但是我们知道咋被删除的数组当中比X小的不超过K/2-1个,在B中更不会有K/2-2个,所以两者相加,不可能达到K个,所以原假设不成立。从而他们一定是在前K小里面。
private int findkth(int []nums1, int start1, int s1, int [] nums2, int start2, int s2, int k){ if(s1>s2) return findkth(nums2,start2,s2,nums1,start1,s1,k); if(s1==0) return nums2[start2+k-1]; if(k==1) return Math.min(nums1[start1],nums2[start2]); int pa=Math.min(s1,k/2); int pb=k-pa; if(nums1[start1+pa-1]<nums2[start2+pb-1]){ return findkth(nums1,start1+pa,s1-pa,nums2,start2,s2,k-pa); } else{ return findkth(nums1,start1,s1,nums2,start2+pb,s2-pb,k-pb); } }