在数组中消除重复项

11

来源:Google 面试题

编写一段代码,确保输入中相同的元素在输出中尽可能地扩散?

基本上,我们需要以这样的方式放置相同的元素,使得总体扩散最大化。

例如:

Input: {1,1,2,3,2,3}

Possible Output: {1,2,3,1,2,3}  

Total dispersion = Difference between position of 1's + 2's + 3's = 4-1 + 5-2 + 6-3 = 9 .

我不确定是否有针对这个问题的最优多项式时间算法。除此之外,没有提供任何其他细节。

我的想法是计算输入中每个元素的频率,然后将它们按顺序排列在输出中,每次一个不同的元素,直到所有频率都用完为止。

我不确定我的方法是否可行。

有什么其他方法/想法吗?


1
你能给一个至少包含三个相同数字的例子吗? - David Eisenstat
我不确定在那种情况下输出会是什么,这就是为什么有疑问的原因 :)。 - Spandan
嗯,不清楚你的问题是什么,因为你没有告诉我们在这种情况下要优化什么。至多两个副本的解决方案是微不足道的。 - David Eisenstat
@Spandan 如果没有指定所需的输出,那么除了David所说的之外,你可能还需要从他们那里收集信息。他们可能想要测试你的算法技能以及从将使用算法的人那里获得清晰的问题规范的能力。 - G. Bach
你到底有多不确定没有最优的多项式时间算法?我们需要上下界!量化你的不确定性! - j_random_hacker
“2111122221”和“1212121212”会得到相同的分数吗?这似乎不太对。 - Bernhard Barker
4个回答

4
我相信这个简单的算法可以工作:
  • 计算每个不同元素的出现次数。
  • 创建一个新列表
  • 将所有出现超过一次的元素添加到列表中(组内顺序无关紧要)
  • 将所有唯一元素的一个实例添加到列表中
  • 将所有出现超过一次的元素的一个实例添加到列表中
  • 将所有出现超过两次的元素的一个实例添加到列表中
  • 将所有出现超过三次的元素的一个实例添加到列表中
  • ...
现在,这显然不会给出良好的分布:
对于{1, 1, 1, 1, 2, 3, 4} ==> {1, 2, 3, 4, 1, 1, 1}
对于{1, 1, 1, 2, 2, 2, 3, 4} ==> {1, 2, 3, 4, 1, 2, 1, 2} 然而,我认为这是根据提供的评分函数得到的最佳分布。 由于离散度分数计算距离之和而不是距离平方和,因此您可以有几个相邻的重复项,只要您在其他地方有一个大的间隔来补偿。
对于平方距离和分数,问题变得更加困难。 也许这个面试问题关键在于候选人认识到评分函数的这个弱点?

这是我写的相同方法。我以为可能还有其他的东西。无论如何,谢谢。 - Spandan
对于 1,1,1,1,2,2,2,2,3,这将输出 1,2,3,1,2,1,2,1,2(对吗?),但我认为 1,2,1,2,3,1,2,1,2 更好。 - Bernhard Barker
@Dukeling:我认为两个答案得分上没有任何区别。 - Spandan
一个平方距离和得分会有同样的漏洞,一个大间隙加上一个小间隙比两个相等的间隙更好。你需要取平方根。 - Bergi

1
在 Perl 中
@a=(9,9,9,2,2,2,1,1,1);

然后,制作一个哈希表来统计列表中不同数字的数量,就像制作频率表一样。

map { $x{$_}++ } @a;

然后重复遍历所有找到的键,按照已知顺序将适当数量的单个数字添加到输出列表中,直到所有键都用完。
@r=();
$g=1; 
while( $g == 1 ) { 
   $g=0;
   for my $n (sort keys %x) 
      {
      if ($x{$n}>1) {
                    push @r, $n;
                    $x{$n}--;
                    $g=1
                    }
      } 
}

我相信这可以适用于任何支持哈希表的编程语言。

