代碼實現
只想抄個答案把題過了的可以直接複製下面的代碼:
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int slow = 0, fast = 0, tar = 0;
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow != fast);
while (tar != slow) {
slow = nums[slow];
tar = nums[tar];
}
return tar;
}
};
題目描述
算法分析
由於題中的限制,所以不能用先排序再遍歷的方法(不能更改原數組),也不能用無序集合或者散列表來做(只能使用常量空間),所以只能從數組本身找規律,這裏介紹一種線性時間複雜度的算法。
首先我們有如下的一個數組,i爲下標,num爲數字,有nums[i] = num(nums爲給定的數組):
然後我們建立這樣的映射F,使得F(i) = num
同時,如果給出了F(a) = b,我們再對b進行映射,會有F(b) = c,我們再對c做同樣的操作,一直不斷循環,反映到題中的數組上,就是如下的情況:
這裏我們起始給定a = 0,F(a) = 3,然後F(3) = 2,F(2) = 1,…,F(4) = 2,F(2) = 1,出現了循環,但是並不是因爲有重複數字纔出現環,我們先看重複數字會發生什麼樣的情況:
會出現多對一映射,即至少存在這樣的a,b,c(a ≠ b),有F(a) = c,F(b) = c,反應到圖中即爲指向數字‘2’的有兩個箭頭(F(3) = 2,F(4) = 2)
很容易知道,如果我們對一個不存在重複數字的數組進行這樣的循環映射操作,最後也會發生循環,且循環開始的下標值一定是第一個進行映射操作的下標值,即,如果我們從F(a) = b開始進行映射操作,最後仍會以F(a) = b的出現標誌着進入循環(可以自己畫一個簡單的圖來驗證)。
但是如果有重複的數字,我們就會發現,因爲存在F(a) = c,F(b) = c,那麼中間步驟一旦出現了F(x) = b,就會有這樣的循環:F(c) = y, ..., F(x) = b, F(b) = c, F(c) = y
,這種循環和剛剛講的循環不同的地方在於,映射循環開始的下標值正好是發生重複的數字,所以問題就轉化爲如何找到這樣的循環,以及循環開始的節點。
代碼分析
首先,接下來的操作僅作說明,如果想了解原理可以參考鏈表找環的起點
算法一共就兩個要點,第一,找到環,第二,找到環起點。
先來看找環操作,如下:
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow != fast);
通過一快一慢兩個指針進行循環映射操作(慢指針一次走一格,快指針一次走兩格),兩個指針相遇時,就說明指針已經進入環內(前提是必須有環)。
再看找環起點的操作,如下:
while (tar != slow) {
slow = nums[slow];
tar = nums[tar];
}
return tar;
slow是剛纔循環結束後的慢指針,tar初始值是0,兩個指針同時向前走,相遇的時候tar指針就是環的起點,即數組重複的數字。
整個流程如下:
int findDuplicate(vector<int>& nums) {
int slow = 0, fast = 0, tar = 0;
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow != fast);
while (tar != slow) {
slow = nums[slow];
tar = nums[tar];
}
return tar;
}
最後,這個代碼雖然在leetcode上可以通過,但是還是有一個bug的,假如輸入{2,1,0,3,3}
這樣的數組你會發現輸出結果是0,因爲0,1,2這3個數組成了環,相當於我們一開始說的無重複數字的數組產生的環,這時候就需要額外的進行判斷,有興趣的可以進行嘗試改進(提示:觀察這樣的數組輸出結果的規律,以及其中重複數字出現位置的規律)