1. 程式人生 > >[LeetCode] Maximum XOR of Two Numbers in an Array 陣列中異或值最大的兩個數字

[LeetCode] Maximum XOR of Two Numbers in an Array 陣列中異或值最大的兩個數字

Given a non-empty array of numbers, a0, a1, a2, … , an-1, where 0 ≤ ai < 231.

Find the maximum result of ai XOR aj, where 0 ≤ ij < n.

Could you do this in O(n) runtime?

Example:

Input: [3, 10, 5, 25, 2, 8]

Output: 28

Explanation: The maximum result is 5 ^ 25 = 28.

這道題是一道典型的位操作Bit Manipulation的題目,我開始以為異或值最大的兩個數一定包括陣列的最大值,但是OJ給了另一個例子{10,23,20,18,28},這個陣列的異或最大值是10和20異或,得到30。那麼只能另闢蹊徑,正確的做法是按位遍歷,題目中給定了數字的返回不會超過231

,那麼最多隻能有32位,我們用一個從左往右的mask,用來提取數字的字首,然後將其都存入HashSet中,我們用一個變數t,用來驗證當前位為1再或上之前結果res,看結果和HashSet中的字首異或之後在不在HashSet中,這裡用到了一個性質,若a^b=c,那麼a=b^c,因為t是我們要驗證的當前最大值,所以我們遍歷HashSet中的數時,和t異或後的結果仍在HashSet中,說明兩個字首可以異或出t的值,所以我們更新res為t,繼續遍歷,如果上述講解不容易理解,那麼建議自己帶個例子一步一步試試,並把每次迴圈中HashSet中所有的數字都打印出來,基本應該就能理解了,算了,還是博主帶著大家來看題目中給的例子吧:

3        10        5        25        2        8

11      1010     101     11001     10      1000

我們觀察這些數字最大的為25,其二進位制最高位在 i=4 時為1,那麼我們的迴圈[31, 5]之間是取不到任何數字的,所以不會對結果res有任何影響。

當 i=4 時,我們此時mask為前28位為‘1’的二進位制數,跟除25以外的任何數相‘與’,都會得到0。 然後跟25的二進位制數10101相‘與’,得到二進位制數10000,存入HashSet中,那麼此時HashSet中就有0和16兩個數字。此時我們的t為結果res(此時為0)‘或’上二進位制數10000,得到二進位制數10000。然後我們遍歷HashSet,由於HashSet是無序的,所以我們會取出0和16中的其中一個,如果prefix取出的是0,那麼t=16‘異或’上0,還等於16,而16是在HashSet中存在的,所以此時結果res更新為16,然後break掉遍歷HashSet的迴圈。實際上prefix先取16的話也一樣,那麼t=16‘異或’上16,等於0,而0是在HashSet中存在的,所以此時結果res更新為16,然後break掉遍歷HashSet的迴圈。

3        10        5        25        2        8

11      1010     101     11001     10      1000

當 i=3 時,我們此時mask為前29位為‘1’的二進位制數,如上所示,跟數字3,5,2中任何一個相‘與’,都會得到0。然後跟10的二進位制數1010,或跟8的二進位制數1000相‘與’,都會得到二進位制數1000,即8。跟25的二進位制數11001相‘與’,會得到二進數11000,即24,存入HashSet中,那麼此時HashSet中就有0,8,和24三個數字。此時我們的t為結果res(此時為16)‘或’上二進位制數1000,得到二進位制數11000,即24。此時遍歷HashSet中的數,當prefix取出0,那麼t=24‘異或’上0,還等於24,而24是在HashSet中存在的,所以此時結果res更新為24,然後break掉遍歷HashSet的迴圈。大家可以嘗試其他的數,當prefix取出24,其實也可以更新結果res為24的。但是8就不行啦,因為HashSet中沒有16。不過無所謂了,我們只要有一個能更新結果res就可以了。

3        10        5        25        2        8

11      1010     101     11001     10      1000

