719. Find K-th Smallest Pair Distance

1 题目理解

输入:一个int数组nums,一个数字k
输出:返回所有数字对中,数字对距离第k小的距离。
规则:一个数组中两两配对计算差值得到距离。距离值从小到大排序,第k位置的元素就是返回值。
例如
Input:
nums = [1,3,1]
k = 1
Output: 0
所有的配对距离是:
(1,3) -> 2
(1,1) -> 0
(3,1) -> 2
所有距离中从小到大排序,第1个是0,所以返回0。

Note:

2 <= len(nums) <= 10000.
0 <= nums[i] < 1000000.
1 <= k <= len(nums) * (len(nums) - 1) / 2.

2 分析思路

内容来源于花花酱

2.1 桶排序

最直接的想法是用两个for循环计算每个匹配对的距离,然后对距离进行排序。返回第k个元素 。排序算法选择桶排序,加快速度。int[] distance记录距离出现的次数。distance[0]表示有几个配对的距离为0。

class Solution {
    public int smallestDistancePair(int[] nums, int k) {
        Arrays.sort(nums);
        int n = nums.length;
        int  max = nums[n-1];
        int[] distance = new int[max+1];
        for(int i=0;i<n;i++){
            for(int j=i+1;j<n;j++){
                distance[nums[j] - nums[i]]++;
            }
        }
        for(int i = 0;i<max;i++){
            k -= distance[i];
            if(k<=0){
                return i;
            }
        }
        return 0;
    }
}

时间复杂度 O ( n 2 m a x ) O(n^2max) O(n2max)

2.2 二分+动态规划

我们先对数组nums排序。
返回值最小值是0,最大值=nums最大值-nums最小值。
用二分法查找返回值,也就是距离。
例如m=3,我们需要查找有多少个配对的距离小于等于3。
查找的方法使用动态规划。
设dp[0] 为所有和nums[0]配对的距离小于等于3,有多少个。
dp[1]为所有和nums[0],nums[1]的配对距离小于等于3,有多少个。

dp[n-1]表示所有配对中距离小于等于3,有多少个。
例如nums={1,1,3,5,8}。
最开始,i=0,j=0,
nums[0] - nums[0] =0<=m,成立,j++;
nums[1]-nums[0]=1-1=0<=m,成立,j++;
nums[2]-nums[0]=3-1=2<=m,成立,j++;
nums[3]-nums[0]=5-1=4<=m,不成立,退出循环,因为数组是有序的,再比较下去,距离只会越来越大。
那么dp[0] = j-i-1=3-0-1=2,表示所有和nums[0]配对中,有2个配对的距离<=m。

接着计算i=1,此时j不需要从0开始。

配对(0,2)在上一步已经计算了,配对(1,2)的距离一定<=m。
因为nums[2]-nums[0]<=m,在上一步已经计算过了,而nums[1]>nums[0],同样的数减去一个更大的数,差只能更小。
所以j不需要从0开始。

j=3,nums[3]-nums[1]=5-1=4<=m,不成立,退出循环。
dp[1] = dp[0] + (j-i-1)=3,表示所有和nums[0]、nums[1]配对中,有3个配对的距离<=m。

接着计算i=4,j=3。

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 m =  l+((r-l)>>1);
            if(countNumber(nums,m)>=k){
                r = m - 1;
            }else{
                l = m + 1;
            }
        }
                 
        return l;
    }
    
    private int countNumber(int[] nums,int m){
        int j = 0;
        int n = nums.length;
        int[] dp = new int[n];
        for(int i=0;i<n;i++){
            while(j<n && nums[j]-nums[i]<=m) j++;
            dp[i] = ( i==0? (j-i-1) : dp[i-1]+(j-i-1));
        }
        return dp[n-1];
    }
}

时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),n应该是数组长度和数组最大值之间的最大值。当然最后的代码还可以做空间优化。

2.3 用堆实现

参考力扣
首先将数组排序,排序之后,相邻元素的差最小。先将nums[i]和nums[i+1],放入堆中,之后再依次将nums[i]和nums[i+2] nums[i]和nums[i+3]放入堆中 …
时间复杂度O((k+N)lgN),时间复杂度过高。

class Solution {
    public int smallestDistancePair(int[] nums, int k) {
        Arrays.sort(nums);
        PriorityQueue<Node> heap = new PriorityQueue<Node>(nums.length,
            Comparator.<Node> comparingInt(node -> nums[node.nei] - nums[node.root]));
        
        int n = nums.length;
        for(int i=0;i<n-1;i++){
            heap.offer(new Node(i,i+1));
        }
        Node node = null;
        for(;k>0;k--){
            node = heap.poll();            
            if(node.nei+1<n){
                heap.offer(new Node(node.root,node.nei+1));
            }
        }
        return nums[node.nei]- nums[node.root];
    }
    class Node {
        int root;
        int nei;
        Node(int r, int n) {
            root = r;
            nei = n;
        }
    }

}

3相似题目786

3.1 二分

虽说相似,没有找到规律还是理解不了的。即使找到规律了也不一定会数数,在O(n)时间复杂度内。

class Solution {
    public int[] kthSmallestPrimeFraction(int[] A, int K) {
        double l = 0;
        double r = 1.0;
        int[] answer = new int[2];
        while(l<r){
            double m = (l+r)/2;
            int[] res = countSmallerOrEqual(A,m);
            if(res[0] == K){
                answer[0] = res[1];
                answer[1] = res[2];
                break;
            }else if(res[0]>=K){
                r = m;
            }else{
                l = m;
            }
        }
        return answer;
    }
    
    private int[] countSmallerOrEqual(int[] A,double m){
        int count = 0;
        int p = 0,q = 1;
        int i=0;
        for(int j=1;j<A.length;j++){
            while(A[i]<=m*A[j]) i++;
            count += i;
            if(i>0){
                i=i-1;
                if(p*A[j]<q*A[i]){
                    p = A[i];
                    q = A[j];
                }
            }
            
        }
        return new int[]{count, p, q};
    }
   
}

对于countSmallerOrEqual函数不知道怎么写,可以先用两个for循环来写。会发现i的循环可以提取出来。因为A数组是递增的。例如[1,2,3,5]。如果1/2满足条件,那么1/3一定满足条件。因为分子不变,分母增加,数值会越来越小。

private int[] countSmallerOrEquals(int[] A ,double m){
    	int count = 0;
        int p = 0,q = 1;
        for(int j=1;j<A.length;j++){
            int i=0;
            for(;i<j;i++){
                if(A[i]>m*A[j]){
                    break;
                }
            }
            count += i;
            i=i-1;
            if(p*A[j]<q*A[i]){
                p = A[i];
                q = A[j];
            }
        }
        return new int[]{count, p, q};
    }

3.2 堆

class Solution {
    public int[] kthSmallestPrimeFraction(int[] A, int k) {
        int[] answer = new int[2];
        PriorityQueue<int[]> pq = new PriorityQueue<int[]>((a, b) ->
        A[a[0]] * A[b[1]] - A[a[1]] * A[b[0]]);
        
        for(int i=1;i<A.length;i++){
            pq.offer(new int[]{0,i});
        }
        
        while(k>1){
            int[] vals = pq.poll();
            if(vals[0]+1 < vals[1]){
                pq.offer(new int[]{vals[0]+1,vals[1]});
            }
            k--;
        }
        answer = pq.poll();
        return new int[]{A[answer[0]],A[answer[1]]};
    }
  
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值