找出數組中重複的數字
問題描述:
力扣鏈接
在一個長度爲 n 的數組 nums 裏的所有數字都在 0~n-1 的範圍內。數組中某些數字是重複的,但不知道有幾個數字重複了,也不知道每個數字重複了幾次。請找出數組中任意一個重複的數字。
方法一:排序做法
將數組進行排序,排序後再查找重複數字。
class Solution {
public int findRepeatNumber(int[] nums) {
Arrays.sort(nums);
int pre = nums[0];
for(int index=1; index < nums.length; index++){
if(pre == nums[index])
return pre;
pre = nums[index];
}
return -1;
}
}
時間複雜度:O(nlogn),空間複雜度:O(1)
方法二:哈希表
從頭到尾按順序掃描數組的每個數字,每掃描到一個數字的時候,都可以用O(1)的時間來判斷哈希表裏是否存在這個數字,如果不存在的話,就把該數字加入到這個哈希表中,如果存在的話就說明該數字重複。
class Solution {
public int findRepeatNumber(int[] nums) {
int n = nums.length;
Set<Integer> set = new HashSet<Integer>();
for(int index=0;index<n;index++){
if(set.contains(nums[index]))
return nums[index];
set.add(nums[index]);
}
return -1;
}
}
時間複雜度:O(n),空間複雜度:O(n)
方法三:原地置換法
即書中所提到的方法:當掃描到下標爲i的數字時,首先比較這個數字(用m表示)是否等於i,如果相等,掃面下一個數字;如果不相等,將它與下標爲m的數組中數字進行比較,如果相等就說明該數字時重複的(再下標和im的位置都出現了),如果不相等,就把下標爲i(值爲m)與下標爲m的數組中的兩個數進行互換,把m放到屬於他的位置。接下來重複這個比較、交換的過程,直到我們發現一個重複的數字。
class Solution {
public int findRepeatNumber(int[] nums) {
int n = nums.length;
for(int index=0;index<n;index++){
while(nums[index] != index){
if(nums[index] == nums[nums[index]])
return nums[index];
int temp = nums[index];
nums[index] = nums[temp];
nums[temp] = temp;
}
}
return -1;
}
}
小心錯誤交換方式:num[index]的值再第二行時就已經改變了
int temp = nums[index];
nums[index] = nums[nums[index]];
nums[nums[index]] = temp;
時間複雜度:O(n),空間複雜度:O(1)。時間複雜度解釋:代碼中經管有一個二重循環,但每個數字最多兩次交換就能找到屬於它的位置。
總結:第一種方式最簡單明瞭,但是時間複雜度最高,空間複雜度較低;第二種方法時間複雜度較低,但是空間複雜度最高;第三種方式時間複雜度和空間複雜度都是最低的,也就是書中的方法。
這題是不能用二分解的,原因就出在數組的長度上面,因爲278題長度n+1,1 ~ n代表必定有一個數字出現兩次,所以你用二分有個區間的數字在原數組中出現次數是>這個區間範圍的,但是對這題而言,長度n,0 ~ n-1且有重複,代表有數重複,就有數不存在,這樣長度才符合,這將會導致可能重複的數和不存在的數在同一個區間你無法找出來。
不修改數組找到重複數字
問題描述:
力扣鏈接
Given an array nums containing n + 1 integers where each integer is between 1 and n (inclusive), prove that at least one duplicate number must exist. Assume that there is only one duplicate number, find the duplicate one.
原地置換法
由於不能修改數組,可以使用一個輔助數組來存儲上述的數組,然後再用原地置換法解決問題,這種方法的時間複雜度爲O(n),空間複雜度爲O(n)。參考上面的代碼即可。
二分查找法
將1 ~ n的數字從中間的數字m分爲兩個部分,前面一半是1 ~ m,後面的一半爲m+1 ~ n,如果1 ~ m中的數字的個數超過了m,那麼這一區間一定重複的數字,否則,另一個區間裏一定會有重複的數字。將有重複的數字的區間再一分爲二,直到找到哪個重複的數字。
class Solution {
public int findDuplicate(int[] nums) {
int n = nums.length;
int start = 1;
int end = n -1;
while (end >= start) {
int middle = ((end - start) >> 1) + start;
int count = countRange(nums,start,middle);
if (end == start) {
if (count > 1)
return start;
else
break;
}
if (count > (middle - start + 1)) {
end = middle;
} else {
start = middle + 1;
}
}
return -1;
}
int countRange(int[] nums, int start, int end) {
int count = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] >= start && nums[i] <= end)
count++;
}
return count;
}
}
輸入長度爲n的數組,那麼函數countRange將被調用O(logn)次,每次需要O(n)的時間。所以該方法的時間複雜度爲:O(nlogn),空間複雜度爲O(1)。時間換空間。