每日一題,防止癡呆 = =
一、題目大意
給定一個包含 n + 1 個整數的數組 nums,其數字都在 1 到 n 之間(包括 1 和 n),可知至少存在一個重複的整數。假設只有一個重複的整數,找出這個重複的數。
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/find-the-duplicate-number
二、題目思路以及AC代碼
這道題剛看到的時候以爲是用異或做了 = =,結果發現題目中說重複數的重複次數可能大於1次,這用異或就沒有辦法了,主要還是參照題解學習了三種解題方式。
思路一:二分
題目要求時間複雜度小於O(n2),所以無非就是O(nlogn)和O(n),一看到O(nlogn),我首先是想到快排,然後稀裏糊塗就給過了(其實題目要求不能修改原數組的),所以就可以考慮二分。
但是對於二分,我們需要構造單調序列,這裏可以考慮單調序列cnt,其中cnt[i]表示在nums數組中,小於等於i的數的個數,我們用target表示我們要找的重複數,那麼可以想象當i < target的時候,cnt[i] 是小於等於i的(當且僅當重複數沒有佔用小於target數的位置時等號成立),當i >= target的時候,cnt[i]是大於i的,所以我們就可以根據以上情況進行二分,即如果cnt[mid] <= mid,那麼target一定在右側的數組中,如果cnt[mid] > mid,那麼target一定在左側的數組中或者就是mid.
按道理來說,應該先計算cnt數組,複雜度是O(n),然後再二分,複雜度是O(logn),總體複雜度O(n),但是由於題目要求不能使用額外的存儲空間,所以這裏就得現算cnt,造成結果的複雜度是O(nlogn).
思路二:二進制
和思路一差不多,這裏的思路是考慮確定重複數的各個二進制位。即我們考慮重複數的第i位是否是1,我們令x表示nums數組中,第i位爲1的總個數,y表示1~n中,第i位爲1的總個數。那麼如果x > y,那該位就是1,如果x <= y,那麼該位就是0.
我們可以分情況考慮上述的問題。重複數我們分爲重複1次,和重複多次。如果是重複一次,那麼當重複數第i位爲1的時候,則x = y + 1,當重複數爲0的時候,則x = y,滿足上述提到的條件。然後當重複數是重複多次的時候,必定要替代一個原來的數B,如果重複數第i位爲1,B第i位爲0,那麼x > y+1,如果重複數第i位爲1,B第i位爲1,那麼x = y+1 > y,如果重複數第i位爲0,B第i位爲1,那麼x < y,如果重複數第i位爲0,B第i位爲0,那麼x = y,綜上所述,都滿足上述提到的判定重複數第i位值得條件,即如果x > y,那該位就是1,如果x <= y,那麼該位就是0.
思路三:判環
該題同樣可以建立成一個圖中判環的問題。我們令i -> nums[i]爲邊建圖,那麼由於有重複數的原因,所以勢必有兩個頂點同時指向同一個頂點,此時有環出現,而環的起點就是我們要找的重複數。
可以使用快慢指針來實現。
AC代碼
上述代碼我實現了一下思路一和思路三,列在下面。
思路一:
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int n_size = nums.size();
int l = 1, r = n_size - 1;
int mid = (l + r) >> 1;
while (l < r) {
mid = (l + r) >> 1;
int x = 0;
for (int i=0;i<n_size;i++) {
if (nums[i] <= mid) {
x++;
}
}
if (x <= mid) l = mid + 1;
else r = mid;
}
return l;
}
};
思路三:
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int slow = 0, fast = 0;
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while(slow != fast);
slow = 0;
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow;
}
};
如果有問題,歡迎大家指正!!!