我该如何在O(n)时间内在已排序的数组中找到一个出现奇数次的数字?

40

我有一个问题,思考了很多次却没有想出答案...所以在这里发布这个问题。也许我能得到别人的观点,尝试让它起作用...

问题是:给定一个排序好的数组,其中包含一个出现奇数次数的值和其他出现偶数次数的值。我们需要在log n时间内找到解决方案。

在O(n)时间内找到解决方案很容易,但是在log n时间内执行看起来相当棘手。


4
这个数组里的数字是连续的整数吗?目前为止似乎没有比((log N)^2)更好的答案,但也没有直接利用数组已排序的特性。 - Pete Kirkham
3
酷毛病!他们在哪里布置这样好的问题作业? :) - Kris
1
@AGeek 请在您的作业接受期结束后发布解决方案。谢谢。 - Dave O.
1
@Bragboy 如果所有问题都像这个一样难回答,那将会很困难^^。 - Dave O.
1
@joe snyder和其他任何人:解决O(log(n)^2)运行问题是微不足道的。甚至运行在O(log(n))的解决方案尚未被发现(!) - 或者证明不存在这样的算法。 - Dave O.
显示剩余10条评论
14个回答

0
使用哈希表。
For each element E in the input set
    if E is set in the hash table
         increment it's value
    else        
         set E in the hash table and initialize it to 0

For each key K in hash table
    if K % 2 = 1
        return K

由于这个算法是2n,所以它属于O(n)。


0

我们没有关于数组内部长度分布以及整个数组的任何信息,对吧?

因此,数组长度可能是1、11、101、1001或其他一些长度,至少为1且没有上限,并且必须包含至少1种类型的元素(“数字”),最多包含(长度-1)/2 + 1个元素,总大小为1、11、101:1、1到6、1到51个元素等等。

我们应该假设每种可能的大小具有相等的概率吗?这将导致子数组的中间长度为size/4,对吧?

一个大小为5的数组可以被分成1、2或3个子列表。

如果我们深入细节,显而易见的事情并不那么明显。

一个大小为5的数组可以通过一种方式“分割”成一个子列表,但这种方式有争议,是否称之为“分割”都说不清楚。它只是一个由5个元素组成的列表(aaaaa)。为避免混淆,让我们假设列表内部的元素是有序字符,而不是数字(a、b、c等)。

分成了两个子列表,它们可能是(1, 4),(2, 3),(3, 2),(4, 1)。(abbbb, aabbb, aaabb, aaaab)。
现在让我们回到之前提出的问题:'划分'(5)应该假定与这4个划分成两个子列表的概率相同吗?还是应该将它们混合在一起,并假定每个分割都均等地可能,(1/5)?
或者我们是否可以在不知道子列表长度概率的情况下计算解决方案?

从我的角度来看,问题描述非常清晰:一个包含排序数字的长度为n的数组。每个数字出现偶数次,除了其中一个数字出现奇数次。除此之外,没有任何其他含义! (a),(a,a,a),(a,...(2*i次 a)...,a,b)等都是有效的输入。 - Dave O.
到目前为止还没有问题,但是要决定哪个算法最适合查找所需的数字,我们必须对典型数组进行假设,了解每种情况发生的频率,这对于无限可能的问题来说并不容易。也许您可以展示一个产生长度为5的所有数组或所有长度不超过5的数组的算法,或者使用其他限制条件,但是您决定如何生成这些数组的方式将会影响概率。 - user unknown

0

线索是你正在寻找log(n)。这比n小。

一次一个地遍历整个数组?那是n。那行不通。

我们知道数组中的前两个索引(0和1)应该是相同的数字。如果奇数在它们之后,50和51也是如此。

因此,在数组中找到中间元素,将其与右侧元素进行比较。如果数字的变化发生在错误的索引上,我们就知道数组中的奇数在它之前;否则,它在之后。通过一组比较,我们确定目标所在的数组的哪一半。

从那里继续。


1
如果探针的值没有变化会怎样? - Dave O.

-3

假设索引从0开始。二分查找最小的偶数i,使得x [i]!= x [i + 1];您的答案是x [i]。

编辑:由于公众要求,这里是代码

int f(int *x, int min, int max) {
  int size = max;
  min /= 2;
  max /= 2;
  while (min < max) {
    int i = (min + max)/2;
    if (i==0 || x[2*i-1] == x[2*i])
      min = i+1;
    else
      max = i-1;
  }
  if (2*max == size || x[2*max] != x[2*max+1])
    return x[2*max];
  return x[2*min];
}

@Dave:感谢您在不等待答案的情况下将分数拿走。现在,您有i = 2j和x [i] == x [i + 1],在下一次迭代中,取j = j / 2,i = 2j。 - David Lehavi
@David Lehavi 让 x := {a, a, b, b, c}。第一次探测:i == 2 => j == 1。x[i] == x[i+1] == b(第一个b)。第二次迭代:new_j == 0,new_i == 0。这就是我困惑的地方。此外,在我的例子中,没有一个偶数 i 满足 x[i] != x[i+1]。 - Dave O.
@Dave 我已经修改了我的答案来回答你的问题。 - David Lehavi
我不理解第二次迭代。 “first”和“last”是最小和最大索引,所以一开始是0和4-对吧? first = first / 2 = 0/2 = 0,last = last / 2 +1 = 4/2 +1 = 3(!=您的答案中的2)。 此外,请注意,在a={0,0,1}中,没有偶数i使得a[i]!= a[i+1]-即使您的算法正确,您的措辞也不正确。 您能否编写一个函数“int oddNumber(int *array,int minIndex,int maxIndex)”(索引包括在内)? 预先感谢您。 - Dave O.
1
@David Lehavi 感谢您的努力。由于您的解决方案是有效的 C 代码,您可以将其复制粘贴到您喜欢的 IDE 中进行编译,并像我一样失败(a: {1,2,2})。亲爱的 David,我并不是要抨击您!我非常感兴趣这个解决方案(以至于我会开始一个赏金)。当我听说有人想出了一个解决方案时,我想确保它是正确的,因此我密切关注错误。也许您在推理上犯了错误,或者您正在正确的轨道上..祝您赢得赏金。问候。 - Dave O.
显示剩余4条评论

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