【问题标题】:Array Homework Question阵列作业问题
【发布时间】:2009-07-20 00:47:34
【问题描述】:

给定一个包含 1 到 1,000,000 之间整数的数组。一个整数在数组中出现两次。你怎么能确定是哪一个?你能想出一种使用少量额外内存的方法吗?

算法:

  • 解决方案1:
    1. 有一个哈希表
    2. 遍历数组并将其元素存储在哈希表中
    3. 一旦你找到一个已经在哈希表中的元素,它就是 dup 元素
      优点:
      • 它在 O(n) 时间内运行,并且只有 1 次通过
      缺点:
      • 它使用 O(n) 额外内存
  • 解决方案2:
    1. 使用归并排序对数组进行排序(O(nlogn) 时间)
    2. 再次解析,如果你看到一个元素两次,你得到了 dup。
      优点:
      • 它不使用额外的内存
      缺点:
      • 运行时间大于O(n)
  • 你们能想出更好的解决方案吗?

    【问题讨论】:

    • 一个做得好的家庭作业问题的好例子,+1
    • IIRC,合并排序是面向列表的,而不是面向数组的。转换肯定会消耗内存。使用面向数组的快速排序可能会更好。
    • 据我所知,归并排序仍然可以用于数组。但是,我只是评论说这是关于 SO 的家庭作业问题应该如何的一个典型例子。已发布问题以及已完成的工作。
    • 数组的长度正好是 1,000,001,并且 1 到 1,000,000 之间的每个数字在这个数组中只包含一次,除了一个恰好包含两次的数字之外,这是否正确?跨度>

    标签: arrays algorithm


    【解决方案1】:

    这个问题有点模棱两可;当请求是“哪一个”时,是返回被复制的,还是被复制的序列中的位置?如果是前者,以下三种解决方案中的任何一种都可以;如果是后者,那么只有第一个有帮助。

    解决方案 #1:假设数组是不可变的

    构建位图;在遍历数组时设置第 n 位。如果该位已设置,则您已找到重复项。它以线性时间运行,适用于任何大小的数组。

    位图将使用数组中可能的值尽可能多的位创建。当您遍历数组时,您会检查数组中的第 n 位。如果已设置,则您已找到重复项。如果不是,则设置它。 (可以在 Bit arrays 的此 Wikipedia 条目中的伪代码中看到这样做的逻辑,或使用 System.Collections.BitArray 类。)

    解决方案 #2:假设数组是可变的

    对数组进行排序,然后进行线性搜索,直到当前值等于前一个值。使用最少的内存。在比较操作期间更改排序算法以检测重复项并提前终止的奖励积分。

    解决方案 #3:(假设数组长度 = 1,000,001)

    1. 对数组中的所有整数求和。
    2. 从中减去整数 1 到 1,000,000 的总和。
    3. 剩下的将是您的重复值。

    这几乎不需要额外的内存,如果同时计算总和,可以一次性完成。

    缺点是需要做整个循环才能找到答案。

    优点是简单,而且很有可能实际上比其他解决方案运行得更快。

    【讨论】:

    • 只有当数组包含 1 到 1,000,000 之间的所有整数加上一个双精度数(即 1,000,001 个元素)时才有效,不是吗?
    • 好点;我假设数组包含所有整数。太糟糕了;这是一个很好的答案:)
    • 这和哈希表一样,你使用的是位图而不是哈希表,它减小了一点大小,但仍然使用 O(n) 额外的内存
    • 该位图需要 1,000,000 位或 122.07 kB,无论数组大小如何(最多为 1,000,001 个元素)。
    • @lavino:你的是 O(n) (最好的)并且使用 O(1) 额外空间。还有什么想要的?
    【解决方案2】:

    假设 1 到 1,000,000 的所有数字都在数组中,那么从 1 到 1,000,000 的所有数字之和为(1,000,000)*(1,000,000 + 1)/2 = 500,000 * 1,000,001 = 500,000,500,000

    因此,只需将数组中的所有数字相加,再减去 500,000,500,000,就剩下出现两次的数字。

    O(n) 时间,O(1) 内存。

    如果假设不成立,您可以尝试使用 Bloom Filter - 它们可以比哈希表更紧凑地存储(因为它们只存储存在的事实),但它们确实冒着误报的风险。不过,这种风险可以通过我们选择在布隆过滤器上花费多少内存来限制。

    然后我们可以使用布隆过滤器在 O(n) 时间内检测潜在的重复项,并在 O(n) 时间内检查每个候选者。

    【讨论】:

    • 这仅适用于范围内的所有整数都存在的情况。至少从我记得的关于高斯在学校的故事中……
    • 是的,没错。从我对这个问题的阅读中,我认为这是真的。但看起来我的阅读是错误的。
    • 概率性地做这件事有什么好处?
    • 内存仍然是 O(n),但系数要小得多(取决于您选择的误报率)。
    • @Jason - 布隆过滤器只有误报,没有误报。所以你不会错过正确的答案。您确实会冒误报的风险,但这可以通过表的大小来控制,然后您可以对剩下的几个进行 O(n) 检查。
    【解决方案3】:

    这个python代码是modification of QuickSort

    def findDuplicate(arr):
        orig_len = len(arr)
        if orig_len <= 1:
            return None
        pivot = arr.pop(0)
        greater = [i for i in arr if i > pivot]
        lesser = [i for i in arr if i < pivot]
        if len(greater) + len(lesser) != orig_len - 1:
            return pivot
        else:
            return findDuplicate(lesser) or findDuplicate(greater)
    

    我认为它在 O(n logn)) 中找到了一个重复项。它在堆栈上使用了额外的内存,但我相信它可以重写为仅使用原始数据的一份副本:

    def findDuplicate(arr):
        orig_len = len(arr)
        if orig_len <= 1:
            return None
        pivot = arr.pop(0)
        greater = [arr.pop(i) for i in reversed(range(len(arr))) if arr[i] > pivot]
        lesser = [arr.pop(i) for i in reversed(range(len(arr))) if arr[i] < pivot]
        if len(arr):
            return pivot
        else:
            return findDuplicate(lesser) or findDuplicate(greater)
    

    产生greaterlesser 的列表推导式通过调用pop() 来破坏原件。如果去掉greaterlesserarr不为空,那么一定有重复,一定是pivot em>。

    代码在排序数据上存在通常的堆栈溢出问题,因此需要随机枢轴或将数据排队的迭代解决方案:

    def findDuplicate(full):
        import copy
        q = [full]
        while len(q):
            arr = copy.copy(q.pop(0))
            orig_len = len(arr)
            if orig_len > 1:
                pivot = arr.pop(0)
                greater = [arr.pop(i) for i in reversed(range(len(arr))) if arr[i] > pivot]
                lesser = [arr.pop(i) for i in reversed(range(len(arr))) if arr[i] < pivot]
                if len(arr):
                    return pivot
                else:
                    q.append(greater)
                    q.append(lesser)
        return None
    

    但是,现在代码需要在循环顶部获取数据的深层副本,从而改变内存需求。

    计算机科学就这么多。天真的算法破坏了我在 python 中的代码,可能是因为 python 的排序算法:

    def findDuplicate(arr):
        arr = sorted(arr)
        prev = arr.pop(0)
        for element in arr:
            if element == prev:
                return prev
            else:
                prev = element
        return None
    

    【讨论】:

      【解决方案4】:

      我建议不要对数组进行排序然后检查,而是编写一个比较排序函数的实现,该函数在找到 dup 后立即退出,从而不需要额外的内存(显然取决于您选择的算法)和最坏情况 O(nlogn) 时间(同样,取决于算法),而不是最佳(和平均,取决于...)情况 O(nlogn) 时间。

      例如就地合并排序的实现。

      http://en.wikipedia.org/wiki/Merge_sort

      【讨论】:

        【解决方案5】:

        提示:使用 A XOR A == 0 和 0 XOR A == A 的性质。

        【讨论】:

        • 这有什么帮助?如果你把所有的数字异或在一起,比如说,不知道结果应该是什么,这不可能是诊断性的。
        • 如果数组包含 1,000,001 个元素,那么这个解决方案比计算总和更好(需要更少的内存)。计算 a[1] xor 1 xor a[2] xor 2...
        【解决方案6】:

        作为解决方案 (2) 的变体,您可以使用 radix sort。没有额外的内存,并且会运行 线性时间。您可以争辩说时间也受到数字表示的大小的影响,但您已经给出了界限:基数排序在 O(k n) 时间内运行,其中 k 是您每次通过时可以排序的位数。这使得用于排序的整个算法 O(7n) 加上用于检查重复数字的 O(n) - 即 O(8n)=O(n)。

        优点:

        • 没有额外的内存
        • O(n)

        缺点:

        • 需要八次 O(n) 次传递。

        【讨论】:

        • 我不确定我是否同意基数排序是 O(n)。在这种特殊情况下,密钥的分布是这样的,除了有一对重复之外,没有两个是相等的。对于要检查重复的列表中的任意最大值,基数排序的增长与其他有效排序完全相同,特别是它必须在多达 log(n) 次传递中最多对 n 个元素进行排序,因此基数是 O(n log(n ))。如果键分布是其他情况,例如固定大小的哈希值,并且键的数量明显大于键的域,则基数排序具有给定的 O(n) 复杂度。
        【解决方案7】:

        那么查找所有重复项的问题呢?这可以在不到 O(n ln n) 时间? (排序&扫描)(如果要恢复原始数组,携带原始索引,结束后重新排序,可以在O(n)时间内完成)

        【讨论】:

        • 使用计数排序有额外的内存,是的。或者,使用基数排序没有额外的内存(但是,你可以争辩说使用基数排序操作系统“有点”作弊,因为它实际上是 O(kn),其中 k 是最大位数,并且 k 与 log n 成正比 - - 然而,这个问题已经给出了界限,所以 k 是固定的。
        【解决方案8】:
        def singleton(array):
          return reduce(lambda x,y:x^y, array)
        

        【讨论】:

        • 适用于某些输入(例如,range(1000) + [101]),但不适用于其他输入(range(1001) + [101])。
        【解决方案9】:

        通过将整数排序在它们应该在的位置来对整数进行排序。如果你得到“碰撞”而不是你找到正确的数字。

        空间复杂度 O(1)(只是可以被覆盖的相同空间) 时间复杂度小于 O(n) 因为你会在结束之前统计发现冲突。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-10-26
          • 1970-01-01
          • 2011-04-14
          • 2010-12-28
          • 2016-09-14
          • 2012-02-26
          • 2012-08-01
          • 2011-08-18
          相关资源
          最近更新 更多