题目
数对 (a,b) 由整数 a 和 b 组成,其数对距离定义为 a 和 b 的绝对差值。
给你一个整数数组 nums 和一个整数 k ,数对由 nums[i] 和 nums[j] 组成且满足 0 <= i < j < nums.length 。返回 所有数对距离中 第 k 小的数对距离。
示例 1:
输入:nums = [1,3,1], k = 1
输出:0
解释:数对和对应的距离如下:
(1,3) -> 2
(1,1) -> 0
(3,1) -> 2
距离第 1 小的数对是 (1,1) ,距离为 0 。示例 2:
输入:nums = [1,1,1], k = 2
输出:0示例 3:
输入:nums = [1,6,1], k = 3
输出:5提示:
n == nums.length
2 <= n <= 10^4
0 <= nums[i] <= 10^6
1 <= k <= n * (n - 1) / 2来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/find-k-th-smallest-pair-distance
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
首先,数组长度10^4级别,只能考虑nlogn的时间了。
可以发现,数组排序是不影响数对距离的计算的,而排序后可以考虑二分,可能的最小的距离为0,最大的距离为数组中最大值减去最小值。
二分数对距离mid,然后计算小于等于mid的数对个数cnt(此过程也是通过二分实现),和k作比较,如果cnt小于k,说明mid小了,调整二分下界,反之调整上界。见代码一。
其实上述找第k个xx的这个过程,有一点类似应用快排找数组中第k大元素。
代码
代码一 排序+二分
class Solution {
public int smallestDistancePair(int[] nums, int k) {
Arrays.sort(nums);
int n = nums.length;
int low = 0;
int high = nums[n - 1] - nums[0];
while (low < high) {
int mid = low + (high - low) / 2;
// 统计距离不大于mid的数对个数
int cnt = 0;
for (int i = 0; i < n - 1; i++) {
// 对于nums[i]来说,找到nums中最后一个不大于nums[i] + mid的元素,记其下标为j,那么不大于mid的数对个数为j-i
cnt += bsearch(nums, nums[i] + mid) - i;
}
if (cnt < k) {
low = mid + 1;
} else {
high = mid;
}
}
return low;
}
// 查找nums中最后一个<=target的元素下标
public int bsearch(int[] nums, int target) {
int len = nums.length;
int low = 0;
int high = len - 1;
while (low < high) {
int mid = low + (high - low + 1) / 2;
if (nums[mid] > target) {
high = mid - 1;
} else {
low = mid;
}
}
return low;
}
}
代码二 排序+二分+双指针
内部可以不使用二分,使用双指针就可以统计出距离不大于mid的数对。
枚举右端点,然后移动左端点,直到找到第一个距离小于等于mid的数,左右端点的距离即为该段的数对个数,然后继续移动右端点。
class Solution {
public int smallestDistancePair(int[] nums, int k) {
Arrays.sort(nums);
int n = nums.length;
int low = 0;
int high = nums[n - 1] - nums[0];
while (low < high) {
int mid = low + (high - low) / 2;
int cnt = 0;
for (int i = 0, j = 0; j < n; j++) {
while (nums[j] - nums[i] > mid) {
i++;
}
cnt += j - i;
}
if (cnt < k) {
low = mid + 1;
} else {
high = mid;
}
}
return low;
}
}