【劍指Offer】50、數組中重復的數字


  題目描述:

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

  解題思路:

  解決這個問題最簡單的辦法是將輸入的數組排序,從排序的數組中找出重復的數字只需要從頭到尾掃描即可,所以先排序再查找的時間復雜度主要就取決於排序算法,一般為O(nlogn)。

  還是那句話:“最容易想到的往往不是最佳解法”。

  進一步考慮,我們也不難想到借助空間換時間的思路,通過哈希表來解決。從頭到尾按順序掃描整個數組,依次將其存入哈希表,每掃描到一個元素,都可以用O(1)的時間判斷哈希表里是否已經存在該值,如果不存在,就將其加入哈希表,繼續掃描下一個,如果存在,那么該元素就是第一個重復的數字。這個算法的時間復雜度是O(n),但很顯然這個效率的提升也是用空間換來的。

  因此,我們希望得到一種不消耗額外空間的算法,也就是本題的第三種解法:數組重排。由於題目中告訴我們所有的數字都在0到n-1的范圍內,因此如果沒有重復,那么所存儲的值也正好是0到n-1這n個數字,我們把原數組重新排列為一個元素和對應下標值相同的數組。具體思路如下:

  從頭到尾掃描整個數組中的數字,當掃描到下標為i的數字時,首先比較這個數字(用m表示)是不是等於下標i,如果是,接着比較下一個數字;如果不是,則將其與第m個數字比較,若與第m個數字相同,則說明它就是一個重復數字,如果不同,就將其與第m個數字進行交換,也就是把它放到自己應在的位置去。重復這個過程,直到該位置上的數與下標相同為止。

  該算法看起來是兩層循環,但是每個數字最多進行兩次交換就會找到屬於自己的位置,因為總的時間復雜度還是O(n),不需要額外內存。

  舉例:

  以{2,3,1,0,2,5,3}為例:

  • 0(索引值)和2(索引值位置的元素)不相等,並且2(索引值位置的元素)和1(以該索引值位置的元素2為索引值的位置的元素)不相等,則交換位置,數組變為:{1,3,2,0,2,5,3};
  • 0(索引值)和1(索引值位置的元素)仍然不相等,並且1(索引值位置的元素)和3(以該索引值位置的元素1為索引值的位置的元素)不相等,則交換位置,數組變為:{3,1,2,0,2,5,3};
  • 0(索引值)和3(索引值位置的元素)仍然不相等,並且3(索引值位置的元素)和0(以該索引值位置的元素3為索引值的位置的元素)不相等,則交換位置,數組變為:{0,1,2,3,2,5,3};
  • 0(索引值)和0(索引值位置的元素)相等,遍歷下一個元素;
  • 1(索引值)和1(索引值位置的元素)相等,遍歷下一個元素;
  • 2(索引值)和2(索引值位置的元素)相等,遍歷下一個元素;
  • 3(索引值)和3(索引值位置的元素)相等,遍歷下一個元素;
  • 4(索引值)和2(索引值位置的元素)不相等,但是2(索引值位置的元素)和2(以該索引值位置的元素2為索引值的位置的元素)相等,則找到了第一個重復的元素。

  編程實現(Java):

public class Solution {
    // Parameters:
    // numbers:     an array of integers
    // length:      the length of array numbers
    // duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
    // Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
    // 這里要特別注意~返回任意重復的一個,賦值duplication[0]
    // Return value:true if the input is valid, and there are some duplications in the array number
    //                     otherwise false
    public boolean duplicate(int numbers[],int length,int [] duplication) {
        if(numbers==null || length<1)
            return false;
        //三種方法:排序后查找、哈希表
        //第三種:數組重排
         for(int i=0;i<length;i++){
            while(numbers[i]!=i){ //每個元素最多被交換兩次就可以找到自己的位置,依次復雜度是O(n)
                if(numbers[numbers[i]]==numbers[i]){
                    duplication[0]=numbers[i];
                    return true;
                }else{
                    int temp=numbers[numbers[i]]; //交換
                    numbers[numbers[i]]=numbers[i]; //將numbers[i]放到屬於他的位置上
                    numbers[i]=temp;
                }
            }
        }
        return false;
    }

  補充說明:

  (1) 如果只要求判斷是否有重復元素,不用找到該值,那么可以使用異或的思路,所有的元素和從0到n-1的下標一起異或,那么如果沒有重復元素,相當於從0到n-1每個元素都出現了兩次(下標和對於的元素),最后的異或結果一定是0,否則說明有重復元素。

  (2) 上述數組重排的思路雖然比較巧妙,但是一個缺點是改變了原來的數組,如果題目要求不能修改原來的數組,一個是可以使用哈希表,另一個是可以使用劍指Offer上給出的二分查找思路,但是相對比較麻煩。具體如下:

  以長度為8的數組{2,3,5,4,3,2,6,7}為例,根據題目要求,這個長度為8的數組,所有元素都在1到7的范圍內,中間的數字4將1—7分成兩部分,分別為1—4和5—7,接下來統計1—4在數組中出現的次數,發現是5次,則說明這4個數字中一定有重復數字。接下來再把1—4分成1、2和3、4兩部分,1和2一共出現了兩次,3和4一共出現了3次,說明3和4中有一個重復,再分別統計即可得到是3重復了。這並不保證找出所有的重復數字,比如2就沒有找到。

  實際上,這種二分查找時間復雜度也達到了O(nlogn),不如用哈希表空間換時間來的直觀。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM