找到具有 O(1) 空间和 O(n) 时间的重复数字

Posted

技术标签:

【中文标题】找到具有 O(1) 空间和 O(n) 时间的重复数字【英文标题】:find the duplicate number with O(1) space and O(n) time 【发布时间】: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) 时间。

谁能帮我理解为什么?

【问题讨论】:

这可能是 Find repeating in O(n) and constant space 和/或 How to find time complexity of an algorithm 或 Time complexity of power() 的副本 【参考方案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]

【讨论】:

以上是关于找到具有 O(1) 空间和 O(n) 时间的重复数字的主要内容,如果未能解决你的问题,请参考以下文章

查找数组中重复的唯一元素+时间复杂度O(n)+空间复杂度O

在 O(1) 空间中的数组中查找重复元素(数字不在任何范围内)

51nod 1267 4个数和为0 思路:哈希map+避免重复的点

设计一个 O(n) 算法来找到一个不在 [0,n-1] 范围内的数字 [重复]

287. 寻找重复数

face++