當 i=2 時,我們此時mask為前30位為‘1’的二進位制數,如上所示,跟3的二進位制數11相‘與’,會得到二進位制數0,即0。然後跟10的二進位制數1010相‘與’,會得到二進位制數1000,即8。然後跟5的二進位制數101相‘與’,會得到二進位制數100,即4。然後跟25的二進位制數11001相‘與’,會得到二進位制數11000,即24。跟數字2和8相‘與’,分別會得到0和8,跟前面重複了。所以最終HashSet中就有0,4,8,和24這四個數字。此時我們的t為結果res(此時為24)‘或’上二進位制數100,得到二進位制數11100,即28。那麼就要驗證結果res能否取到28。我們遍歷HashSet,當prefix取出0,那麼t=28‘異或’上0,還等於28,但是HashSet中沒有28,所以不行。當prefix取出4,那麼t=28‘異或’上二進位制數100,等於24,在HashSet中存在,Bingo!結果res更新為28。其他的數可以不用試了。

3        10        5        25        2        8

11      1010     101     11001     10      1000

當 i=1 時,我們此時mask為前31位為‘1’的二進位制數,如上所示,每個數與mask相‘與’後,我們HashSet中會有2,4,8,10,24這五個數。此時我們的t為結果res(此時為28)‘或’上二進位制數10,得到二進位制數11110,即30。那麼就要驗證結果res能否取到30。我們遍歷HashSet,當prefix取出2,那麼t=30‘異或’上2,等於28,但是HashSet中沒有28,所以不行。當prefix取出4,那麼t=30‘異或’上4,等於26,但是HashSet中沒有26,所以不行。當prefix取出8,那麼t=30‘異或’上8,等於22,但是HashSet中沒有22,所以不行。當prefix取出10,那麼t=30‘異或’上10,等於20,但是HashSet中沒有20,所以不行。當prefix取出24,那麼t=30‘異或’上24,等於6,但是HashSet中沒有6,所以不行。遍歷完了HashSet所有的數,結果res沒有被更新,還是28。

3        10        5        25        2        8

11      1010     101     11001     10      1000

當 i=0 時,我們此時mask為前32位為‘1’的二進位制數,如上所示,每個數與mask相‘與’後,我們HashSet中會有2,3,5,8,10,25這六個數。此時我們的t為結果res(此時為28)‘或’上二進位制數1,得到二進位制數11101,即29。那麼就要驗證結果res能否取到29。取出HashSet中每一個數字來驗證,跟上面的驗證方法相同,這裡博主偷懶就不寫了,最終可以發現,結果res無法被更新,還是28,所以最終的結果就是28。

綜上所述,我們來分析一下這道題的核心。我們希望用二進位制來拼出結果的數,最終結果28的二進位制數為11100,裡面有三個‘1’,我們來找一下都是誰貢獻了這三個‘1’?在 i=4 時,數字25貢獻了最高位的‘1’,在 i=3 時,數字25貢獻了次高位的‘1’,在 i=2 時,數字5貢獻了第三位的‘1’。而一旦某個數貢獻了‘1’,那麼之後在需要貢獻‘1’的時候,此數就可以再繼續貢獻‘1’。而一旦有兩個數貢獻了‘1’後,那麼之後的‘1’就基本上只跟這兩個數有關了,其他數字有‘1’也貢獻不出來。驗證方法裡使用了前面提到的性質,a ^ b = t,如果t是所求結果話,我們可以先假定一個t,然後驗證,如果a ^ t = b成立,說明該t可以通過a和b‘異或’得到。參見程式碼如下:

class Solution {
public:
    int findMaximumXOR(vector<int>& nums) {
        int res = 0, mask = 0;
        for (int i = 31; i >= 0; --i) {
            mask |= (1 << i);
            unordered_set<int> s;
            for (int num : nums) {
                s.insert(num & mask);
            }
            int t = res | (1 << i);
            for (int prefix : s) {
                if (s.count(t ^ prefix)) {
                    res = t;
                    break;
                }
            }
        }
        return res;
    }
};

參考資料: