查找具有彼此最远的元素的子集

时间:2012-09-05 09:40:14

标签: algorithm proof

我有一个我似乎无法弄清楚的面试问题。给定大小为N的阵列,找到大小为k的子集,使得子集中的元素彼此相距最远。换句话说,最大化元素之间的最小成对距离。

Example:

Array = [1,2,6,10]
k = 3

answer = [1,6,10]

强力方式需要找到大小为k的所有子集,这些子集在运行时是指数级的。

我的一个想法是从数组中均匀分布值。我的意思是

  1. 取第一个和最后一个元素
  2. 找出它们之间的差异(在这种情况下为10-1)并将其除以k((10-1)/ 3 = 3)
  3. 从两端向内移动2个指针,从前一个选择中挑选+/- 3的元素。所以在这种情况下,你从1和10开始,找到最接近4和7的元素。那就是6。
  4. 这是基于直觉,即元素应尽可能均匀地分布。我不知道如何证明它有效/无效。如果有人知道如何或有更好的算法,请分享。谢谢!

4 个答案:

答案 0 :(得分:6)

这可以使用DP在多项式时间内解决。

如您所述,第一步是对列表A进行排序。让X [i,j]成为从第一个元素A中选择j个元素的解决方案。

现在,X [i + 1,j + 1] = max(min(X [k,j],A [i + 1] -A [k]))超过k <= i。

我将离开初始化步骤并记住子集步骤供您使用。

在您的示例(1,2,6,10)中,它的工作方式如下:

    1    2   6   10
1   -    -   -    -
2   -    1   5    9
3   -    -   1    4
4   -    -   -    1

答案 1 :(得分:2)

我认为基本的想法是正确的。您应该首先对数组进行排序,然后获取第一个和最后一个元素,然后确定其余元素。

我想不出一个多项式算法来解决这个问题,所以我建议使用两个选项中的一个。

一个是使用搜索算法,分支绑定样式,因为你手头有一个很好的启发式:任何解决方案的上限是到目前为止所选元素之间的最小间隙,所以第一个猜测(均匀间隔的细胞,如你所建议的)可以给你一个良好的基线,这将有助于立即修剪大多数分支。这适用于k的较小值,但最糟糕的情况是O(N^k)

另一种选择是从相同的基线开始,计算它的最小成对距离,然后尝试改进它。假设您有一个最小距离为10的子集,现在尝试用11来获得一个。这可以通过贪婪算法轻松完成 - 选择排序序列中的第一项,使其与前一项之间的距离更大 - 或等于你想要的距离。如果你成功了,试着进一步增加,如果你失败了 - 没有这样的子集。

当阵列很大并且k也相对较大时,后一种解决方案可以更快,但是阵列中的元素相对较小。如果它们受到某个值M的约束,则此算法将花费O(N*M)时间,或者稍微改进O(N*log(M)),其中N是数组的大小。

正如Evgeny Kluev在他的回答中所建议的那样,最大成对距离也有一个很好的上限,可用于这些算法中的任何一个。所以后者的复杂性实际上是O(N*log(M/k))

答案 2 :(得分:0)

我想你的套装是有序的。如果没有,我的答案会稍微改变。

Let's suppose you have an array X = (X1, X2, ..., Xn)

Energy(Xi) = min(|X(i-1) - Xi|, |X(i+1) - Xi|), 1 < i <n

j <- 1
while j < n - k do
    X.Exclude(min(Energy(Xi)), 1 < i < n)
    j <- j + 1
    n <- n - 1
end while

答案 3 :(得分:0)

$length = length($array);
sort($array); //sorts the list in ascending order
$differences = ($array << 1) - $array; //gets the difference between each value and the next largest value
sort($differences);  //sorts the list in ascending order
$max = ($array[$length-1]-$array[0])/$M; //this is the theoretical max of how large the result can be
$result = array();
for ($i = 0; i < $length-1; $i++){
    $count += $differences[i];
    if ($length-$i == $M - 1 || $count >= $max){ //if there are either no more coins that can be taken or we have gone above or equal to the theoretical max, add a point
        $result.push_back($count);
        $count = 0;
        $M--;
    }
}
return min($result)

对于非代码人员:对列表进行排序,找到每个2个连续元素之间的差异,对该列表进行排序(按升序排列),然后循环遍历它总结顺序值,直到您通过理论最大值或不存在剩下足够的元素;然后将该值添加到新数组并继续,直到您到达数组的末尾。然后返回新创建的数组的最小值。

这只是一个快速草案。快速浏览一下这里的任何操作都可以在线性时间内进行(基数排序)。

例如,对于1,4,7,100和200以及M = 3,我们得到:

$differences = 3, 3, 93, 100
$max = (200-1)/3 ~ 67
then we loop:
$count = 3, 3+3=6, 6+93=99 > 67 so we push 99
$count = 100 > 67 so we push 100
min(99,100) = 99

这是一个简单的练习,将其转换为我留给读者的固定解决方案(P.S.在读完书中的所有时间之后,我一直想说出来:P)