287 尋找重複數
阿新 • • 發佈:2021-01-29
技術標籤:# 二分專題javaleetcode演算法資料結構
287. 尋找重複數
給定一個包含 n + 1
個整數的陣列 nums
,其數字都在 1
到 n
之間(包括 1
和 n
),可知至少存在一個重複的整數。
假設 nums
只有 一個重複的整數 ,找出 這個重複的數 。
示例 1:
輸入:nums = [1,3,4,2,2]
輸出:2
示例 2:
輸入:nums = [3,1,3,4,2]
輸出:3
示例 3:
輸入:nums = [1,1]
輸出:1
示例 4:
輸入:nums = [1,1,2]
輸出:1
提示:
2 <= n <= 3 * 104
nums.length == n + 1
1 <= nums[i] <= n
nums
中 只有一個整數 出現 兩次或多次 ,其餘整數均只出現 一次
進階:
- 如何證明
nums
中至少存在一個重複的數字? - 你可以在不修改陣列
nums
的情況下解決這個問題嗎? - 你可以只用常量級 O ( 1 ) O(1) O(1) 的額外空間解決這個問題嗎?
- 你可以設計一個時間複雜度小於 O ( n 2 ) O(n^2) O(n2) 的解決方案嗎?
思路:
- 第一反應一般是對陣列排序,然後遍歷一次,遇到相鄰的兩個元素相等了,那這個數就是答案。但時間複雜度是 O ( n l o g n ) O(nlogn) O(nlogn)(排序),會修改元素組,不滿足進階要求。
- 不難想到用一個雜湊表
Set
存元素,即遍歷一遍陣列,元素都存入雜湊表,當出現重複的時候就是答案。時間複雜度 O ( n ) O(n) O(n),空間複雜度 O ( n ) O(n) O(n),空間複雜度不滿足條件進階要求。 - 常見的一種方法就是遍歷一遍陣列,讓陣列中的元素與下標一一對應,如
nums[1] = 1,nums[2] = 2
,即讓nums[i] = nums[nums[i]]
,0~n
共有n+1
個下標,但是數字是1~n
,故有一個下標對應的位置需要存兩個數,這個數就是重複的,當然這也改變了原陣列,不符合進階要求。不改變原陣列的話,也可以開闢一個新陣列,用於計數,即遍歷一次陣列,temp[nums[i]]++
temp
陣列,temp[i] > 1
的i
就是重複的數字,但不符合常量級 O ( 1 ) O(1) O(1) 的額外空間。 - 排除上述常見方法後,總算想到殺手鐗,二分演算法了。
注意:分的區間是數的範圍,而不是索引的範圍,如果在這個範圍的數字個數大於區間長度,那麼這個區間內一定有數字重複了。繼續對該區間進行劃分,直到區間長度為1為止。
時間複雜度:分治法為
O
(
l
o
g
n
)
O(logn)
O(logn),每次都要統計區間範圍內的數字,複雜度為
O
(
n
)
O(n)
O(n),所以總的複雜度為
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)。
class Solution {
public int findDuplicate(int[] nums) {
//陣列中的數字在1~n之間,故按數字的範圍二分,left和right起始值如下
int left = 1;//數字最小為1
//由題意知,nums.length = n + 1,故n=nums.length-1
int right = nums.length - 1;
while(left < right){//當left=right時,即只剩下一個數時停止迴圈,那個數就是結果
int mid = (left + right)/2;
//計算整個陣列在前半部分的個數
int count = getCount(nums,left,mid);
if(count > (mid - left + 1)){//個數大於區間本該有的個數,該區間出現了重複數字
right = mid;
}else{
left = mid+1;
}
}
return left;//實際上最後返回left還是right都可以,因為他們最後相等
}
public int getCount(int[] nums,int left,int right){
int count = 0;
for(int num : nums){
//統計left~right(假設是1~4)這right-left+1(=4)個數字在整個陣列中出現的次數
if(num >= left && num <= right){
count++;
}
}
return count;
}
}