1、問題描述
給定一個包含n+1個整數的數組nums,其中,每個整數的取值範圍爲[1,n],根據鴿巢原理可知,至少存在一個重複的整數。假設只有一個重複的數,找出這個數。
2、解題思路
- 邊界條件與特殊情況:(1)nums中整數的個數小於2;
- 問題分析:以nums=[1,3,4,2,3]爲例,索引index的範圍是在[0,n],元素值value的範圍是[1,n],想象一下,把數組看成一個鏈表,數組中每一個位置上的元素值作爲下一個元素的索引,當有元素重複時,會得到下面這樣的帶有環的索引鏈表:
index | value |
---|---|
0 | 1 |
1 | 3 |
2 | 4 |
3 | 2 |
4 | 3 |
- 思路1:根據上面的分析,類比判斷鏈表中是否存在環問題的解題思路,採用快慢指針來解決這個問題,整個思路分爲兩個步驟:
- (1)檢測環。具體地,快、慢指針fast、slow都從0開始,進行如下循環:slow每次進行一跳,而fast進行兩跳,比如,一開始slow=i、fast=i,下一步中,slow=nums[i],而fast=nums[nums[i]]。當slow等於fast,循環停止,此時slow和fast都停在環上某個節點。
- (2)找到環的入口。具體地,finder指針從0開始,slow從上一步中slow和fast在環中的相遇的開始,進行如下循環:slow和finder指針每次都進行一跳。當finder等於fast時,循環停止,此時finder指向的爲環的入口。
- 這裏可能有點難理解的是:爲什麼finder和slow會在環的入口處相遇?我們可以證明一下:
- 假設起點到環的入口處長度爲m,環的周長爲c,在檢測環的過程中,快慢指針在環中相遇,此時,fast走了2n步,slow走了n步,根據以上假設可得出以下結論:
- 由於兩個指針在環中相遇,所以多走的n步一定是c的整數倍,即n%c=0;
- fast和slow相遇時,slow在環中行走的距離是n-m,此時,若finder從起點開始,slow從相遇點開始,兩者同步前進,每次一步,當finder走了m步到達環的入口處時,slow走了n-m+m=n步,而n又是c的整數倍,故兩者會在環的入口相遇。
- 先聲明一下數據結構:
- slow慢指針、fast快指針,finder指針。
- 初始化,一開始slow和fast都爲0,finder也爲0
- 處理邏輯: 檢測環和找到環的入口。
- 這個方法的時間複雜度爲,空間複雜度爲。
- 思路2:一說到查找,且時間複雜度小於,則可以嘗試二分查找,二分查找的思路如下:
- 由於數組的元素的取值範圍在[1,n],所以需要查找的數的區間也left=1、right=n,取該區間的中位數mid,統計數組中小於等於mid的元素個數count,如果count<=mid,則說明重複的整數在[mid+1,right]區間,否則在[left,mid]之間,如果left <right,則繼續在子區間查找。
- 由於進行了次查找,統計小於mid的元素個數count時間複雜度爲,所以時間複雜度爲,空間複雜度爲。
3、代碼實現
快慢指針實現:
class Solution {
public:
int findDuplicate(vector<int>& nums) {
if (nums.size() < 2){
return 0;
}
int slow=0;
int fast = 0;
int finder = 0;
while(true){
slow = nums[slow];
fast = nums[nums[fast]];
if(slow == fast){
break;
}
}
while(true){
slow = nums[slow];
finder = nums[finder];
if(slow == finder){
break;
}
}
return finder;
}
};
二分搜索實現:
class Solution {
public:
int findDuplicate(vector<int>& nums) {
if (nums.size() < 2){
return 0;
}
int left = 1;
int right = nums.size() - 1;
while(left < right){
int mid = (left + right) /2;
int count = countNums(nums,mid);
if(count <= mid){
left = mid + 1;
}
else{
right = mid;
}
}
return left;
}
int countNums(vector<int>& nums,int e){
int count = 0;
for(int i=0;i<nums.size();i++){
if(nums[i] <= e){
count ++;
}
}
return count;
}
};