Given an integer array, return the k-th smallest distance among all the pairs. The distance of a pair (A, B) is defined as the absolute difference between A and B.
Example 1:
Input: nums = [1,3,1] k = 1 Output: 0 Explanation: Here are all the pairs: (1,3) -> 2 (1,1) -> 0 (3,1) -> 2 Then the 1st smallest distance pair is (1,1), and its distance is 0.
Note:
2 <= len(nums) <= 10000
.0 <= nums[i] < 1000000
.1 <= k <= len(nums) * (len(nums) - 1) / 2
.
/**********************************************************************/
最近做了好些动态规划的题目,而且都是一上来觉得完全ojbk用naive算法就可以做出来,最后发现不用动态规划就一定会超时限,最后灰溜溜地用动态规划实现了一遍。
所以这道题目我一上来就想着用动态规划,活生生把思路带歪,把题目想难了。
- 其实这题目很简单,用最naive的方法就可以解出来。因为题干数组nums里每个值都限定在1e6以内,因此建一个1e6大小的数组(我起名叫distance),遍历n次nums数组并计算出来每两个单元之间的差,然后在distance数组里把这个差值对应的点加1就可以了。最后遍历一遍distance数组就可以找到第k小的差值。这种方法只需要O(n*n+1e6)的时间复杂度。当然不快,最后在Leetcode里用时排在后20%,但是贵在简单。
- 再介绍一个更快的算法,是我看LeetCode里前10%了解到的。简单来说,其核心就是先对整个nums数组进行排序,得到差值的上限。然后就可以对这个上限和显而易见的下限0用二分法来找指定的差值了。二分查找的过程也很精妙:因为给任何一个数值t,在一个已经排序了的数组上可以很容易并很快地找出所有比这个数值t小的差值的数量count。因此只要将count与题干的k进行比较,就可以得到新的上下限了。
- 最后说一下我很愚蠢的动态规划算法:考虑DP[i][j][k]表示从nums[i]到nums[j]中的第k小的差值。可以发现,DP[i][j][k]的值等于——DP[i][j-1][1~k],DP[i+1][j][1~k]和|nums[i]-nums[j]|中第k小的值,并且还需要去除DP[i+1][j-1][1~k]中的重叠部分。因此当计算DP[i][j][1~k]时,只需要从DP[i][j-1][1],DP[i+1][j][1],DP[i+1][j-1][1] 开始,并和 |nums[i]-nums[j]| 一起进行比较,就能把DP[i][j][1~k]填满。显然,这样的动态规划算法需要O(n*n*k)的时间复杂度,比naive的方法还要慢,18个用例里有8个都超出时限了,非常愚蠢………………
最后附上代码:
首先是第一个方法的——
class Solution {
public int smallestDistancePair(int[] nums, int k) {
int distance[] = new int[1000000];
int ln = nums.length;
for(int i = 0;i<ln;i++)
for(int j = i+1;j<ln;j++)
{
distance[abs(nums[i]-nums[j])]++;
}
int result = 0;
while(k>0)
{
k-=distance[result];
result++;
}
return result-1;
}
public int abs(int a)
{
return a>=0?a:-a;
}
}
然后是动态规划方法的代码(第二个方法我没有实现,就不放代码了)——
class Solution {
public int smallestDistancePair(int[] nums, int k) {
int ln = nums.length;
int DP [][][] = new int[ln][ln][k+1];
for(int i = 0;i<ln-1;i++)
{
DP[i][i+1][1] = abs(nums[i] - nums[i+1]);
}
for(int step = 2;step<ln;step++)
{
for(int i = 0;i+step<ln;i++)
{
int Left = 1;
int Right = 1;
int extra = abs(nums[i]-nums[i+step]);
int Compare = 1;
for(int t = 1;t<=(step+1)*step/2 && t<=k;t++)
{
if(Left > step*(step-1)/2 || Left>k)
{
if(DP[i+1][i+step][Right]<=extra && Right<=step*(step-1)/2 && Right<=k)
{
if(Compare<=(step-2)*(step-1)/2 && Compare<=k && DP[i+1][i+step][Right]==DP[i+1][i+step-1][Compare])
{
Compare++;
Right++;
t--;
}
else{
DP[i][i+step][t] = DP[i+1][i+step][Right];
Right++;
}
}
else
{
DP[i][i+step][t] = extra;
extra = Integer.MAX_VALUE;
}
}
else if(Right > step*(step-1)/2 || Right>k)
{
if(DP[i][i+step-1][Left]<=extra && Left<=step*(step-1)/2 && Left<=k)
{
if(Compare<=(step-2)*(step-1)/2 && Compare<=k && DP[i][i+step-1][Left]==DP[i+1][i+step-1][Compare])
{
Compare++;
Left++;
t--;
}
else
{
DP[i][i+step][t] = DP[i][i+step-1][Left];
Left++;
}
}
else
{
DP[i][i+step][t] = extra;
extra = Integer.MAX_VALUE;
}
}
else
{
int min = min(DP[i][i+step-1][Left],DP[i+1][i+step][Right],extra);
if(min == 1)
{
if(Compare<=(step-2)*(step-1)/2 && Compare<=k && DP[i][i+step-1][Left]==DP[i+1][i+step-1][Compare])
{
Compare++;
Left++;
t--;
}
else
{
DP[i][i+step][t] = DP[i][i+step-1][Left];
Left++;
}
}
else if(min == 2)
{
if(Compare<=(step-2)*(step-1)/2 && Compare<=k && DP[i+1][i+step][Right]==DP[i+1][i+step-1][Compare])
{
Compare++;
Right++;
t--;
}
else
{
DP[i][i+step][t] = DP[i+1][i+step][Right];
Right++;
}
}
else
{
DP[i][i+step][t] = extra;
extra = Integer.MAX_VALUE;
}
}
}
}
}
return DP[0][ln-1][k];
}
public int abs(int a)
{
return a>=0?a:-a;
}
public int min(int a,int b,int c)
{
int min = 0;
min = a<b?(a<c?1:3):(b<c?2:3);
return min;
}
}