劍指offer 03:陣列中的重複數字
阿新 • • 發佈:2021-11-10
問題
找出陣列中重複的數字。
在一個長度為 n 的陣列 nums 裡的所有數字都在 0~n-1 的範圍內。陣列中某些數字是重複的,但不知道有幾個數字重複了,也不知道每個數字重複了幾次。請找出陣列中任意一個重複的數字。
示例1
輸入:[2, 3, 1, 0, 2, 5, 3]
輸出:2或者3
解法一
思路:排序+遍歷
- 首先對資料排序,
- 排序完成後遍歷資料,如果遇到相同的資料,立即返回。
- 時間複雜度O(nlogn),空間複雜度O(logn)
具體實現:java
class Solution { public int findRepeatNumber(int[] nums) { Arrays.sort(nums); int compare = nums[0]; for (int i = 1; i < nums.length; i++) { if (compare == nums[i]) { return compare; } else { compare = nums[i]; } } return -1; } }
解法二
思路: hash表
- 遍歷陣列,將資料作為hash表的key,個數作為value
- 如果發現有一個key已經存在直接返回資料
- 時間複雜度O(n),空間複雜度O(n)
具體實現:java
class Solution { public int findRepeatNumber(int[] nums) { HashMap<Integer,Integer> map = new HashMap<>(nums.length); for(int i = 0;i<nums.length;i++){ if(map.get(nums[i])==null){ map.put(nums[i],1); } else{ return nums[i]; } } return -1; } }
解法三
思路:衝突法
分析:
可以知道所有數字都在0~n-1的範圍內,那麼我們可以知道:如果完全不衝突,必然0-n-1每個數一個,那麼我們可以將陣列下表作為key,必然有key是重複的。找到這個重複的key就好了
- 遍歷陣列,將資料放在對應的下標。如果下標衝突,則返回資料,如果不衝突,則與當前資料交換位置
- 比如:[2, 3, 1, 0, 2, 5, 3]
- 第一次:[1,3,2,0,2,5,3]
- 第二次:[3,1,2,0,2,5,3]
- 第三次:[0,1,2,3,2,5,3]
- 第四次,2衝突了,直接返回2
- 時間複雜度O(n),空間複雜度O(1)
class Solution { public int findRepeatNumber(int[] nums) { for (int i = 0; i < nums.length; ) { if (i==nums[i]){ i++; continue; } if (nums[nums[i]] == nums[i]) { return nums[i]; } else { int t = nums[i]; nums[i] = nums[t]; nums[t] = t; } } return -1; } }
問題變更一
如果需要找出全部的相同的數字,上面的方法還有效嗎?
- 全部可以實現(建議自行嘗試)
問題變更二
在一個長度為n+1的數組裡的所有數字都在1~n範圍內,所以陣列種至少有一個數字是重複的。請找出陣列種任意一個重複的數字,但不能修改輸入的陣列。
解法一
思路:複製陣列
- 首先將陣列複製下來。
- 然後利用原問題種的任意一種解法
程式碼參考以上
解法二
思路:二分查詢的思想
分析:
我們把1n的數字從中間的數字m分為兩部分,前面一般為1m,後面一般為m+1n。如果1m的數字的數目超過了m,那麼這一版的區間裡面一定包含重複的數字;如果兩個區間都相等,那麼將m的位置前移一位,再進行比較。以此為思想
- 比如陣列 [1,2,2,4,5]
- 第一次比較:m為3; 1-3和4-5分別有3個和2個。無法辨別,移動m=2
- 第二次比較:m為2;1-2和3-5分別有3個和2個。區間1-2中一定有重複的數字
- 第三次比較:m為1;1為1次,2為2次。重複數字為2
- 時間複雜度O(nlogn),空間複雜度O(1)
public int findRepeatNumber(int[] nums){
if (null == nums || nums.length == 0 || nums.length == 1) {
return -1;
}
int left = 1;
int right = nums.length;
int mid = left + (right - left) / 2;
while (left <= right) {
int preNums = mid - left + 1;
int pre = countRange(nums ,left, mid);
if (pre == preNums) {
mid = mid - 1;
//邊界值
if (mid < left){
mid = left + (right - left) / 2;
left = mid + 1;
mid = left + (right - left) / 2;
}
} else if (pre < preNums) {
if (left == right) {
return left;
}
left = mid + 1;
mid = left + (right - left) / 2;
} else {
if (left == right) {
return left;
}
right = mid;
mid = left + (right - left) / 2;
}
}
return -1;
}
參考:二分查詢