[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 ≤ i, j < 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
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; } };
參考資料: