劍指offer:面試題3:數組中重複的數字

1.數組介紹

首先對數組進行基本的介紹,數組的特點就是連續存儲。在C++中,我們一般有兩種類型的數組:一種是靜態數組,我們事先知道其大小。一種是動態數組,也就是vector,事先可以不規定大小。

2.題目介紹

這一類型題目有很多種類型,比如數組是否有序,重複元素的個數等等。

2.1 找出數組中的重複元素

在一個長度爲n的數組裏的所有數字都在0到n-1的範圍內。 數組中某些數字是重複的,但不知道有幾個數字是重複的。也不知道每個數字重複幾次。請找出數組中任意一個重複的數字。 例如,如果輸入長度爲7的數組{2,3,1,0,2,5,3},那麼對應的輸出是第一個重複的數字2。

根據題意,我們可以知道數組元素的範圍是確定的,以及最後的要求是輸出任意一個元素。

2.1.1排序,然後遍歷

已知數組是無序的(沒有說有序就是無序),我們可以先對數組進行排序,比如 {2,3,1,0,2,5,3}\{2,3,1,0,2,5,3\},排序之後得到{0,1,2,2,3,3,5}\{0,1,2,2,3,3,5\},這樣一來,我們就可以通過判斷相鄰元素是否相等,得到重複元素。這種時間複雜度爲O(nlogn)O(nlogn),空間複雜度爲O(1)O(1)。因爲排序是在數組上進行的。代碼如下:

bool duplicate1(int numbers[], int length, int* duplication) {
        sort(numbers,numbers+length);
        bool flag=false;
        for(int i=0;i<length-1;i++)
        {
            if(numbers[i]==numbers[i+1])
            {
                *duplication= numbers[i];
                flag=true;
                break;
            }
        }
        return flag;
}

2.1.2 使用哈希表

因爲數組中的每個元素都在確定的範圍裏,並且都爲正數。所以我們可以直接使用數組當作哈希表。因此我們額外創建一個長度爲n的數組,使用輸入數組的元素作爲下標。以num={2,3,1,0,2,5,3}num=\{2,3,1,0,2,5,3\}爲例,我們初始化一個全是0的哈希數組,{0,0,0,0,0,0,0,0}\{0,0,0,0,0,0,0,0\},我們最終要求的哈希表是{01,2,3,4,5,6,7}\{0,1,2,3,4,5,6,7\},即hash[i]=ihash[i]=i;所以當hash[num[i]]!=num[i]hash[num[i]]!=num[i]的時候,我們就將對應元素放入哈希表。比如,num[0]=2num[0]=2;hash[2]hash[2]此時爲0,所以將hash[2]=2hash[2]=2;當我們遍歷到num[4]num[4]的時候,我們發現此時hash[2]hash[2]已經等於2了,則說明之前已經遇到過2了,那麼2爲重複元素。空間複雜度爲O(n)O(n),時間複雜度爲O(n)O(n).

bool duplicate2(int numbers[], int length, int* duplication) {
        bool flag=false;
        //array initialize
        int hash_t[length];
        for(int i=0;i<length;i++)
        {
            hash_t[i]=0;
        }
        for(int i=0;i<length;i++)
        {
            if(hash_t[numbers[i]]!=0)
            {
                flag=true;
                *duplication=numbers[i];
                 break;
            }
            else
                hash_t[numbers[i]]=numbers[i];
        }
        return flag;
}

2.1.3 手動重排數組

