给你一个整数数组nums,返回nums[i] XOR nums[j]的最大运算结果。其中 0<=i<=j<n
提示:
1 <= nums.length <= 2 * 104
0 <= nums[i] <= 231 - 1
示例1:
输入:nums = [3,10,5,25,2,8]
输出:28
解释:最大运算结果是 5 XOR 25 = 28.
示例2:
输入:nums = [0]
输出:0
分析: 一开始想到的是暴力搜索法,时间复杂度是0(N^2),很不幸超时了,代码如下:
class Solution {
public:
int findMaximumXOR(vector<int>& nums) {
//先试下暴力法能否解
int n=nums.size();
int maxone=0;
for(int i=0; i<n-1;i++){
for(int j=i+1; j<n;j++){
maxone=max(nums[i]^nums[j],maxone);
}
}
return maxone;
}
};
..................................
既然0(N^2)的时间复杂度会超时,那么就需要换一种思路降低时间复杂度。在本题要发挥异或的特点。下面贴上这道题的一种能通过的解法。其基本思路是应用了贪心原则!
本题关键在于,最终最大的异或结果一定是由所有数中,高位最先为1的数(也就是最大的数)参与得到的。算法的中心思想就是假设我们要求的最大异或值为x,根据题目的定义,x是最多31位的二进制数,我们每一次都对x的最高位做是否为1的假设,然后不断更新x的位数进一步假设,如果可以为1就进行更新1,不可以为1就赋值为0,直到对x的31个位数进行全部赋值,那么最大的x就出来了。
可以看到第一次的赋值结果是在原数组中某两个数字异或操作可以得到接受的,然后第二次赋值的结果是在第一次结果的基础上判断是否可以赋值1,在这里我们虽然不知道最终是哪两个数字异或得到的最大结果,但是能保证每一步的判断都是可以由某两个数字操作真实可存在的,因为每一步的操作是以上一步的结果位根基的,不会存在说之针对某一位进行判断1或者0而不考虑前面位数,这样可能导致最终的最大XOR结果来自多个不同数字不同位的有效XOR而不是某两个特定数字的XOR。
说了这么多,关键在于如何判断x从前往后的第n为是否可以为1.用语言描述就是:
x
=
a
i
⊕
a
j
x=a_i⊕a_j
x=ai⊕aj,则
x
k
=
a
i
k
⊕
a
j
k
x^k=a^k_i⊕a^k_j
xk=aik⊕ajk,则
a
j
k
=
x
k
⊕
a
i
k
a^k_j=x^k⊕a^k_i
ajk=xk⊕aik
上式中
a
i
a_i
ai和
a
j
a_j
aj是原数组中两个不同下标的元素,k代表31位的二进制数从左到右到第k位的元素整体。从上式可以看出,我们将k从1到31位不断增加,来对x进行是否为1进行判断,将当前情况下数组中的所有元素前k位存入哈希表,假设在查重过程中
a
j
k
=
x
k
⊕
a
i
k
a^k_j=x^k⊕a^k_i
ajk=xk⊕aik 满足了条件,证明x的第k位可以为1,否则就为1。程序包含一个31位的循环(不断查找x的位数),在每个循环里面吧所有的可能的
a
j
k
a^k_j
ajk 存入哈希表,然后我们再取
a
i
k
a^k_i
aik与
x
k
x^k
xk 做异或运算,如果在哈希表里面查到了这个结果,说明是可行的,没查到说明x的当前位不能是1.注意到
a
i
k
a^k_i
aik 也会不断遍历所谓
a
j
k
a^k_j
ajk的的值,他们本质上是个体对整体的概念,所有的
a
i
k
a^k_i
aik个体构成了存储
a
j
k
a^k_j
ajk的哈希表而已! 下面是两种写法,第二种是官方的写法,他是通过右移来操作的,将操作的数字全部移动到从右到左开始数起来的范围。不是很好理解,第一种方法是直接就是在原数组上面进行从前网红取得比较好理解~~
取出一个数的前N位,就是与1与,前N位1与
class Solution {
public:
int findMaximumXOR(vector<int>& nums) {
if (nums.size() < 2) return 0;
int maxNum = 0;
int flag = 0;
//本题关键在于,最终最大的异或结果一定是由所有数中,高位最先为1的数(也就是最大的数)参与得到的。
for (int i = 31; i >= 0; --i)
{//从最大值maxNum最高位到最低位开始确定</span>
set<int> hash;
flag |= (1 << i); //利用flag取出每一个x的前n-i位,n为31
for (int x : nums)
{
hash.insert(flag & x); //将取出来的每一种前n-i位存入到集合中,以备接下来比较。
}
int tmp = maxNum | (1 << i); //maxNum为上一次比较得出的前n-i位最大的数值,利用tmp尝试,若紧邻的下一位为1,是否有可能?
for (int x : hash)
{
//此处用到了x1^tmp=x2,则x1^x2=tmp.原因:x1^tmp=x2,两边同时异或x1,则tmp=x1^x2.
if (hash.find(x ^ tmp) != hash.end()) //如果存在,就说明有一个数的前n-i位与另一个数的前n-i位异或结果为该maxNum
{
maxNum = tmp;
break;
}
}
}
return maxNum;
}
};
class Solution {
private:
// 最高位的二进制位编号为 30
static constexpr int HIGH_BIT = 30;
public:
int findMaximumXOR(vector<int>& nums) {
int x = 0;
for (int k = HIGH_BIT; k >= 0; --k) {
unordered_set<int> seen;
// 将所有的 pre^k(a_j) 放入哈希表中
for (int num: nums) {
// 如果只想保留从最高位开始到第 k 个二进制位为止的部分
// 只需将其右移 k 位
seen.insert(num >> k);
}
// 目前 x 包含从最高位开始到第 k+1 个二进制位为止的部分
// 我们将 x 的第 k 个二进制位置为 1,即为 x = x*2+1
int x_next = x * 2 + 1;
bool found = false;
// 枚举 i
for (int num: nums) {
if (seen.count(x_next ^ (num >> k))) {
found = true;
break;
}
}
if (found) {
x = x_next;
}
else {
// 如果没有找到满足等式的 a_i 和 a_j,那么 x 的第 k 个二进制位只能为 0
// 即为 x = x*2
x = x_next - 1;
}
}
return x;
}
};