解法一:原地哈希
为了控制时间复杂度O(n),空间复杂度O(1),很容易想到原地哈希策略
func findDuplicate(nums []int) int {
// 原地哈希
for i:=0; i<len(nums); i++{
// 每个元素范围是[1, len(nums)-1]
// 可以将每个元素置换到其值-1的下标上,那么如果有一个重复的元素,那么它一定和下标对应不上
for nums[i] >= 1 && nums[i] <= len(nums) && nums[i] != nums[nums[i]-1]{
nums[i], nums[nums[i]-1] = nums[nums[i]-1], nums[i]
}
}
// 找到第一个下标+1不等于元素值的数字
for i, v := range nums{
if i+1 != v{
return v
}
}
return 0
}
但是题目要求了我们不能修改原数组
解法二:快慢指针
借鉴环形链表寻找环起点的解题思路,关键是要理解如何将输入的数组看作为链表。
nums 中的数字范围是 [1,n],总共有n+1个数字。考虑一下两种情况:
1.如果数组中没有重复的数,以数组 [1,3,4,2]为例,我们将数组下标 i 和数 nums[i] 建立一个映射关系 f(i),其映射关系 i->f(i)为:
0->1
1->3
2->4
3->2
如果我们从下标为 0 出发,根据 f(n) 计算出一个值,以这个值为新的下标,再根据 f(n) 计算出一个值,以此类推,直到下标越界,那么我们会得到一个类似链表一样的序列0->1->3->2->4->null
2.如果数组中有重复的数,以数组 [1,3,4,2,2] 为例,其映射关系 i->f(i)为:
0->1
1->3
2->4
3->2
4->2
同样的,得到一个类似链表一样的序列0->1->3->2->4->2->4->2->……,这里 2->4 是一个循环
因此,数组中如果有重复的数,那么就会产生多对一的映射,这样形成的链表就一定会有环。那么现在题目就抽象为:
- 数组中存在一个重复的数 -》形成的链表存在环
- 找出这个重复的数字 -》找到链表的环起点
我们知道环形链表可以采用快慢指针技巧,那么落在这道题的数组中,该怎么走呢?根据上述数组转链表的映射关系,走一步就是执行一次映射函数f(i),可推出:
- 慢指针走一步 slow = slow.Next -> slow = nums[slow]
- 快指针走两步 fast = fast.Next.Next -> fast = nums[nums[fast]]
func findDuplicate(nums []int) int {
slow, fast := 0, 0
for fast < len(nums) && nums[fast] < len(nums) {
slow = nums[slow]
fast = nums[nums[fast]]
if slow == fast {
// 相遇后,慢指针回到起点,块指针还在相遇点,以相同速度前进
slow = 0
for slow != fast {
slow = nums[slow]
fast = nums[fast]
}
return slow
}
}
return -1
}