題目:
給定一個包含 n + 1 個整數的數組 nums,其數字都在 1 到 n 之間(包括 1 和 n),可知至少存在一個重複的整數。假設只有一個重複的整數,找出這個重複的數。
示例1:
輸入: [1,3,4,2,2]
輸出: 2
示例2:
輸入: [3,1,3,4,2]
輸出: 3
說明:
- 不能更改原數組(假設數組是隻讀的)。
- 只能使用額外的 O(1) 的空間。
- 時間複雜度小於。
- 數組中只有一個重複的數字,但它可能不止重複出現一次。
題目本身不難,解法太多了,難就難在說明上,大概翻譯一下說明的意思
- 數組排序和符號翻轉的方法不能用
- HashMap和額外數組計數不能用
- 暴力算法不可取
- 異或解法不能用
第四條可把我坑慘了,想了半天用異或怎麼解,硬是沒找到規律,後面從算法複雜度入手想,小於的意思是也是可以的,就想到了二分的解法,數字是1到n之間,長度爲n+1,每多一個重複的數一定會少一個其他數,這樣在重複數x的附近一定滿足這樣的規律:用cnt(i)表示數組中比i小的數的數量,若i<x,則cnt(i)<i,若i>=x,則cnt(i)>i,用這樣的規律去二分查找就好了,每次計算cnt(i)需要遍歷整個數組,所以整體時間複雜度是。
代碼:
int findDuplicate(int* nums, int numsSize){
int l=1,r=numsSize,mid;
int cnt,ans;
while(l<=r){
mid=(l+r)/2;
int i;
cnt=0;
for(i=0;i<numsSize;i++){
if(nums[i]<=mid)
cnt++;
}
if(cnt>mid){
ans=mid;
r=mid-1;
}
else
l=mid+1;
}
return ans;
}
運行結果:
上面就是題解一的思路,官方題解第二種解法屬實太抽象看不懂,後邊主要研究的是Floyd判圈算法,這個讓我想到了小學的一道奧數題,印象中是一摸一樣的。基本思路是將數組抽象爲一個圓環,因爲1~nn 之間有n+1個數,所以一定有重複數字出現,而重複的數字即是圓環與線的交匯點。然後設置兩個指針fast和slow,快指針一次走兩步,慢指針一次走一步。當兩個指針第一次相遇時,令快指針回到起點且也變成一次走一步,慢指針則繼續前進,再次回合時即是線與圓環的交匯點。
關鍵點就是爲什麼第二次可以相遇,以及爲什麼相遇處就是重複數字。官方推導如下
在k=1的情況下,起點到入口的距離就是第一次相遇點到入口的距離,k>1也只是多了幾整圈,所以一起出發必相遇,而且必在入口處。
代碼:
int findDuplicate(int* nums, int numsSize){
int slow=nums[0],fast=nums[nums[0]];
//第一次相遇
while(slow!=fast)
{
//讓慢指針每次移動一位,快指針每次移動兩位
slow=nums[slow];
fast=nums[nums[fast]];
}
//讓快指針回到起點
fast=0;
//讓慢指針和快指針速度一致,找環的入口
while(slow!=fast)
{
slow=nums[slow];
fast=nums[fast];
}
return slow;
}
運行結果:
mark,mark,mark,tql