- 对于第K小、第K大的问题,通常使用的解法是优先队列,优先队列的求解方法可以查看我的文章:优先队列解决多个升序列表的第K小但是对于优先队列枚举会超时的情况,我们一般采用二分法来求解。
- 我们考虑题目如果要求:求出数组/二维数组中的第k小的数/数对,我们可以考虑另一个相同的解法:对于数组/二维数组中的数/数组 x,他是第几小的数字?
- 上面的描述可以看出是很明显的二分的问法,可以防止对数组的全部遍历,对于二分查找有疑惑的可以查看我的文章:二分法题解合集以及模板【第一部分】
乘法表中第k小的数
- 根据上面的分析,我们把此题等价成对于乘法表中的数字x,他是乘法表中的第几小的数字。
- 对于乘法表中的第 i i i行,其数字均为 i i i的倍数,这一行一共有 n n n个数字,因此不超过 x x x的数字有 m i n ( min( min(⌊ x a \frac xa ax⌋, n ) n) n),对每一行求和,即可得到矩阵中小于 x x x的个数。
代码如下:
bool check(int m,int n,int k,int mid){
int cnt=0;
for(int i=1;i<=m;i++){
cnt+=min(mid/i,n); //计算一共有多小小于mid的数字
}
return cnt>=k; //如果大于等于k,有边界过大,right右移
}
int findKthNumber(int m, int n, int k) {
int left=1;
int right=m*n;
while(left<right){ //二分查找
int mid=(left+right)>>1;
if(check(m,n,k,mid)) right=mid;
else left=mid+1;
}
return left;
}
找出第k小的距离对
- 根据分析,我们可以把题目等价成:对于某个距离x,他是数组中的第几小距离,很明显可以用二分求解距离,左端点left=0,最小为0,右端点right=最大值-最小值。
- 考虑对于某个距离值x,如何求出数组中有多少个比他小的距离值?可以对数组进行排序,再采用双指针的思想,对于右指针r的每一个位置,求解左指针l能指向的最左的位置,由于数组是排好序的,因此对于上一个满足条件的[l,r],需要判断[l,r+1]是否满足小于距离x,如果不满足,右移左端点直到满足条件为止
代码如下:
bool check(vector<int>& nums,int k,int mid){
int l=0;
int r=1;
int cnt=0;
for(r=1;r<nums.size();r++){ //双指针对排序数组进行查找
while(nums[r]-nums[l]>mid) l++; //距离大于x了,需要增大l使距离减小
cnt+=r-l; //[l,r]中共有数对r-l
}
return cnt>=k; //大于k说明距离太大,需要减小right
}
int smallestDistancePair(vector<int>& nums, int k) {
sort(nums.begin(),nums.end());
int left=0;
int right=(*max_element(nums.begin(),nums.end())-*min_element(nums.begin(),nums.end())); //right为最大值和最小值之差
while(left<right){ //二分查找
int mid=(left+right)>>1;
if(check(nums,k,mid)) right=mid;
else left=mid+1;
}
return left;
}