关于算法:数组作业问题

Array Homework Question

您将得到一个整数介于1和1000000之间的数组。数组中有两个整数。你如何确定哪一个?你能想出一个办法用一点额外的记忆来做吗?

Algo:

  • 解决方案1:
  • 有一个哈希表
  • 遍历数组并将其元素存储在哈希表中
  • 一旦找到一个已经在哈希表中的元素,它就是dup元素
    • 赞成的意见:

      • 它在O(N)时间内运行,只有1次通过

      欺骗:

      • 它使用O(N)额外内存
  • 解决方案2:
  • 使用合并排序(o(nlogn)时间)对数组排序
  • 再次分析,如果看到一个元素两次,就得到了dup。
    • 赞成的意见:

      • 它不需要额外的内存

      欺骗:

      • 运行时间大于O(N)

你们能想出更好的解决办法吗?


这个问题有点模棱两可;当请求是"哪一个"时,是返回被复制的值,还是返回被复制的值的序列中的位置?如果是前者,下面三个解决方案中的任何一个都会起作用;如果是后者,第一个解决方案是唯一有帮助的解决方案。

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

生成位图;在迭代数组时设置第n位。如果位已设置,则发现一个副本。它以线性时间运行,适用于任何大小的数组。

将使用数组中尽可能多的值创建位图。在迭代数组时,检查数组中的第n位。如果设置好了,您就找到了副本。如果不是,那么设置它。(这样做的逻辑可以在这个wikipedia条目中的伪代码中看到,也可以使用system.collections.bitarray类。)

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

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

解决方案3:(假设数组长度=1000001)

  • 对数组中的所有整数求和。
  • 从中减去整数1到1000000的和。
  • 剩下的就是你的复制值。
  • 这几乎不需要额外的内存,如果同时计算和,可以一次完成。

    缺点是你需要做整个循环来找到答案。

    其优点是简单,而且事实上,它比其他解决方案运行得更快。


    假设1到1000000之间的所有数字都在数组中,1到1000000之间的所有数字之和是(1,000,000)*(1,000,000 + 1)/2 = 500,000 * 1,000,001 = 500,000,500,000

    所以只要把数组中的所有数字加起来,减去500000,就可以得到两次出现的数字。

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

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

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


    此python代码是对quicksort的修改:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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)中发现了一个副本。它在堆栈上使用了额外的内存,但可以重写为只使用原始数据的一个副本,我相信:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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)

    使用对pop()的调用来产生更大和更小的破坏原始内容的列表理解。如果从arr中移除大小部分后arr不是空的,那么必须有一个副本,并且必须是轴。

    代码在排序后的数据上遇到了常见的堆栈溢出问题,因此需要一个随机透视或迭代解决方案来对数据进行排队:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    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的排序算法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    def findDuplicate(arr):
        arr = sorted(arr)
        prev = arr.pop(0)
        for element in arr:
            if element == prev:
                return prev
            else:
                prev = element
        return None


    提示:使用xor a==0和0 xor a==a的属性。


    我建议不要对数组进行排序然后进行检查,而是编写一个比较排序函数的实现,该函数在发现dup后立即退出,这样就不需要额外的内存(显然取决于您选择的算法)和最坏的O(nlogn)时间(同样,取决于算法),而不是最佳的(和平均的)。e,取决于……)案例o(非登录)时间。

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

    http://en.wikipedia.org/wiki/mergeu排序


    通过按应该的位置对整数进行排序。如果你发现"碰撞",你会发现正确的数字。

    空间复杂性o(1)(与可重写的空间相同)时间复杂度小于0(n),因为在到达终点之前,您会静态地找到collison。


    1
    2
    def singleton(array):
      return reduce(lambda x,y:x^y, array)


    找到所有副本的问题如何?这个能在以下时间内完成吗时间?(排序和扫描)(如果要还原原始数组,请携带原始索引并在结束后重新排序,这可以在o(n)时间内完成)


    作为解决方案(2)的变体,可以使用基数排序。没有额外的内存,将运行线性时间。你可以说时间也会受到数字表示的大小的影响,但是你已经给出了限制:基数排序在时间o(k n)中运行,其中k是你可以在每次传递中对ar排序的位数。这使得整个排序算法O(7N)加上检查重复数字的O(N),即O(8N)=O(N)。

    赞成的意见:

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

    欺骗:

    • 需要八个O(N)通行证。