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]]};
}
}