数组中两个数字的最大异或和位掩码

问题描述

LC:https://leetcode.com/problems/maximum-xor-of-two-numbers-in-an-array/

input

有人能解释一下当我们在 nums[i] 上应用 AND 运算符时发生了什么,它看起来像一个逐渐变小的掩码(从 2^31、2^30...开始)

public int findMaximumXOR(int[] nums) {
        
     int max = 0,mask = 0;
        
     for (int i = 31; i >= 0; i--){
            mask = mask | (1 << i);
            
            Set<Integer> set = new HashSet<>();
            
            for(int num : nums){
                int left = num & mask
                set.add(left);
            }
            int greed = max | (1 << i);
            
            for (int prefix : set){
                if (set.contains(greed ^ prefix)) {
                    max = greed;
                    break;
                }
            }
        }
        return max;
    }

我从评论中知道它应该保留左边的位而忽略右边的位,但我仍然不确定这段代码背后发生了什么。

解决方法

我发现将其视为递归算法最容易。

基本情况是所有数字都为零。那么最大异或显然为零。

递归地,我们修剪掉每个数字的最低有效位并解决子问题。现在,假设我们知道这个子问题的最大值,称它为 submax,答案是 submax<<1(submax<<1)|1,因为数字的最低有效位只影响最低有效位最大异或。我们可以通过测试任意两个数字是否有这个 XOR 来检查答案是否为 (submax<<1)|1

此代码更喜欢屏蔽低位而不是移位。循环从最高有效到最低有效打开 mask 的每个连续位,对应于当前递归调用中数字的增加长度。 num & mask 将当前忽略的低位清零。

,

给定输入[3,10,5,25,2,8],输出为28(由5^25给出):

   Num   Binary
    5     00101
 ^ 25     11001
   ------------
 = 28     11100

注意 xor 运算在两位不同时给出 1,否则为 0。因此,考虑到这一点,我们从左侧开始(最重要的位,在您的代码中由 i=31 给出)。

mask = mask | (1 << i);

我们在每次迭代中计算 mask。在第一次迭代中,掩码是 100...000,然后是 1100...000,以此类推。如果您不确定如何操作,请参阅 this answer

请注意,我们使用的是贪婪方法 - 当您处理第 i 位时,您只关心该位置的位;左边的那些之前已经处理过了,右边的将在随后的迭代中处理。考虑到这一点,在下一步中,我们创建一个哈希集 set 并将所有 num & mask 值放入其中。例如,当 i=30 时,我们的掩码是 110...000,因此在这一点上,我们只查看 i=305 中的第 25 位(左侧的 MSB ,在 i=31 处,已经得到处理,并且由于我们执行 &,右边的那些被忽略,因为我们的掩码在那里有所有 0)。

接下来,用:

int greed = max | (1 << i);

我们为当前迭代设置了我们的“期望”。理想情况下,我们希望在第 1 个位置有一个 i,因为我们想要找到最大的异或。使用这个集合,我们查看 set 中的所有元素,看看是否有任何元素符合我们的期望。如果找到,则相应地更新 max,否则我们的 max 保持不变。