Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive), prove that at least one duplicate number must exist. Assume that there is only one duplicate number, find the duplicate one.
Example 1:
Input: [1,3,4,2,2]
Output: 2
Example 2:
Input: [3,1,3,4,2]
Output: 3
Note:
- You must not modify the array (assume the array is read only).
- You must use only constant, O(1) extra space.
- Your runtime complexity should be less than O(n2).
- There is only one duplicate number in the array, but it could be repeated more than once.
思路
包含有n+1個不大於n的正整數的數組一定存在重複元素,本題要求從這樣的數組中找出重複的那個元素。題目很簡單,有很多方法可做,但是難在要求空間複雜度O(1),時間複雜度小於O(n2),並且源數組是隻讀的,不允許任何修改操作。這樣就限制了諸如Hash表,排序等的解法。最優解法是採用弗洛伊德循環尋找算法,原理如下:
- 需要明確一點:n+1個不大於n的正整數組成的數組可看做一個鏈表:
- A[0]=1 >>>> A[1]=3 >>>> A[3]=2 >>>> A[2]=4 >>>> A[4]=2
i | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
A[i] | 1 | 3 | 4 | 2 | 2 |
如果這樣的數組中出現重複元素,那麼這個鏈表就會出現一個環
- A[0]=1 >>>> A[1]=3 >>>> A[3]=2 >>>> A[2]=4 >>>> A[4]=2
- 弗洛伊德循環尋找算法就是查找這個環入口的算法,過程爲:
(1)算法採用了一個慢指針和一個快指針,慢指針每次移動1步,快指針每次移動2步,初始兩個指針都指向0,由於路徑中存在環,兩個指針最終肯定會匯合。第一次匯合時,我們將快指針重置爲0,並將移動步數從2修改爲1,等到兩個指針再次匯合時,指向的元素即爲環入口。
(2)原理推導:如下圖,P段表示鏈表中的非環部分,C表示慢指針與快指針第一次匯合時在環中重合的部分,L表是整個環的長度,X表示L-C部分;
(3)則當快指針與慢指針第一次匯合時,慢指針走過的路徑長度爲P+C,快指針走過的路徑長度爲L+C+nL(n爲整數,由於快指針速度快,因此快指針一定是比慢指針多跑了n圈);然後由於快指針的速度是慢指針速度的2倍,而他們經過的時間是相同的,因此路程有如下關係:
因此可得出結論:“P段的長度”等於“X段的長度+整數圈環的長度”,故此時只需要讓快指針重置爲0(即回到P段的開始)並使速度與慢指針相同,則他們一定會在環路入口的位置再次匯合。
Java實現
class Solution {
public int findDuplicate(int[] nums) {
int slow = 0;
int fast = 0;
do {
slow = nums[slow];
fast = nums[nums[fast]];
}while (slow != fast);
fast = 0;
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
}
Python實現
class Solution(object):
def findDuplicate(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
slow = fast = 0
while True:
slow = nums[slow]
fast = nums[nums[fast]]
if slow == fast:
break
fast = 0
while slow != fast:
slow = nums[slow]
fast = nums[fast]
return fast
Scala實現
object Solution {
def findDuplicate(nums: Array[Int]): Int = {
var slow = 0
var fast = 0
do {
slow = nums(slow)
fast = nums(nums(fast))
} while (slow != fast)
fast = 0
while (slow != fast) {
slow = nums(slow)
fast = nums(fast)
}
return fast
}
}