使用哈希表,空間複雜度過高;使用排序,時間複雜度過高。因爲採取一種折中的辦法,通過交換手動將輸入數組排序爲哈希表的樣子,也就是把每個數組元素放在以該元素爲下標的位置。

  1. {2,3,1,0,2,5,3}\{2,3,1,0,2,5,3\} 對於第0個位置,num[0]!=0,我們不知道0在哪裏,但是我們知道num[0]=2 這個2應該放在哪裏。所以我們先查看第2個位置的值,發現第二個位置的值爲1,不爲2,因此我們交換第0個位置的元素和第二個位置的元素,即{1,3,2,0,2,5,3}\{1,3,2,0,2,5,3\}
  2. 交換之後,其實第0個位置的元素依然不符合要求,因此我們進行如上操作,交換第0個位置和第1個位置的元素,即 {3,1,2,0,2,5,3}\{3,1,2,0,2,5,3\}
  3. 依然不符合要求,進行操作,即{0,1,2,3,2,5,3}\{0,1,2,3,2,5,3\}
  4. 操作完成之後,發現第0~3位置都符合要求,因此我們對於第4個位置元素進行操作,第4個位置元素爲2,我們查看發現此位置的元素已經是2了,也就是說明此時不需要交換了,出現了一個多餘的2。依次類推,可以找出所有多餘元素。

時間複雜度爲O(n)O(n),空間複雜度O(1)O(1).

bool duplicate3(int numbers[], int length, int* duplication) {

    bool flag=false;
    for(int i=0;i<length;i++)
    {
        while(numbers[i]!=i)
        {
            if(numbers[numbers[i]]==numbers[i])//satisfied hash condition
            {
                flag=true;
                *duplication=numbers[i];
                return flag;
            }
            //exchange
            int temp=numbers[i];
            numbers[i]=numbers[temp];
            numbers[temp]=temp;
        }

    }
    return flag;

}

第三種方法效率最高,但是修改了數組本身,如果題目要求不修改數組呢?

2.2 不修改數組找到重複數字(Leetcode.287尋找重複數)

給定一個包含 n + 1 個整數的數組 nums,其數字都在 1 到 n 之間(包括 1 和 n),可知至少存在一個重複的整數。假設只有一個重複的整數,找出這個重複的數。

示例 1:

輸入: [1,3,4,2,2]
輸出: 2
示例 2:

輸入: [3,1,3,4,2]
輸出: 3
說明:

不能更改原數組(假設數組是隻讀的)。
只能使用額外的 O(1) 的空間。
時間複雜度小於 O(n2) 。
數組中只有一個重複的數字,但它可能不止重複出現一次。

2.2.1 使用hash表

因爲給定了數組元素的範圍,所以可以直接使用數組做hash表。和上面一樣,有O(n)O(n)的空間開銷。

int findDuplicate(vector<int>& nums) {
        int n=nums.size();
        int result;
        vector<int>hash_n(n,0);
        for(int i=0;i<n;i++)
        {
            if(hash_n[nums[i]]!=0)
            {
                result=nums[i];
                break;
            }
            else
            {
                hash_n[nums[i]]=nums[i];
            }
        }
        return result;
    }

2.2.2 使用二分查找

因爲不可以修改數組,所以我們應該就是查找重複元素。並且只有一個重複元素,所以目標就是找到那個重複元素。

因爲所有元素都在1-n之間,所以我們可以劃分前後兩個區間,比如n=5,我們分成兩份1-2,3-5,我們統計所有元素在這兩個區間的個數,以[1,3,4,2,2][1,3,4,2,2]爲例,在[1,2]之間的有3個,但是區間長度爲2,說明那個重複元素在[1,2]之間。然後再次進行劃分,因爲長度爲1,也就是統計1,2的次數,可以找到重複元素爲2. 時間複雜度爲O(nlogn)O(nlogn),空間複雜度爲O(1)O(1)

public:
    int CountNumber(vector<int>& nums,int start,int end_)
    {
    int n=nums.size();
    int ans=0;
    for(int i=0;i<n;i++)
    {
        if(nums[i]>=start&&nums[i]<=end_)
            ++ans;
    }
    return ans;
    }
    int findDuplicate(vector<int>& nums) {
        int length=nums.size();
        int start=1;
        int end_=length;
        while(start<=end_)
        {
            int mid=(start+end_)/2;
            int count_s_m=CountNumber(nums,start,mid);
            if(end_==start&&count_s_m>1)
                return start;
            if(count_s_m>mid-start+1)
                end_=mid;
            else
                start=mid+1;

        }
        return -1;
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章