給定一個包含 n + 1 個整數的數組 nums,其數字都在 1 到 n 之間(包括 1 和 n),可知至少存在一個重複的整數。假設只有一個重複的整數,找出這個重複的數。
示例 1:
輸入: [1,3,4,2,2]
輸出: 2
示例 2:
輸入: [3,1,3,4,2]
輸出: 3
說明:
不能更改原數組(假設數組是隻讀的)。
只能使用額外的 O(1) 的空間。
時間複雜度小於 O(n2) 。
數組中只有一個重複的數字,但它可能不止重複出現一次。
來源:力扣(LeetCode)
https://leetcode-cn.com/problems/find-the-duplicate-number
拿到這個問題,我想我們第一時間會想到的一定是用桶來計數或者是使用hashmap來計數,這樣的時間複雜度只有O(n),速度還是很快的,但是有個問題就是在說明中要求額外的空間是O(1).那麼就不能使用這兩種簡單明白的方法了,這個時候我們可以考慮轉換一下思路,將這個數組問題轉換成我們熟悉的鏈表問題,也就是判斷一個鏈表是否有環,有環的話,這個環的入口在哪裏的問題。
數組中的元素都當作是鏈表上的節點,那麼有重複的數,有重複的數我們就可以看作是有環,而且這個環的入口就是這個重複的數,至於如何證明環的入口就是重複的數,我會寫個簡單的證明過程:
1.設從起點到入口的距離爲X,從入口到相遇點的距離爲L,環的周長是C
2.那麼快指針走的距離是X+nC+L,慢指針走的距離X+mC+L
3.那麼就有X+nC+L = 2(X+mC+L)->X = (n-2m)C-L
4.所以他們以相同的速度走再次相遇時肯定是在入口處
public int findDuplicate(int[] nums) {
//將這個重複的問題抽象成環形鏈表
//因爲快慢指針在重複的元素是成環的,環的入口就是重複的元素
//至於這個環的入口就是重複的元素是如何推導的
//設從起點到入口的距離爲X,從入口到相遇點的距離爲L,環的周長是C
//那麼快指針走的距離是X+nC+L,慢指針走的距離X+mC+L
//那麼就有X+nC+L = 2(X+mC+L)->X = (n-2m)C-L
//所以他們以相同的速度走再次相遇時肯定是在入口處
int slow = 0;
int fast = 0;
while (true) {
slow = nums[slow];
fast = nums[nums[fast]];
if (slow == fast) {
//fast指針回到開頭,兩個指針以相同速度繼續遍歷,相遇的地方就是環的入口
fast = 0;
while (nums[slow] != nums[fast]) {
slow = nums[slow];
fast = nums[fast];
}
return nums[slow];
}
}
}