【问题标题】:Given an array with multiple repeated entries, fnd one repeated entry O(N) time and constant space给定一个包含多个重复条目的数组,找到一个重复条目 O(N) 时间和常数空间
【发布时间】:2011-05-13 03:39:06
【问题描述】:

我们得到了一个大小为 N 的数组,其中包含 0 到 N-2 范围内的整数,包括 0 到 N-2。

数组可以有多个重复的条目。我们需要在 O(N) 时间和常数空间中找到重复的条目之一。

我正在考虑获取数组中所有整数的乘积和总和,以及 0 到 N-2 范围内所有数字的乘积和总和。

然后,总和的差和乘积的除法将给我们两个方程。如果假设只有两个重复条目,这种方法会起作用,但由于可能不止两个,我认为我的方法失败了。

有什么建议吗?

编辑:数组是不可变的。我意识到这是一条重要的信息,我很抱歉我之前忘记包含这个信息。

【问题讨论】:

    标签: java arrays algorithm


    【解决方案1】:

    扫描数组并将每个元素添加到集合中。如果该项目已经存在于集合中 - 你有一个骗子。

    【讨论】:

      【解决方案2】:

      初始化一个大小为 N-2 的位数组,所有条目都为 0。每个索引将代表 0 到 N-2 范围内的所有项目。

      通过设置bitarray[number] == 1 循环遍历您的数组并将项目添加到您的位数组。如果元素已经包含一个 1,那么你已经添加了你的元素,立即返回。

      如果您到达数组末尾但没有找到重复项,则返回 -1。

      【讨论】:

      • 严格来说,位数组不需要常数空间,而是需要O(N)量级的空间。
      • 我们不能创建另一个数组,因为解决方案需要在空间复杂度方面保持不变。
      【解决方案3】:

      假设我们被允许在适当的位置更改数组,当您遍历数组时将每个元素与该“位置”上的元素交换(例如,如果当前元素是 curr,则将其与 a[curr] 交换)但是如果 a[curr ] 已经有 curr 那么你就知道 curr 是重复的。

      a = array...
      for i = 0; i < length(a); i++
        curr = a[i]
        if a[curr] == curr:
          return duplicate curr
        swap(a[i], a[curr])
        # Now a[curr] == curr and so if it happens again we know it is a duplicate. 
      

      这将是 O(n) 和常数空间。

      【讨论】:

      • 如果a[0]在进入循环之前包含0会发生什么?
      • 这完全是假的。假设元素 0 的值为 1,元素 1 的值为 5(这是重复值)。当你看到元素 0 的值为 1 时,你将值与元素 1 交换,所以现在元素 1 的值为 5,元素 1 的值为 1。然后循环循环并说“Hurrah - 元素 1 的值为 1 - 它必须是复制!”。错误的。此外,您现在永远无法检查元素 0 中的值 5。如果元素 2 包含 0,那么您只需在检查 2 之后交换 0 和 5,然后继续,仍然永远不会检查 5。
      【解决方案4】:

      this SO Question 启发,我想我会选择首先使用wikipedia 中的O(n) 算法(尽管不一定快)对数组进行就地排序(@987654323 中的图形排序演示很好@),然后循环遍历结果数组以查找下一个数字等于当前数字的位置。

      【讨论】:

      • O(n) 排序算法非常特殊,需要额外的假设。除此之外,由于您的额外循环,您的方法无论如何都比 O(n) 慢。
      • 在 n 中排序将使用位域方法......这不是恒定空间。如果你已经得到了,那么无论如何都不需要排序。
      • @PartlyCloudy:如果他可以在 O(n) 中排序,那么另一个 O(n) 的循环将把它留在 O(n) 中。
      • 对不起,我的第一条评论是错误的。最坏情况的复杂度是 max(sorting, O(n))。
      • 我认为,所谓的 O(n) 排序在这里是一种“作弊”。您可以使用二进制基数排序在恒定的额外空间中执行此操作,但是(或任何其他基数排序)需要时间 Theta(k * N),其中 k 是整数中的位数。但是N-2 不能大于 2^k,因为整数的值范围高达 N-2。所以真的,时间是 Theta(N log N),只是“Java”标签可能会让我们假设一个整数是一个 int,因此 N 以一个常数为界。但如果我们假设,复杂性分析是无关紧要的:我们可以在 O(1) 中冒泡排序 2^32 个值。
      【解决方案5】:

      这是一个很好的治疗方法。在解决这个问题之前,它会通过一些更简单的问题。

      http://aperiodic.net/phil/archives/Geekery/find-duplicate-elements.html

      它包含一个解决方案,当您可以修改输入数组时,以及当您不能修改时。

      在链接失效的情况下的简要总结:数组索引从 0 .. N-1 开始,数组值从 0 .. N-2 开始。因此,每个数组元素都可以被视为数组本身的索引(或“指针”):元素i“指向”元素ra[i]ra[i] 指向ra[ra[i]] 等等。通过反复遵循这些指针,我最终必须进入一个循环,因为如果不重新访问某个节点或其他节点,我们当然不能永远走下去。

      现在,最后一个元素 N-1 没有被任何其他元素指向。因此,如果我们从那里开始并最终进入一个循环,那么沿途的某个地方必须有一个元素可以从两个不同的地方到达:我们第一次走的路线,以及属于循环一部分的路线。像这样的:

        N-1 -> a1 -> a2 -> a3
                     ^       \
                    /         v
                  a6 <- a5 <- a4
      

      在这种情况下,a2 可以从两个不同的地方到达。

      但是一个可以从两个不同的地方到达的节点正是我们正在寻找的,在数组中的一个副本(两个不同的数组元素包含相同的值)。

      接下来的问题是如何识别a2,答案是使用Floyd's cycle-finding algorithm。特别是它告诉我们在 O(N) 时间和 O(1) 空间中循环的“开始”。

      【讨论】:

      • 我仍然不完全确定指向数组的元素是什么意思。能否详细说明一下?
      • @efficiency:每个元素的值是小于 N 的数字,因此是数组的索引。因此,如果 ra[N-1] 为 4,那么下一个要查找的位置是 ra[4],它可能是 7,依此类推。
      • 这就是我的怀疑。再次感谢。
      • 从n-1(最后一个)元素开始有什么意义?难道我们不能也从 0 开始,仍然打循环吗?
      • @AsadIqbal:没有。例如,如果元素 0 指向它自己,而没有其他任何东西指向它,那么我们会找到一个循环,但我们没有找到一个从两个不同位置指向的元素,因为那个循环没有“尾巴”。位置 N-1 是特殊的(它没有被指向),这意味着它引导我们进入的任何循环都必须有一个尾巴。当然,我们可以解决这个问题,如果 0 对我们没有帮助,则从 1 重新开始,但我认为最好从保证工作的地方开始。
      【解决方案6】:

      尝试用其他数据结构来思考。一些数据结构,如 HashSet,在添加或搜索时不会遍历当前元素,这会保留您的 O(n)。

      HashSet hSet = new HashSet();
      
      for(int i = 0; i < array.length(); i++){    
          if(hSet.contains(array[i])
             return array[i];
          else
             hSet.add(array[i]);
      }
      return -1;
      

      虽然我不确定这是否能满足您的内存需求,extraneon 之前发布的带有第二次遍历的就地排序的帖子可能更符合您的需求

      【讨论】:

      • 再一次,我们不能创建另一个数据结构,因为这会违反常量空间限制。
      【解决方案7】:

      (抱歉还不能添加 cmets....)

      @Blastfurnace 啊.. 很好,for 循环需要先检查

      if a[i] == i:
        continue  # Don't swap with yourself!
      

      如果数组是不可变的,那么您可以只保留以下元素,即跳过直到 a[i] == i 然后从 a[i] 转到 a[a[i]]。这将遇到一个循环,然后我们可以使用“如何检测链表中的循环”解决方案(保持 2 个指针,其中一个以 1 的速度移动,另一个以 2 的速度移动,当它们相遇时你就知道你已经达到了循​​环)。

      如果我们可以更改数组然后将其原封不动地返回,那么我们可以作弊:) 从 i = 0 开始,如果 a[a[i]] 还不是,则将它变成一个负整数,如果它已经是负数,那么我们知道元素 a[i] 已被访问过两次。在返回之前将所有负数转为正数(对于 0 使用 MIN_INT)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-01-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多