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,那么最多只能有32位,我们用一个从左往右的mask,用来提取数字的前缀,然后将其都存入set中,我们用一个变量t,用来验证当前位为1再或上之前结果res,看结果和set中的前缀异或之后在不在set中,这里用到了一个性质,若a^b=c,那么a=b^c,因为t是我们要验证的当前最大值,所以我们遍历set中的数时,和t异或后的结果仍在set中,说明两个前缀可以异或出t的值,所以我们更新res为t,继续遍历,如果上述讲解不容易理解,那么建议自己带个例子一步一步试试,并把每次循环中set中所有的数字都打印出来,基本应该就能理解了,参见代码如下:
class Solution { public: int findMaximumXOR(vector<int>& nums) { int res = 0, mask = 0; for (int i = 31; i >= 0; --i) { mask |= (1 << i); 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; } };解法二:
遍历数组,对每个数的二进制构建一棵字典树。
再次遍历数组,对每个数的每个二进制位与1异或:数组元素的二进制位为0,则寻找到值为1的子树,数组元素的二进制位为1,则寻找到值为0的子树。(当然也可以与0异或)
与1异或后子树不为空,则可计数并累加;与1异或后子树为空,则继续向下查找。
划重点!!!
对数组元素的二进制构建Trie树。寻找子树的时候,与1(或0)异或。 class Solution { public: //定义数的节点类型 struct Trie { vector<Trie*> child; Trie() : child(vector<Trie*>(2, NULL)) {} }; //建树 void add(int num) { Trie* tmp = root; for(int i = 31; i >= 0; i--) { int val = (num >> i) & 1; if(!tmp->child[val]) { tmp->child[val] = new Trie(); //有节点即表示有数,null表示没有数 } tmp = tmp->child[val]; } } //查找数 int search(int num) { int ans = 0; Trie* tmp = root; for(int i = 31; i >= 0; --i) { int val = (num >> i) & 1; ans = ans << 1; if(tmp->child[val]) { ans++; tmp = tmp->child[val]; } else { tmp = tmp->child[!val]; //不可能到不了根节点,非此即彼,所以此时选择child[!val]一定存在 } } return ans; } int findMaximumXOR(vector<int>& nums) { root = new Trie(); for(auto val : nums) { add(val); } int ans = INT_MIN; for(auto val : nums) { ans = max(ans, search(~val)); //取反保证查找返回的是“异或”后的最大值 } return ans; } private: Trie* root; };总结:
两种方法思路完全不同,第一种利用异或的性质,a^b=c,则a=b^c,类似于预设未知数c,通过判断b^c是否有对应的a来判断c的值,若有c=1,若无c=0;第二种方法则将问题转换为二叉树的查找问题,将找数变为找叶子结点,十分巧妙。
