287. 寻找重复数(Find the Duplicate Number)

题解

本题的难点在于:

  • 不能更改原数组
  • 只能使用 O ( 1 ) O(1) O(1)的额外空间

二分法

按题目表达,设数组长度为 n n n,则数组中元素 ∈ [ 1 , n − 1 ] \in[1,n-1] [1,n1],且只有一个重复元素。一个直观的想法,设一个数字 k ∈ [ 1 , n − 1 ] k\in[1,n-1] k[1,n1],统计数组中小于等于 k k k的数字的个数 c o u n t count count

  1. c o u n t < = k count<=k count<=k,说明重复数字一定在 ( k , n − 1 ] (k,n-1] (k,n1]的范围内。
  2. c o u n t > k count>k count>k,说明重复数字一定在 [ 0 , k ] [0,k] [0,k]的范围内。

利用这个性质,我们使用二分查找逐渐缩小重复数字所在的范围。

  1. 初试化左右 数字 边界 l e f t = 1 , r i g h t = n − 1 left=1,right=n-1 left=1,right=n1
  2. 循环条件 l e f t < r i g h t left<right left<right:
    • m i d = ( l e f t + r i g h t ) / / 2 mid=(left+right)//2 mid=(left+right)//2
    • 按照性质,统计数组中小于等于 m i d mid mid的元素个数 c o u n t count count
    • c o u n t < = m i d count<=mid count<=mid,说明重复数字一定在 ( m i d , r i g h t ] (mid,right] (mid,right]的范围内。令 l e f t = m i d + 1 left=mid+1 left=mid+1
    • c o u n t > m i d count>mid count>mid,说明重复数字一定在 [ l e f t , m i d ] [left,mid] [left,mid]的范围内。令 r i g h t = m i d right=mid right=mid
  3. 返回 l e f t left left

复杂度分析

  • 时间复杂度: O ( n l o g ( n ) ) O\left(nlog(n)\right) O(nlog(n)),二分法执行了 l o g ( n ) log(n) log(n)次遍历 n n n,因此复杂度为 O ( n l o g ( n ) ) O\left(nlog(n)\right) O(nlog(n))
  • 空间复杂度: O ( 1 ) O(1) O(1)

Python

class Solution:
    def findDuplicate(self, nums: List[int]) -> int:
        left = 1
        right = len(nums) - 1
        while(left<right):
            mid=(left+right)//2
            count=0
            for num in nums:
                if(num<=mid):
                    count+=1
            if(count<=mid):
                left=mid+1
            else:
                right=mid
        return left

Java(待完成)

快慢指针

分为两步:

  1. 找到环
  2. 找到环的入口(即重复元素)

找环:

  1. 定义快慢指针 s l o w = 0 , f a s t = 0 slow=0,fast=0 slow=0,fast=0
  2. 进入循环:
    • s l o w slow slow每次走一步,即 s l o w = n u m s [ s l o w ] slow=nums[slow] slow=nums[slow]
    • f a s t fast fast每次走两步,即 f a s t = n u m s [ n u m s [ f a s t ] ] fast=nums[nums[fast]] fast=nums[nums[fast]]
    • s l o w = = f a s t slow==fast slow==fast时,退出循环。
      当快慢指针相遇时,一定在环内。此时假设 s l o w slow slow走了 k k k步,则 f a s t fast fast走了 2 k 2k 2k步。设环的周长为 c c c,则 k % c = = 0 k\%c==0 k%c==0

找环的入口:

  1. 定义新的指针 f i n d = 0 find=0 find=0
  2. 进入循环:
    • f i n d find find每次走一步,即 f i n d = n u m s [ f i n d ] find=nums[find] find=nums[find]
    • s l o w slow slow每次走一步,即 s l o w = n u m s [ s l o w ] slow=nums[slow] slow=nums[slow]
    • 当两指针相遇时,即 f i n d = = s l o w find==slow find==slow,返回 f i n d find find

为何相遇时,找到的就是入口:
假设起点到环的入口(重复元素),需要 m m m步。此时 s l o w slow slow走了 n + m n+m n+m步,其中 n n n是环的周长 c c c的整数倍,所以相当于 s l o w slow slow走了 m m m步到达入口,再走了 n n n步。所以相遇时一定是环的入口。

复杂度分析

  • 时间复杂度: O ( n ) O\left(n\right) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)

Python

class Solution:
    def findDuplicate(self, nums: List[int]) -> int:
        slow=0
        fast=0
        while(1):
            slow=nums[slow]
            fast=nums[nums[fast]]
            if(slow==fast):
                break
        find=0
        while(1):
            find=nums[find]
            slow=nums[slow]
            if(find==slow):
                return find

Java(待完成)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>