这是我写的相同方法。我以为可能还有其他的东西。无论如何,谢谢。 - Spandan

0

根据Vorsprung和HugoRune建议的算法的Python代码:

from collections import Counter, defaultdict

def max_spread(data):
    cnt = Counter()
    for i in data: cnt[i] += 1

    res, num  = [], list(cnt)
    while len(cnt) > 0:
        for i in num:
            if num[i] > 0:
                res.append(i)
                cnt[i] -= 1
            if cnt[i] == 0: del cnt[i]

    return res

def calc_spread(data):
    d = defaultdict()
    for i, v in enumerate(data):
        d.setdefault(v, []).append(i)

    return sum([max(x) - min(x) for _, x in d.items()])

0

HugoRune的答案利用了不寻常的评分函数,但我们实际上可以做得更好:假设有d个不同的非唯一值,则解决方案要想达到最优,唯一需要的是输出中的前d个值必须包含这些值的任意组合,同样地,输出中的最后d个值必须包含这些值的任意(即可能是不同的)组合。(这意味着所有唯一数字都出现在每个非唯一数字的第一个和最后一个实例之间。)

非唯一数字的第一个副本的相对顺序不重要,同样最后一个副本的相对顺序也不重要。 假设值1和2在输入中都出现了多次,并且我们已经建立了一个符合我在第一段给出的条件的候选解,其中1的第一个副本在位置i处,而2的第一个副本在位置j>i处。现在假设我们交换这两个元素。元素1已经向右移动了j-i个位置,因此它的分数贡献将下降j-i。但是元素2已经向左移动了j-i个位置,因此它的分数贡献将增加j-i。这些会相互抵消,从而总分数不变。

现在,可以通过以下方式交换元素来实现任何元素的排列:将在位置1的元素与应在位置1的元素交换,然后对于位置2进行相同的操作,依此类推。在第i步之后,排列的前i个元素是正确的。我们知道每次交换都不会改变评分函数,而置换只是交换序列,因此每个置换也都不会改变评分函数!这适用于输出数组两端的d个元素。

当一个数字存在3个或更多的副本时,只有第一个和最后一个副本的位置对该数字的距离有贡献。中间的副本放在哪里并不重要。我将在两个端点的d个元素块之间的元素称为“中央”元素。它们由唯一的元素组成,以及出现至少3次的所有非唯一元素的若干个副本。与之前一样,很容易看出这些“中央”元素的任何排列都对应于一系列交换,并且任何这样的交换都不会改变总分数(实际上比之前更简单,因为交换两个中央元素甚至不会改变这些元素的得分贡献)。

这导致了一个简单的O(nlog n)算法(如果您在第一步使用桶排序,则为O(n))来从长度为n的输入数组X生成解决方案数组Y:

  1. 对输入数组X进行排序。
  2. 通过一次遍历X来计算不同的非唯一元素数量。将其称为d。
  3. 将i、j和k设置为0。
  4. 当i < n时:
    • 如果X[i+1] == X[i],则有一个非唯一元素:
      • 设置Y[j] = Y[n-j-1] = X[i]。
      • 将i增加两次,并将j增加一次。
      • 当X[i] == X[i-1]时:
        • 设置Y[d+k] = X[i]。
        • 增加i和k。
    • 否则,我们有一个唯一元素:
      • 设置Y[d+k] = X[i]。
      • 增加i和k。

1
在三个项目的情况下,您对总离散度的衡量是什么?如果一种类型的项目位于p0、p1和p2位置,“位置差异”可能包括: - Paddy3118
1
@Paddy3118:我想我明白你的意思了。我一开始假设总离散度将通过查找相同数字对之间的位置差来计算 这些数字之间没有任何第三个副本,因此如果有 k 个特定数字的副本,则总和中将有 k-1 项。但也可能是应该包括所有相同数字对之间的位置差,这将产生 k*(k-1)/2 项——问题并没有说明哪种方法是所需的。在这种情况下,我的简单算法可能不起作用。 - j_random_hacker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接