1. 程式人生 > >Leetcode 421.陣列中兩數的最大異或值

Leetcode 421.陣列中兩數的最大異或值

陣列中兩數的最大異或值

給定一個非空陣列,陣列中元素為 a0, a1, a2, … , an-1,其中 0 ≤ ai < 231 

找到 ai 和a最大的異或 (XOR) 運算結果,其中0 ≤ i,j < n

你能在O(n)的時間解決這個問題嗎?

示例:

輸入: [3, 10, 5, 25, 2, 8]

 

輸出: 28

 

解釋: 最大的結果是 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'異或'得到。參見程式碼如下:

 1 import java.util.HashSet;
 2 
 3 class Solution {
 4     public static int findMaximumXOR(int[] nums) {
 5         int max = 0;
 6         int mask = 0;
 7         for (int i = 31; i >= 0; i--) {
 8             // 從最高位試著找nums的字首
 9             mask = mask | (1 << i);
10             HashSet<Integer> set = new HashSet<Integer>();
11             for (int num : nums) {
12                 set.add(mask & num);
13             }
14             //判斷最大異或結果的當前位是否為1
15             int temp=max|(1<<i);
16             for (int prefix: set){
17                 if (set.contains(prefix^temp)) {
18                     max=temp;
19                 }
20             }
21         }
22         return max;
23     }
24 }