题解
本题的难点在于:
- 不能更改原数组
- 只能使用 O ( 1 ) O(1) O(1)的额外空间
二分法
按题目表达,设数组长度为 n n n,则数组中元素 ∈ [ 1 , n − 1 ] \in[1,n-1] ∈[1,n−1],且只有一个重复元素。一个直观的想法,设一个数字 k ∈ [ 1 , n − 1 ] k\in[1,n-1] k∈[1,n−1],统计数组中小于等于 k k k的数字的个数 c o u n t count count:
- 若 c o u n t < = k count<=k count<=k,说明重复数字一定在 ( k , n − 1 ] (k,n-1] (k,n−1]的范围内。
- 若 c o u n t > k count>k count>k,说明重复数字一定在 [ 0 , k ] [0,k] [0,k]的范围内。
利用这个性质,我们使用二分查找逐渐缩小重复数字所在的范围。
- 初试化左右 数字 边界 l e f t = 1 , r i g h t = n − 1 left=1,right=n-1 left=1,right=n−1
- 循环条件
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。
- 返回 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(待完成)
快慢指针
分为两步:
- 找到环
- 找到环的入口(即重复元素)
找环:
- 定义快慢指针 s l o w = 0 , f a s t = 0 slow=0,fast=0 slow=0,fast=0
- 进入循环:
- 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。
找环的入口:
- 定义新的指针 f i n d = 0 find=0 find=0
- 进入循环:
- 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
2885

被折叠的 条评论
为什么被折叠?



