【劍指offer】 面試題3 陣列中重複的數字
題目一 找出陣列中重複的數字
在一個長度為n的數組裡的所有數字都在0~n-1的範圍內。 陣列中某些數字是重複的,但不知道有幾個數字是重複的。也不知道每個數字重複幾次。請找出陣列中任意一個重複的數字。例如,如果輸入長度為7的陣列{2,3,1,4,2,5,3} 那麼對應的輸出是重複的數字2或3。
方法1 雜湊表
時間複雜度O(n),空間複雜度O(n), 額外空間用於構建雜湊表
思想:建立一個雜湊表,然後對當前陣列中元素遍歷,若當前所遍歷的元素不在雜湊表中,則放入,若存在即為重複數字。
對應關係【當前元素值 —>雜湊表的索引】。 例: 當前遍歷位數字2, 如果hash[2]=true。表示已經2存在雜湊表中。
程式碼如下:
# include <iostream>
# include <vector>
# include <memory>
using namespace std;
bool duplicate(int numbers[], int length, int* duplication){
if(numbers == nullptr || length <= 1 ) return false; // 等於1就是沒重複
int *hashTable = new int[length];
for(int i=0;i< length;++i)
{
if(hashTable[numbers[i]]== true) // 說明雜湊表中已經存在此數
{
*duplication = numbers[i];
return true;
}
else
{
hashTable[numbers[i]] = true;
}
}
return false;
}
int main(){
int numbers[] = {2,3,1,0, 2,5,3};
int *duplication = new int(0);
int length = sizeof(numbers)/sizeof(numbers[0]);
duplicate(numbers,length, duplication);
cout<<*duplication<<endl;
return 0;
}
方法2 交換位置法
時間複雜度O(n),空間複雜度O(1) 在輸入上進行操作,不額外分配空間
陣列中的數字都在0~ n-1的範圍內,如果沒有重複的話,那麼當陣列排序之後,數字i 將出現在下標為i 的位置上,如果陣列中有重複,那麼有些位置可能存在多個數字,利用這點來進行操作。
首先,重排這個陣列,從頭到尾進行掃描,當在第i 位時,我們先判斷這個數字(m)是不是等於i,如果是,那麼掃描下一個數字,如果不是,則拿他和第m個數字進行比較,如果他和第m個數字相等,則找到了一個重複數字(因為同時出現在第i,m位置),如果他和第m個數字不相等,就把他們交換。
# include <iostream>
# include <vector>
# include <memory>
using namespace std;
bool duplicate(int numbers[], int length, int* duplication){
if(numbers == nullptr || length <= 1 ) return false; // 等於1就是沒重複
for(int i=0;i<length;++i)
{
while(numbers[i] !=i)
{
if(numbers[i] == numbers[numbers[i]]) // 如果要交換位置的兩個數相等,即為重複,否則互換位置。
{
*duplication = numbers[i];
return true;
}
int temp = numbers[i];
numbers[i] = numbers[temp];
numbers[temp] = temp;
}
}
return false;
}
int main(){
int numbers[] = {2,3,1,0,2,5,3};
int *duplication = new int(0);
int length = sizeof(numbers)/sizeof(numbers[0]);
duplicate(numbers,length, duplication);
cout<<*duplication<<endl;
return 0;
}
題目二 不修改陣列,找出陣列中重複的數字
在一個長度為n+1的數組裡的所有數字都在1~n的範圍內,所以陣列中是少有一個數字是重複的。請找出陣列中任意一個重複的數字,但不能修改輸入的陣列。例如輸入長度為8的陣列{2,3,5,4,3,2,6,7} 那麼輸出的重複數字是2或者3.
題目二看起來和題目一非常相似,但是不同的是,題目二中
明確規定不能修改輸入陣列,所以上面提到的交換法就不能再用。
方法1 建立輔助陣列
和上面的雜湊表類似,只要逐一的複製原陣列的每個數字複製到輔助陣列(如果被複制的數字是m,則把它複製到輔助陣列中下表為m的位置),這樣就很容易的發現那個數字是重複的。但是該方法需要O(n)的輔助空間。
方法2 類二分查詢
主要思想:
把1~n的數字 從中間的數字m分為兩部分,前面那一半為1~m, 後面一半為m+1~n。如果前一半(1-m)的數字總數超過了 m,那麼這一半的區間裡一定包含了重複的數字,否則,重複數字就在另一半的區間中。然後我們繼續對包含重複數字的區間一分為二,直到找到這個重複數字,其實這個方法和二分查詢類似,只是多了一步統計區間內數字個數。
# include <iostream>
# include <vector>
# include <memory>
using namespace std;
int getDuplication(const int *numbers, int length) {
if (numbers == nullptr || length <= 0) return -1;
int start = 1, end = length - 1;
while (start < end) {
int lowCnt = 0;
int mid = start + ((end - start) >> 1);
for (int i = 0; i != length; i++) {
if (numbers[i] <= mid && numbers[i] >= start) {
lowCnt++;
}
}
if (lowCnt > (mid - start + 1)) {
end = mid;
}
else {
start = mid + 1;
}
}
return start;
}
int main(){
int numbers[] = {2,4,1,0,3,5,3};
int length = sizeof(numbers)/sizeof(numbers[0]);
int Duplication = getDuplication(numbers,length);
cout<<"The Duplication is : "<<Duplication;
return 0;
}