在数组中查找重复 - 时间复杂度< O(n ^ 2)和恒定的额外空间O(1)。 (亚马逊访谈)

时间:2016-11-22 21:24:49

标签: python arrays algorithm

以下是问题陈述和解决方案。我无法理解解决方案背后的逻辑。

问题陈述:
给定包含n + 1个整数的数组nums,其中每个整数在1和n之间(包括1和n),证明必须存在至少一个重复的数字。假设只有一个重复的数字,找到重复的数字。

注意: 您不能修改数组(假设该数组是只读的)。 您必须仅使用常量O(1)额外空间。 您的运行时复杂度应小于O(n2)。 数组中只有一个重复的数字,但它可以重复多次。

样本输入:[3 4 1 4 1] 输出:1

leetcode上发布的问题的解决方案是:

class Solution(object):
    def findDuplicate(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        low = 1
        high = len(nums)-1

        while low < high:
            mid = low+(high-low)/2
            count = 0
            for i in nums:
                if i <= mid:
                    count+=1
            if count <= mid:
                low = mid+1
            else:
                high = mid
        return low

上述代码的解释(根据作者): 此解决方案基于二进制搜索。

首先,搜索空间是1到n之间的数字。每次我选择一个数字mid(中间的数字)并计算所有等于或小于mid的数字。然后,如果计数大于mid,则搜索空间将为[1 mid],否则为[mid + 1 n]。我这样做,直到搜索空间只有一个数字。

假设n = 10,我选择mid = 5。然后我计算数组中所有小于等于mid的数字。如果有超过5个小于5的数字,那么通过Pigeonhole Principle(https://en.wikipedia.org/wiki/Pigeonhole_principle),其中一个不止一次发生。所以我将搜索空间从[1 10]缩小到[1 5]。否则重复的数字在下半部分,因此对于下一步,搜索空间将是[6 10]。

怀疑: 在上述解决方案中,count <= mid时,为什么我们要将low更改为low = mid + 1或以其他方式更改{ {1}}? 背后的逻辑是什么?

我无法理解此算法背后的逻辑

相关链接: https://discuss.leetcode.com/topic/25580/two-solutions-with-explanation-o-nlog-n-and-o-n-time-o-1-space-without-changing-the-input-array

4 个答案:

答案 0 :(得分:4)

这是二元搜索。您正在将搜索空间减半并重复。

以这种方式思考:您有101个项目的列表,并且您知道它包含值1-100。取中间点,50。计算有多少项小于或等于50.如果有超过50项小于或等于50,则副本在0-50范围内,否则副本为在51-100范围内。

二进制搜索只是将范围缩小了一半。看0-50,取中点25并重复。

我认为这个算法的关键部分是导致混淆的是for循环。我试图解释一下。首先请注意,此算法中的没有使用索引 - 只需检查代码,您就会发现索引引用不存在。其次,请注意,算法在inspection_count循环的每次迭代中循环遍历整个集合。

让我进行以下更改,然后在每while次循环后考虑inspection_count=0 for i in nums: inspection_count+=1 if i <= mid: count+=1 的值。

inspection_count

当然len(nums)将等于n。 for循环迭代整个集合,并且每个元素都检查它是否在候选范围内(值,而不是索引)。

复制测试本身简单而优雅 - 正如其他人所指出的那样,这是鸽子的原则。给定{p..q}值的集合,其中每个值都在q-p < n范围内,如果p = 0, q = 5, n = 10 "I have ten values, and every value is between zero and five. At least one of these values must be duplicated." ,则范围内必须有重复项。想想一些简单的案例 -

p = 50, q = 99, n = 50
"I have a collection of fifty values, and every value is between fifty and ninety-nine.
There are only forty nine *distinct* values in my collection.
Therefore there is a duplicate."

我们可以概括一下,但更有效和相关的例子是

{{1}}

答案 1 :(得分:2)

设置low = mid+1high = mid背后的逻辑基本上是基于binary search的解决方案。搜索空间被分成两半,while循环仅在下一次迭代中搜索下半部分(high = mid)或上半部分(low = mid+1)。

  

所以我将搜索空间从[1 10]缩小到[1 5]。否则重复的数字在下半部分,因此对于下一步,搜索空间将是[6 10]。

这是关于你的问题的解释的一部分。

答案 2 :(得分:0)

假设您有10个号码。

a=[1,2,2,3,4,5,6,7,8,9]

然后mid = 5 小于或等于5的元素数是6(1,2,2,3,4,5)。 现在count = 6,大于mid。这意味着在前半部分至少有一个副本,所以代码正在做的是将搜索空间设置为从[1-10]到[1-5]的前半部分,依此类推。 否则在下半部分会出现重复,因此搜索空间将为[5-10]。

请告诉我你是否有疑问。

答案 3 :(得分:0)

public static void findDuplicateInArrayTest() {

    int[] arr = {1, 7, 7, 3, 6, 7, 2, 4};

    int dup = findDuplicateInArray(arr, 0, arr.length - 1);

    System.out.println("duplicate: " + dup);
}

public static int findDuplicateInArray(int[] arr, int l, int r) {

    while (l != r) {

        int m = (l + r) / 2;
        int count = 0;

        for (int i = 0; i < arr.length; i++)
            if (arr[i] <= m)
                count++;

        if (count > m)
            r = m;
        else
            l = m + 1;
    }
    return l;
}