【问题标题】:Find Duplicates in an array in O(N) time在 O(N) 时间内查找数组中的重复项
【发布时间】:2011-11-28 22:18:32
【问题描述】:

有没有办法在 O(N) 时间内找到 N 个元素的数组中的所有重复元素?

例子:

输入:11, 29, 81, 14, 43, 43, 81, 29

输出:29, 81, 43

对输入进行排序并进行线性扫描以检测重复项会破坏顺序并给出输出:29,43,81。

根据给定的数组对另一个索引数组{0,1,...N-1}进行键排序得到{1,4,2},然后对结果索引集进行排序得到{1,2,4}将给我们{29,81,43},但这需要@987654327 @时间。

有解决这个问题的 O(N) 算法吗?

附:我忘了补充:我不想使用哈希表。我正在寻找非哈希解决方案。

【问题讨论】:

  • 如果空间不是限制,将每个元素存储在哈希中。发生碰撞时,您有一个副本。
  • @Anurag:最佳情况/平均运行时间 O(n) 但最坏情况 O(n2)。
  • @Anurag:你说的散列究竟是什么意思?
  • @Charles Bailey:我认为他的意思是地图。
  • 您为什么要寻找非哈希解决方案?

标签: c++ algorithm


【解决方案1】:

我相信a trie 是一个很好的解决方案(体面的内存使用,可用于立即确定条目是否已被看到,从而保持顺序,并具有线性复杂性)。

如果您将元素插入到 trie 中,就好像它们是每个节点中每个数字(从 MSD 开始)的字符串一样,您可以以 O(m N ) 其中 m 是以 10 为基数的数字的平均长度。

您只需遍历所有条目并将它们插入到 trie 中。每当一个元素已经存在时,您就跳过它并继续下一个。此中的重复项(与我之前对基数排序的回答不同)立即找到,而不是在最后一次迭代中找到。

我不确定您是否会从此处使用后缀树中受益,因为输入到 trie 中的字符的“基数”只有 10(与 ANSI 字符串的基数 128 相比),但这是可能的.

【讨论】:

  • 不客气。谢谢@amit,特别是昨晚你对我的耐心!
  • 使用 trie 使得算法 O(N log N)。更糟糕的是,与 Kaganar 建议的 hashmap 相比,所有 trie 的实现都非常慢。如果您想要速度,请使用他的答案(实际上是 O(N))。
【解决方案2】:

如果您的输入都是小整数,您可以使用 counting sort,它在 O(n) 时间内运行并需要 O(m) 空间,其中 m 是可能输入范围的大小。

作为空间优化,使用位数组并使用单个位(而不是计数)来存储您之前是否看过该项目就足够了。

【讨论】:

  • 这样做会告诉你哪些元素是重复的。要以原始顺序获取元素:将哪些元素是重复的存储在位向量中,然后对原始数据进行另一次线性扫描,输出重复的元素,仍然是 O(n),并给出您按所需顺序排列元素。
【解决方案3】:

听起来您不赞成分配任何额外的空间。尽管如此,哈希表仍然是速度的正确解决方案。老实说,大多数用于简单数据(例如整数)的哈希表实现都因其“万能”的性质而过于繁重,以至于我只是根据自己的需要自行推出。当您需要相对较少的工作时,它可以将慢代码转换为快代码。

另外,如果您对哈希表的反对意见是它们破坏了顺序,那么也许您可能希望稍微不同地使用它们以获得预期的 O(n),同时保持顺序:

创建一个哈希表,将您的数组元素映射到两位作为从零到三的计数字段,并将三十位作为元素数组的索引。除非您的数组中有超过 10 亿个值,否则 30 位就足够了。这样一来,您的哈希值就只是一个 32 位字。

遍历数组中的元素。如果某个元素不在表中,则将该值插入哈希表并将计数字段设置为零。存储它时索引部分是什么并不重要。如果元素在表中并且计数字段为零,则将其增加到 1 并将元素索引与新的计数字段值一起存储。如果 count 字段已经是 1 或更大,请将其设置为 2,并且不要触及存储的索引 - 保持原样。

再次遍历数组中的元素。查找每个元素,如果它的索引是存储的那个并且关联的计数字段大于零,则将其打印出来。

这应该会在 O(n) 时间内以正确的顺序为您提供您想要的东西。但是,它使用了由于未知原因而不需要的哈希表。我强烈建议您要么接受这样的解决方案,要么解释其局限性,以便获得更有针对性的解决方案。

【讨论】:

    【解决方案4】:

    如果您知道可以这样做的最大值,
    有一个单独的数组,长度为最大值

     int[max] secondarray;
    
        for(int i=o;i<arrayFirst.length;i++){
            if(secondarray[arrayFirst[i]]==0){
                secondarray[arrayFirst[i]]==arrayFirst[i];
             }else{
                 result.add(arrayFirst[i]);
              }
         }
    

    【讨论】:

      【解决方案5】:

      您可以在 O(n) 中执行此操作,但这需要数组为整数。为此所需的空间可以是订单大小 -2^32 到 2^32。 您需要做的是找到原始数组(arrayorig)的最大值和最小值。然后制作两个数组 (arraynew+) 和 (arraynew-) 。

      如果arrayorig 中的所有值都是+,则(arraynew+) 的大小将为max(arraorig)-min(arrayorig),否则(arraynew+) 的大小将为max(arrayorig)。

      如果所有值都是正数,则大小 (arraynew-) 将为零,否则它们将等于 min(arrayorig) 的绝对值。

      然后您可以遍历arrayorig并在与arraorig的值对应的索引处将(arraynew-)或(arraynew +)的值增加1,如果该值为正,则应该对(arraynew +)进行增量,否则如果它的负增量应该在 (arraynew-) 的索引处对 (arraynew-) 进行,该索引等于 arrayorig 的绝对值。 那么 (arraynew+) 和 ((arraynew-) 的所有值 >1 的索引都是 arrayorig 的不同值。

      【讨论】:

        【解决方案6】:
         void printRepeating(int arr[], int size)
         {
         int i;
           printf("The repeating elements are: \n");
         for (i = 0; i < size; i++)
         {
         if (arr[abs(arr[i])] >= 0)
          arr[abs(arr[i])] = -arr[abs(arr[i])];
         else
          printf(" %d ", abs(arr[i]));
         }
          }
        

        【讨论】:

          【解决方案7】:

          查找重复项与排序一样难。最好的办法是利用输入的某些属性来获得 O(N) 排序。

          【讨论】:

          • 通常识别重复项需要 O(N^2) 操作,但在这个特定问题中,整数必须在可以适合数组索引的范围内。您可以通过客厅技巧来利用此属性。将兔子所属的数字放在索引处,并找出不合适的数字,从而将兔子从帽子中拉出来。
          猜你喜欢
          • 2011-08-09
          • 2013-02-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-06-16
          • 2020-01-22
          • 1970-01-01
          • 2012-08-06
          相关资源
          最近更新 更多