[python 刷题] 287 Find the Duplicate Number

[python 刷题] 287 Find the Duplicate Number

题目:

Given an array of integers nums containing n + 1 integers where each integer is in the range [1, n] inclusive.

There is only one repeated number in nums, return this repeated number.

You must solve the problem without modifying the array nums and uses only constant extra space.

快慢指针

之前其实大概说过这道题用快慢指针做,不过时隔一年,重新写的时候发现只是记得一个 快慢指针,但是不记得具体的实现方法了,所以想着重新整理一下。

这道题本质上是个链表题

首先捋一下题目的重点:

  1. 数组中包含 n + 1 个数字
  2. 每个数字的限定范围为 [1, n]
  3. 有且只有一个重复数字
  4. 不能排序数组 (❌ 排序)
  5. 不能用多余的数据结构去存储出现的数字 (❌ 哈希表)

首先考虑一下,如果数组中不包含重复数字,并且每个数字的限定范围依旧是 [1, n] 的场景,那也就是说,排序后一定会出现 [ 1 , 2 , . . . , n ] [1, 2, ..., n] [1,2,...,n],将当前下标中的值视作下个数字的下标,视觉上的效果就是这样的:

在这里插入图片描述

这个时候就算随意将其打乱,最终形成的结果也是:

在这里插入图片描述

也就是说不管怎么打乱,它都会存在一个 1-to-1 的关系,最终结束循环。但是如果数组中出现了一个重复数字,运用快慢指针的技巧,在某个情况下 (依旧是 O ( n ) O(n) O(n) 的时间复杂度),一定会两个指针一定会通过重复数字,而抵达同一个结点的情况下。

以我手残不小心打错的 LC 提供的案例来说,视觉效果如下:

[python 刷题] 287 Find the Duplicate Number_第1张图片

可以非常清楚的看到,出现了两个结点(这里将 array 中的每个 index 视作一个结点)指向另外一个结点的情况。

简单的过一下这个案例,效果如下:

黄色代表慢指针,蓝色代表快指针,绿色代表两点相交

iteration graph
1 [python 刷题] 287 Find the Duplicate Number_第2张图片
2 [python 刷题] 287 Find the Duplicate Number_第3张图片
3 [python 刷题] 287 Find the Duplicate Number_第4张图片
4 [python 刷题] 287 Find the Duplicate Number_第5张图片
5 [python 刷题] 287 Find the Duplicate Number_第6张图片

最终快慢指针是否会落在重复数字上与数组有关,LC 上的一个案例 [2,5,9,6,9,3,8,9,7,1] 最终的落点在 7 上。这个数组比较长,我就不跑了。

现在已经找到这个圈了,接着将其中一个指针指向数组的开始,继续走到两点相交的情况,就能够找到重复的点了:

[python 刷题] 287 Find the Duplicate Number_第7张图片

我个人觉得,这个指向重复的点还是挺清楚的,开始和结束的指针都一样,也就代表着圈的开始,所以这就是重复的数字。

代码如下:

class Solution:
    def findDuplicate(self, nums: List[int]) -> int:
        slow, fast = 0, 0
        while True:
            slow = nums[slow]
            fast = nums[nums[fast]]

            if slow == fast:
                break

        slow = 0

        while True:
            slow = nums[slow]
            fast = nums[fast]
            if slow == fast:
                return slow

这个算法的时间复杂度为 O ( n ) O(n) O(n),是属于 Follow up 的解法

二分搜索

这个二分搜索也属于变种方法,其思路是:

  1. 找到一个数字 k ∈ [ 1 , n − 1 ] k \in [1, n - 1] k[1,n1]

    这里取 [ 1 , n − 1 ] [1, n - 1] [1,n1] 这个范围的原因是,因为重复数字的关系,数组里面最大的数字只可能是 n − 1 n - 1 n1

  2. 计算小于等于 k k k 的数字,使得 c o u n t ( k ) = ∣ X ∣ x ∈ n u m s   a n d   x ≤ k ∣ count(k) = |{X | x \in nums \, and \, x \le k}| count(k)=Xxnumsandxk

1, 3, 4, 2, 2 为例,这里的 k k k 取 3,那么计算小于等于 3 的数字就有 4 个,那么自然就包含一个重复数字,反之亦然。

代码如下:

class Solution:
    def findDuplicate(self, nums: List[int]) -> int:
        l, h = 1, len(nums) - 1

        while l < h:
            m = (l + h) // 2
            c = sum(1 for num in nums if num <= m)

            if c <= m:
                l = m + 1
            else:
                h = m

        return l

因为这里是一个二分搜索整个数组的长度+遍历整个数组,所以时间复杂度为 O ( n l o g ( n ) ) O(n log(n)) O(nlog(n))

你可能感兴趣的:(#,leetcode,python,java,算法)