烏龜和兔子的算法我記得最早是用在判斷鏈表是否有循環,但是實際上今天做了一道題之後,可以總結爲,判斷一些循環的情況,都可以用這個方法,首先看看題目
287. 尋找重複數
給定一個包含 n + 1 個整數的數組 nums,其數字都在 1 到 n 之間(包括 1 和 n),可知至少存在一個重複的整數。假設只有一個重複的整數,找出這個重複的數。
示例 1:
輸入: [1,3,4,2,2]
輸出: 2
示例 2:
輸入: [3,1,3,4,2]
輸出: 3
暴力和排序的方法就不說了,先說一個也很巧妙的方法:
1. 二分法:
假設一下如果不重複的話(1,2,3,4,5),用二分的方式,左邊的從1到中位數mid = 3(nums[i]<=mid)的個數就應該等於3,但是如果,左邊多出來了一個重複的數,那麼絕對就大於mid = 3,如(1,2,2,3,5,6)的個數就爲4,故可知重複的數在左邊,反之在右邊,逐漸的縮小範圍,直到只剩下一個數!
代碼如下:
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int n = nums.size()-1;
int l = 1;
int r = n;
while(l < r){
int mid = (l+r)>>1;
int count = 0;
for(int i:nums){
if(i <= mid) count++;
}
if(count > mid) r = mid;
else l = mid+1;
}
return l;
}
};
2.烏龜與兔子法:
接下來進入正題,爲什麼這題可以用烏龜和兔子的方法?
因爲這題存在着循環!!!
怎麼看有沒有循環?方法如下:
烏龜和兔子都代表兩個指針,假設都指向下標爲0的數nums[0],指針移動的方向爲,當前的值nums[i],即跳到下標爲nums[i]的位置,值爲nums[nums[i]]烏龜每次跳一步,兔子跳兩步。最後烏龜和兔子肯定會相遇!!!,因爲總會有兩個下標指向的是同一個數字!!!
如 ;
(0,1,2,3,4)
(1,3,4,2,2)
每次下一步的方向爲:
1->3->2->4->2->4…循環了!!!
但是這題主要找的是循環入口!!!這就要想起了如何求鏈表的循環入口了。
這裏講的很好:
尋找鏈表的循環入口
代碼如下:
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int tortise = nums[0];
int rabbit = nums[0];
do{
tortise = nums[tortise];
rabbit = nums[nums[rabbit]];
}while(tortise != rabbit);
int ptr1 = nums[0];
int ptr2 = tortise;
while(ptr1 != ptr2){
ptr1 = nums[ptr1];
ptr2 = nums[ptr2];
}
return ptr1;
}
};