给定一个包含 n + 1 个整数的数组 nums ,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。
假设 nums 只有 一个重复的整数 ,找出 这个重复的数 。
示例 1:
输入:nums = [1,3,4,2,2] 输出:2
示例 2:
输入:nums = [3,1,3,4,2] 输出:3
示例 3:
输入:nums = [1,1] 输出:1
示例 4:
输入:nums = [1,1,2] 输出:1
提示:
2 <= n <= 3 * 104 nums.length == n + 1 1 <= nums[i] <= n nums 中 只有一个整数 出现 两次或多次 ,其余整数均只出现 一次
进阶:
如何证明 nums 中至少存在一个重复的数字? 你可以在不修改数组 nums 的情况下解决这个问题吗? 你可以只用常量级 O(1) 的额外空间解决这个问题吗? 你可以设计一个时间复杂度小于 O(n2) 的解决方案吗?
对nums遍历,对nums中的每一个元素进行计数写入字典,如果某个元素的计数值大于1,则该元素就是重复数。
或者对nums排序,找出与前一个元素一样的元素,那么这个就是重复数。
二分法的思路是先猜一个数(有效范围 [left, right]里的中间数 mid),然后统计原始数组中小于等于这个中间数的元素的个数 cnt,如果 cnt 严格大于 mid根据抽屉原理,重复元素就在区间 [left, mid] 里。
例如:区间 [1,7][1, 7][1,7] 的中位数是 4,遍历整个数组,统计小于等于 4 的整数的个数,如果不存在重复元素,最多为 444 个。等于 444 的时候区间 [1,4][1, 4][1,4] 内也可能有重复元素。但是,如果整个数组里小于等于 4 的整数的个数严格大于 444 的时候,就可以说明重复的数存在于区间 [1,4][1, 4][1,4]。
可以把nums中的元素看作是指向下一个位置的值,例如[1,3,4,2,2],那么遍历顺序就是1->3->2->4->2->3…,此时就出现了在4->2就开始重复出现同样的顺序,也就是环。所以有重复出现的数字,那么nums所表示的顺序中,一定存在环,而重复的数字就是环开始的地方,即环的起点,如上面的例子,2是重复数字,也就是环的起点。
那么问题就转换为链表中寻找环的起点,也就想到了利用快慢指针。
先设置慢指针 slow \textit{slow} slow 和快指针 fast \textit{fast} fast ,慢指针每次走一步,快指针每次走两步,根据Floyd 判圈算法,两个指针在有环的情况下一定会相遇,此时我们再将 ¥\textit{slow}$ 放置起点 0,两个指针每次同时移动一步,相遇的点就是答案。
假设环长为 L,从起点到环的入口的步数是 a,从环的入口继续走 b 步到达相遇位置,从相遇位置继续走 c 步回到环的入口,则有 b + c = L b+c=L b+c=L,其中 L、a、b、c 都是正整数。根据上述定义,慢指针走了 a + b a+b a+b 步,快指针走了 2 ( a + b ) 2(a+b) 2(a+b) 步。从另一个角度考虑,在相遇位置,快指针比慢指针多走了若干圈,因此快指针走的步数还可以表示成 a + b + k L a+b+kL a+b+kL,其中 k 表示快指针在环上走的圈数。联立等式,可以得到
2 ( a + b ) = a + b + k L 2(a+b)=a+b+kL 2(a+b)=a+b+kL
解得 a = k L − b a=kL−b a=kL−b,整理可得
a = ( k − 1 ) L + ( L − b ) = ( k − 1 ) L + c a=(k−1)L+(L−b)=(k−1)L+c a=(k−1)L+(L−b)=(k−1)L+c
从上述等式可知,如果慢指针从起点出发,快指针从相遇位置出发,每次两个指针都移动一步,则慢指针走了 a 步之后到达环的入口,快指针在环里走了 k − 1 k−1 k−1 圈之后又走了 c步,由于从相遇位置继续走 c 步即可回到环的入口,因此快指针也到达环的入口。两个指针在环的入口相遇,相遇点就是答案。
# 字典
class Solution:
def findDuplicate(self, nums: List[int]) -> int:
dic = collections.Counter(nums)
for d in dic:
if dic[d] > 1:
return d
# 排序
class Solution:
def findDuplicate(self, nums: List[int]) -> int:
nums.sort()
i,j = 0,1
while j < len(nums):
if nums[j] == nums[i]:
return nums[i]
j += 1
i += 1
class Solution:
def findDuplicate(self, nums: List[int]) -> int:
left = 1
right = len(nums) - 1
while left < right:
mid = left + (right - left ) // 2
# 计数, 找小于等于mid的个数
cnt = 0
for num in nums:
if num <= mid:
cnt += 1
# 根据鸽巢原理/抽屉原理
# <= 说明 重复元素再右半边
if cnt <= mid:
left = mid + 1
else:
right = mid
return left
class Solution:
def findDuplicate(self, nums: List[int]) -> int:
slow = nums[0]
fast = nums[nums[0]]
while slow != fast:
# print(slow, fast)
slow = nums[slow]
fast = nums[nums[fast]]
slow = 0
while slow != fast:
slow = nums[slow]
fast = nums[fast]
return slow