数对 (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
提示 1
Binary search for the answer. How can you check how many pairs have distance <= X?
解法1:排序 + 二分查找
对数对的距离进行二分查找,找到 数对数目 count >= k 的 最小数对距离。
先将数组 nums 从小到大进行排序。因为第 k 小的数对距离必然在区间 [0,max(nums)−min(nums)] 内,令 left=0,right=max(nums)−min(nums),我们在区间 [left,right] 上进行二分。
对于当前搜索的距离 mid,计算所有距离小于等于 mid 的数对数目 cnt,如果 cnt≥k,那么 right=mid−1,否则 left=mid+1。当 left>right 时,终止搜索,那么第 k 小的数对距离为 left。
给定距离 mid,计算所有距离小于等于 mid 的数对数目 cnt 可以使用二分查找:枚举所有数对的右端点 j,二分查找大于等于 nums[j]−mid 的最小值的下标 i,那么右端点为 j 且距离小于等于 mid 的数对数目为 j−i,计算这些数目之和。
Java版:
class Solution {
public int smallestDistancePair(int[] nums, int k) {
Arrays.sort(nums);
int n = nums.length;
int l = 0;
int r = nums[n - 1] - nums[0];
while (l <= r) {
// 循环不变量:
// 数对距离 r + 1 , count >= k
// 数对距离 l - 1 , count < k
// 数对距离<= mid时,计算数对数目count
int mid = l + (r - l) / 2;
int count = 0;
for (int j = 0; j < n; j++) {
int i = binarySearch(nums, nums[j] - mid);
count += j - i;
}
if (count >= k) {
r = mid - 1;
} else {
l = mid + 1;
}
}
return l;
}
// 找到 nums[i] >= target 的最小下标i
private int binarySearch(int[] nums, int target) {
int l = 0;
int r = nums.length - 1;
while (l <= r) {
// 循环不变量:
// nums[r + 1] >= target
// nums[l - 1] < target
int mid = l + (r - l) / 2;
if (nums[mid] >= target) {
r = mid - 1;
} else {
l = mid + 1;
}
}
return l;
}
}
Python版:
class Solution:
def smallestDistancePair(self, nums: List[int], k: int) -> int:
nums.sort()
n = len(nums)
l = 0
r = nums[n - 1] - nums[0]
while l <= r:
mid = l + (r - l) // 2
count = 0
for j in range(n):
i = self.binarySearch(nums, nums[j] - mid)
count += j - i
if count >= k:
r = mid - 1
else:
l = mid + 1
return l
def binarySearch(self, nums: List[int], target: int) -> int:
l = 0
r = len(nums) - 1
while l <= r:
mid = l + (r - l) // 2
if nums[mid] >= target:
r = mid - 1
else:
l = mid + 1
return l
复杂度分析
- 时间复杂度:O(n log n * log D),其中 n 是数组 nums 的长度,D=max(nums)−min(nums)。外层二分查找需要 O(logD),内层二分查找需要 O(nlogn)。
- 空间复杂度:O(logn)。排序的平均空间复杂度为 O(logn)。
解法2:排序 + 二分查找 + 双指针
给定距离 mid,计算所有距离小于等于 mid 的数对数目 count 可以使用双指针:
初始左端点 i=0,我们从小到大枚举所有数对的右端点 j,移动左端点直到 nums[j]−nums[i]≤mid,那么右端点为 j 且距离小于等于 mid 的数对数目为 j−i,计算这些数目之和。
Java版:
class Solution {
public int smallestDistancePair(int[] nums, int k) {
Arrays.sort(nums);
int n = nums.length;
int l = 0;
int r = nums[n - 1] - nums[0];
while (l <= r) {
int mid = l + (r - l) / 2;
int count = 0;
for (int i = 0, j = 0; j < n; j++) {
while (nums[j] - nums[i] > mid) {
i++;
}
count += j - i;
}
if (count >= k) {
r = mid - 1;
} else {
l = mid + 1;
}
}
return l;
}
}
Python版:
class Solution:
def smallestDistancePair(self, nums: List[int], k: int) -> int:
nums.sort()
n = len(nums)
l = 0
r = nums[n - 1] - nums[0]
while l <= r:
mid = l + (r - l) // 2
count = 0
i = 0
for j in range(n):
while nums[j] - nums[i] > mid:
i += 1
count += j - i
if count >= k:
r = mid - 1
else:
l = mid + 1
return l
复杂度分析
- 时间复杂度:O(n×(logn+logD)),其中 n 是数组 nums 的长度,D=max(nums)−min(nums)。外层二分查找需要 O(logD),内层双指针需要 O(n),排序的平均时间复杂度为 O(nlogn)。总的时间复杂度为 O(nlogn + nlogD)。
- 空间复杂度:O(logn)。排序的平均空间复杂度为 O(logn)。