题目
数对 (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-icnt += 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;}}
