JS演算法之找出缺失的整數

admin發表於2020-06-17

題目:一個無序陣列裡有99個不重複正整數,範圍從1到100,唯獨缺少一個整數。如何找出這個缺失的整數?

假設陣列長度是N,那麼該解法的時間複雜度是O(1),空間複雜度是O(N)。

此題有多種解法:

1、建立一個HashMap,以1到100為鍵,值都是0 。然後遍歷整個陣列,每讀到一個整數,就找到HashMap當中對應的鍵,讓其值加一。由於陣列中缺少一個整數,最終一定有99個鍵對應的值等於1, 剩下一個鍵對應的值等於0。遍歷修改後的HashMap,找到這個值為0的鍵。

[JavaScript] 純文字檢視 複製程式碼
// 當前的無序陣列
var DisorderedArray = [1,2,5,3,6,4,...99] //此處省略
var HashMap = new Array(n);
// 遍歷HashMap
for(var i = 0; i < HashMap.length; i++){
    HashMap[i] = 0;
}
// 遍歷無序陣列
for(var j = 0; j < DisorderedArray.length; j++){
    HashMap[j] += 1;
}
for(var k = 0; k < HashMap.length; k++){
    if(HashMap[k] === 0){
        return k;
        break;
    }
}

以上解法在時間上是最優的,但額外開闢了空間,如何能夠降低空間複雜度呢?

此時,便有了解法2:

先把陣列元素進行排序,然後遍歷陣列,檢查任意兩個相鄰元素數值是否是連續的。如果不連續,則中間缺少的整數就是所要尋找的;如果全都連續,則缺少的整數不是1就是100。

假設陣列長度是N,如果用時間複雜度為O(NLogN)的排序演算法進行排序,那麼該解法的時間複雜度是O(NLogN),空間複雜度是O(1)。

[JavaScript] 純文字檢視 複製程式碼
// 當前的無序陣列
var DisorderedArray = [1,2,5,3,6,4,...99] //此處省略
DisorderedArray.sort();
for(var i = 1; i < DisorderedArray.length; i++){
    var prev = DisorderedArray[i-1],
        item = DisorderedArray[i];
    if(item - prev != 1){
        return item-1;
        break;
    }else{
        if(DisorderedArray[0] == 2){
            return 1;
            break;
        }
        if(DisorderedArray[n] == (DisorderedArray.length-1)){
            return DisorderedArray.length;
            break;
        }
    }
}

以上解法是沒有開闢額外空間的,但是時間複雜度又大了,有辦法讓時間和空間都進一步優化麼?

此時,解法3出現:

很簡單也很高效的方法,先算出1+2+3….+100的和,然後依次減去陣列裡的元素,最後得到的差,就是唯一缺失的整數。

假設陣列長度是N,那麼該解法的時間複雜度是O(N),空間複雜度是O(1)。

[JavaScript] 純文字檢視 複製程式碼
var DisorderedArray = [1,2,5,3,6,4,...99] //此處省略
var res = 0;
for(var i = 1; i < 101; i++){
    res += i;
}
for(var j = 0; j < DisorderedArray.length; j++){
    res -= DisorderedArray[j]
}
// 最後這裡的res就是最後缺失的整數

以上解法,對於沒有重複元素的陣列,這解法在時間和空間上已經是最優了。下面開始擴充套件問題


題目擴充套件:一個無序陣列裡有若干個正整數,範圍從1到100,其中99個整數都出現了偶數次,只有一個整數出現了奇數次(比如1,1,2,2,3,3,4,5,5),如何找到這個出現奇數次的整數?

此擴充套件的題目來看,用上面的三種方法是沒有用的,在這裡,就要運用到異或運算,於是就有這了這樣的解法:遍歷整個陣列,依次做異或運算。由於異或在位運算時相同為0,不同為1,因此所有出現偶數次的整數都會相互抵消變成0,只有唯一出現奇數次的整數會被留下。


假設陣列長度是N,那麼該解法的時間複雜度是O(N),空間複雜度是O(1)。

[JavaScript] 純文字檢視 複製程式碼
// js異或運算子(^)
var arr = [4,4,6,8,8,2,3,3,7,6,7,1,5,9,1,5,9];
var res;
for (var i = 0; i < arr.length; i++) {
    var flag = false;
    for (var j = 0; j < arr.length; j++) {
        if(i != j){
            var aaa = arr[i] ^ arr[j];
            if(!aaa) {
                flag = true;
                break;
            }
        }
    }
    if(!flag){
        res = i;
        break;
    }
}
console.log(res)  // 5
console.log(arr[res])  // 2

此處我不得不說一下上面的方法,雖然上面的程式碼解決了問題,可是還是使用了大題程式碼,這在效能上還是會有很大的損耗的。於是就有了我下面這段程式碼,依然可以得到答案,而且大減少了程式碼量

[JavaScript] 純文字檢視 複製程式碼
var arr = [4,4,6,8,8,2,3,3,7,6,7,1,5,9,1,5,9];
arr.reduce((prev,next) => prev ^ next); // 此處得到結果為2
// 由於異或在位運算時相同為0,不同為1,因此所有出現偶數次的整數都會相互抵消變成0,只有唯一出現奇數次的整數會被留下

題目第二次擴充套件:一個無序陣列裡有若干個正整數,範圍從1到100,其中98個整數都出現了偶數次,只有兩個整數出現了奇數次(比如1,1,2,2,3,4,5,5),如何找到這個出現奇數次的整數?

解法:


遍歷整個陣列,依次做異或運算。由於陣列存在兩個出現奇數次的整數,所以最終異或的結果,等同於這兩個整數的異或結果。這個結果中,至少會有一個二進位制位是1(如果都是0,說明兩個數相等,和題目不符)。


舉個例子,如果最終異或的結果是5,轉換成二進位制是00000101。此時我們可以選擇任意一個是1的二進位制位來分析,比如末位。把兩個奇數次出現的整數命名為A和B,如果末位是1,說明A和B轉為二進位制的末位不同,必定其中一個整數的末位是1,另一個整數的末位是0。


根據這個結論,我們可以把原陣列按照二進位制的末位不同,分成兩部分,一部分的末位是1,一部分的末位是0。由於A和B的末位不同,所以A在其中一部分,B在其中一部分,絕不會出現A和B在同一部分,另一部分沒有的情況。


這樣一來就簡單了,我們的問題又迴歸到了上一題的情況,按照原先的異或解法,從每一部分中找出唯一的奇數次整數即可。


假設陣列長度是N,那麼該解法的時間複雜度是O(N)。把陣列分成兩部分,並不需要藉助額外儲存空間,完全可以在按二進位制位分組的同時來做異或運算,所以空間複雜度仍然是O(1)。

相關文章