【问题标题】:find the duplicate number with O(1) space and O(n) time找到具有 O(1) 空间和 O(n) 时间的重复数字
【发布时间】:2017-09-14 14:06:28
【问题描述】:

我正在用 leetcode 解决一个问题

给定一个包含 n + 1 个整数的数组 nums,其中每个整数都介于 1 和 n(含)之间,证明至少存在一个重复数。假设只有一个重复数,在O(n)时间和O(1)空间复杂度内找到重复的数

class Solution(object):
    def findDuplicate(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        xor=0
        for num in nums:
            newx=xor^(2**num)
            if newx<xor:
                return num
            else:
                xor=newx

我接受了解决方案,但我被告知它既不是 O(1) 空间也不是 O(n) 时间。

谁能帮我理解为什么?

【问题讨论】:

标签: python algorithm data-structures


【解决方案1】:

您的问题实际上很难回答。通常在处理复杂性时,有一个假定的机器模型。 A standard model 假设当输入大小为 n 时,内存位置的大小为 log(n) 位,并且对大小为 log(n) 位的算术运算为 O(1)。

在此模型中,您的代码在空间上不是 O(1),在时间上不是 O(n)。您的 xor 值有 n 位,这不适合固定的内存位置(它实际上需要 n/log(n) 个内存位置。同样,它不是 O(n) 时间,因为算术运算已开启大于 log(n) 位的数字。

要在 O(1) 空间和 O(n) 时间内解决您的问题,您必须确保您的值不会变得太大。一种方法是对数组中的所有数字进行异或运算,然后您将得到1^2^3...^n ^ d,其中d 是重复的。因此,您可以对数组的总异或异或1^2^3^..^n,并找到重复值。

def find_duplicate(ns):
    r = 0
    for i, n in enumerate(ns):
        r ^= i ^ n
    return r

print find_duplicate([1, 3, 2, 4, 5, 4, 6])

这是 O(1) 空间和 O(n) 时间,因为 r 从未使用比 n 更多的位(即大约 ln(n) 位)。

【讨论】:

  • 另一种方法是对所有值进行异或运算,如果 n 不是 2^x-1,则将结果与 n+1、n+2、... 进行异或运算,直到达到 2 的幂负 1,我认为它需要更少的异或运算,但不是那么优雅。
  • 计算 xor 1..n 似乎效率低下。在任何语言中使用模数运算(如 C 中的无符号数)的相同主题的另一个解决方案是将数字相加,然后减去它们的总和(即 ((unsigned)n)*(n+1)/2)。
  • @HansOlsson 这有一个缺点是总和可能会变得太大,使用 xor 解决方案您只需要与 n 一样多的位。
  • @HansOlsson 同样,您可以根据 n mod 4. geeksforgeeks.org/calculate-xor-1-n 快速计算 1^2^3...^n 的值。我明确计算了它,因为它更容易解释并且不会改变复杂性。
  • @MarkRansom 是的,但跨二分法模型假设内存位置的大小为 ~lg(n)。
【解决方案2】:

您的解决方案不是 O(1) 空间,这意味着:您的空间/内存不是恒定,而是取决于输入!

newx=xor^(2**num)

这是对log_2(2**num) = num 位的按位异或,其中num 是您的输入数字之一,产生log_2(2**num) = num 位结果。

所以n=10 = log_2(2^10) = 10 bitsn=100 = log_2(2^100) = 100 bits。它呈线性增长(不是恒定的)。

它也不在你得到的 O(n) 时间复杂度内:

  • 对所有 n 个数字的外循环
    • 和一个非常量/非 O(1) 内循环(见上文)
    • 假设:XOR 在输入的位表示方面不是恒定的
      • 并不总是这样处理;但物理学支持这一说法(钱德拉塞卡极限、光速……)

【讨论】:

  • 10 个数字将产生 11 位,而不是 1024 位。 100 个数字将是 101 位。空间复杂度是线性的,而不是指数的。
  • 10位可以表示1024个数,位数没有你说的那么大,只有计算出来的数要异或。
  • 您在正确的轨道上回答了这个问题,但是您对所需位数的计算还有很长的路要走。当你修复它时告诉我。
  • XOR 实际上是常数(不依赖于位模式),只是它随着整数大小的增长而增长。
  • 我的术语 bit-representation 包括长度(我想在复杂性理论中也这样使用)。
【解决方案3】:

这个问题必须用链表弗洛伊德算法来解决。

将数组转换为链表。有 n+1 个位置,但只有 n 个值。

例如如果你有这个数组:[1,3,4,2,2] 将它转换为链表。

指点的工作原理

从索引 0 开始,查看该位置的哪个元素。它是 1。那么索引 0 将指向 nums1。 0 指向 3。然后找出 3 指向哪个值。这将是 nums[3] 等等。

现在您将其转换为链表,您必须使用 Floyd 的兔子和乌龟算法。基本上你有两个指针,慢的和快的。如果有循环,慢速指针和快速指针会在某个时间点相遇。

from typing import List
class Solution:
    def findDuplicate(self, nums: List[int]) -> int:
        # slow and fast are index
        slow,fast=0,0
        while True:
            slow=nums[slow]
            fast=nums[nums[fast]]
            if slow==fast:
                break
        # so far we found where slow and fast met.
        # to find where cycle starts we initialize another pointer from start, let's name is start
        # start and slow will move towards each other, and meeting point will be the point that you are looking for
        start=0
        while True:
            slow=nums[slow]
            start=nums[start]
            if slow==start:
                return slow

请注意,第一个索引之后的所有元素都不会指向索引 0 处的值。因为我们的范围是 1-n。我们正在跟踪我们通过 nums[value] 指向的位置,但由于没有值会是 0,所以没有任何东西会指向 nums[0]

【讨论】:

    猜你喜欢
    • 2012-01-02
    • 2012-08-06
    • 2011-08-09
    • 1970-01-01
    • 2015-05-25
    • 1970-01-01
    • 2011-08-24
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多