LeetCode 719. 找出第 K 小的数对距离

719. 找出第 K 小的数对距离

数对 (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)。
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值