287. 尋找重複數
題目描述
給定一個包含 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) 。
- 陣列中只有一個重複的數字,但它可能不止重複出現一次。
題意
審題可以發現兩個關鍵點:
- nums陣列長度為1+n,其中的數字範圍皆介於[1,n]
- 只有一個重複的數字,但是可能出現2次或以上
由第1點可知:對於任意下標1 <= i <= n, 總有1 <= nums[i] <= n,即將nums陣列看為一個連結串列的話,是不會出現越界錯誤的。具體看為連結串列的方式是將nums[i]作為下一個元素的下標。
舉個例子:
nums = [1,3,4,2,2],假設有個頭節點head且值為0,連結串列可以整理為
head->1->3->2->4->2->4->...
head後面之所以為1是因為nums[0] = 1;同理,nums[1] = 3; nums[3] = 2;以此類推
最後的省略號代表迴圈部分
由第2點可知:給出判斷的nums陣列形成的連結串列必有環,且為單環
演算法
說到如何判斷有環,以及尋找環的起點,常用的演算法為快慢指標。
快慢指標的思想如下:
宣告兩個指標,兩個指標的初始值都是連結串列的頭指標,讓它們兩同時出發,快指標每次前移兩個節點,慢指標每次前移一個節點。
- 如果連結串列無環,那麼兩個指標永遠不會相遇,也就是說當快指標抵達連結串列末尾,慢指標還在連結串列中間
-
如果連結串列有環,那麼兩個指標必會相交,證明如下:
-
兩指標相遇後,再將任意一個指標調至頭節點,兩個指標分別從各自的位置以 每次一個節點 的速度執行,直到再次相遇, 相遇的那個點便為環的入口 。證明如下:
快慢指標的思想如上所述,給出的證明可能不怎麼嚴謹,但應該還是能幫助理解的,具體的程式碼如下。
程式碼
#include <iostream> #include <vector> using namespace std; class Solution { public: int findDuplicate(vector<int>& nums) { int backPos; // 設定快慢指標 int fast = 0, slow = 0; while(true) { fast = nums[nums[fast]]; slow = nums[slow]; if(fast == slow) { fast = 0; while(fast != slow) { fast = nums[fast]; slow = nums[slow]; } backPos = slow; break; } } return backPos; } }; int main() { Solution s; vector<int> nums = {1,3,4,2,5,3}; cout << s.findDuplicate(nums) << endl; return 0; }