前言
本系列的文章爲筆者學習《劍指offer第二版》後的筆記整理以及Java實現,解法不保證與書上一致。
另外,我最近在系統整理一些 Java 後臺方面的面試題和參考答案,有找工作需求的童鞋,歡迎關注我的 Github 倉庫,如果覺得不錯可以點個 star 關注 :
題目描述
在一個長度爲 n 的數組裏的所有數字都在 0~n-1 的範圍內。 數組中某些數字是重複的,但不知道有幾個數字重複了,也不知道每個數字重複幾次。請找出數組中任意一個重複的數字。例如,如果輸入長度爲 7 的數組 {2,3,1,0,2,5,3},那麼對應的輸出是第一個重複的數字 2 或者 3。
解題思路
解決這個問題主要有三種方法:
思路一
先將輸入的數組排序,然後從頭到尾遍歷排序後的數組,如果有重複的數字,那麼一定會存在相鄰元素相等的情況。從頭到尾遍歷的過程種,只需相鄰元素涼涼比較,有相等的情況就找到了重複數字。很明顯,掃描數組需要的時間複雜度爲 O(n),而排序數組則需要 O(nlogn) 的時間複雜度,所以該方法需要總的時間複雜度爲 O(nlogn)。
/**
* 方法一:先排序後遍歷查找看是否有相鄰元素相等
* @param nums 待查找的數組
* @param duplication 用於保存重複數字,第一個被找到的重複數字存放在duplication[0]中
* @return 數組中的重複數字(如果有的話)
*/
public boolean duplicate1(int[] nums, int[] duplication) {
int length = nums.length;
if (nums == null || length <= 0) {
return false;
}
// 排序數組,時間複雜度爲 O(nlogn)
Arrays.sort(nums);
for (int i = 0; i < length - 1; i++) {
if (nums[i] == nums[i + 1]) {
// 排序後相鄰元素相等,說明有重複元素,將其保存到 duplication[0] 中
duplication[0] = nums[i];
return true;
}
}
return false;
}
思路二
通過哈希表來解決。從頭到尾按順序掃描數組中的每個數字,可以通過 O(1) 的時間複雜度來判斷哈希表中是否包含某個數字。每掃描到到一個數字時,如果哈希表中不存在該數字,就將其加入哈希表中。反之,表中已存在則說明找到了一個重複的數字。
這個算法的時間複雜度爲 O(n),但需要消耗大小爲 O(n) 的哈希表的空間,也就是說空間複雜度爲 O(n)。
代碼暫略...
思路三
根據題幹中的信息“長度爲 n 的數組裏的所有數字都在 0 到 n-1 的範圍內”可知,如果數組中沒有重複的數字,那麼數組經過排序之後數字 i 將出現在下標爲 i 的位置,即 nums[i] = i
。但現在由於數組中存在重複的數字,有些數字可能有多個,而有些數字可能空缺了。
如果數組中存在重複數字,就肯定會存在某個 j != i
時,nums[j] = nums[i]
的情況,只要能找到滿足這個條件的數字,則找到了重複的數字。現在我們可以來這麼做:
- 1、掃描到下標
i
時,如果nums[i] == i
,則繼續接着掃描下一個數; - 2、如果
nums[i] != i
,則令nums[i] = j
,比較nums[i]
與nums[j]
的值是否相等,如果相等,說明在與i
不同的位置 ‘j’ 找到了一個重複的數字。 - 在第
2
步的基礎上,如果nums[i] != nums[j]
,則將第i
個數字和第 ‘j’ 個數字交換。接下來不斷重複這個比較、交換的過程,直到找到一個重複的數字(除非不存在重複數字)。
package com.offers.chapter2.question03;
import java.util.Arrays;
/**
* @author Rotor
* @since 2019/9/21 0:35
*/
public class FindDuplicate {
/**
* 方法三:利用題幹信息"長度爲n的數組裏的所有數字都在0到n-1的範圍內"推導
*
* @param nums 待查找的數組
* @param duplication 用於保存重複數字,第一個被找到的重複數字存放在duplication[0]中
* @return 數組中的重複數字(如果有的話)
*/
public boolean duplicate3(int[] nums, int[] duplication) {
int length = nums.length;
if (nums == null || length <= 0) {
return false;
}
// 數組中包含 0~n-1 之外的無效的數字
for (int i = 0; i < length; i++) {
if (nums[i] < 0 || nums[i] > length - 1) {
return false;
}
}
for (int i = 0; i < length; i++) {
while (nums[i] != i) {
/**
* nums[i] != i,設numbers[i] = j,所以如果下面的 if 條件成立,即numbers[i] == numbers[j],
* 說明第 i 位與第 nums[i] 位數字相等,找到重複數字
*/
int j = nums[i];
if (nums[i] == nums[j]) {
duplication[0] = nums[i];
return true;
}
swap(nums, i, nums[i]);
}
}
return false;
}
// 交換 nums[i] 與 nums[nums[i]]
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
public static void main(String[] args) {
}
}
代碼中有一個兩重循環,但每個數字最多隻要交換兩次就能找到屬於它自己的位置,因此總的時間複雜度爲 O(n)。另外,所有的操作步驟都是在輸入數組上進行的,不需要額外分配內存。所以空間複雜度爲 O(1)。
總結
- 本體主要考察對一維數組的理解及編程能力。一維數組在內存中佔據連續的存儲空間,因此可以根據下標定位對應的元素;
- 遇到類似的問題時,可以結合題幹給出的信息進行分析。
後記
如果你同我一樣想要努力學好數據結構與算法、想要刷 LeetCode 和劍指 offer,歡迎關注我 GitHub 上的 LeetCode 題解:awesome-java-notes