1. 程式人生 > >【劍指offer】 面試題3 陣列中重複的數字

【劍指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;
}