【字典树】两个数的最大异或值?

148 阅读1分钟

题目描述

LeetCode上的421. 数组中两个数的最大异或值,难度:中等

给你一个整数数组 nums ,返回 nums[i] XOR nums[j] 的最大运算结果,其中 0 ≤ i ≤ j < n

示例1

输入:nums = [3,10,5,25,2,8]
输出:28
解释:最大运算结果是 5 XOR 25 = 28.

示例2

输入:nums = [14,70,53,83,49,91,36,80,92,51,66,70]
输出:127

提示

  • 1 <= nums.length <= 2 * 105
  • 0 <= nums[i] <= 231 - 1

字典树

首先来解释一下异或运算:「相同值异或结果为 0,不同值异或结果为 1」

我们确定nums[j](0<=j<nums.length),在[0,j]的范围找nums[i],是nums[i]^nums[j]最大,即两者的二进制的每一位尽量不相同。因此我们在遍历nums过程中,就将nums[j]的每一位从高位到低位存储到字典树中。然后从字典树中【字典树已经保存了下标属于[0,j]的nums元素了】找出与nums[j]异或最大的数【该数字一定是nums[0]~nums[j]中的一个】

那么字典树如何操作呢?

  • 从高位到低位遍历(利用右移并与1相与拿到当前位的数字【0/1】),并依次添加到字典树中。
  • 拿到与num异或的最大数字的方法getVal()
    1. 如果该位为1,我们应当向0的方向前进,若0方向不存在,只能向1的方向前进
    2. 如果该位为0,我们应当向0的方向前进,若1方向不存在,只能向0的方向前进
class Trie {
    Trie[] next = new Trie[2];
}
class Solution {
    Trie root = new Trie();
    public int findMaximumXOR(int[] nums) {
        int ans = 0;
        for(int num : nums) {
            add(num);
            ans = Math.max(ans, num ^ getVal(num));
        }
        return ans;
    }

    public void add(int num) {
        Trie node = root;
        // 固定4个字节,即32位,题目给出
        for(int i = 31; i >= 0; i--) {
            // 拿到当前i位置的元素0/1
            int curEle = (num >> i) & 1;
            if(node.next[curEle] == null) {
                node.next[curEle] = new Trie();
            }
            node = node.next[curEle];
        }
    }

    public int getVal(int num) {
        Trie node = root;
        int ans = 0;
        // 遍历的方向是从高位到低位,因此在计算ans的时候需要注意
        for(int i = 31; i >= 0; i--) {
            // 拿到当前i位置的元素
            int curEle = (num >> i) & 1;
            int reverseEle = 1 - curEle;
            // 下面根据当前位置的元素去字典树中判断是否有该元素,若没有就朝相反的方向前进
            if(curEle == 0) {
                // 最好朝着1的方向前进,若1方向为null,那就只能去0的方向了
                // 别忘了计算ans
                if(node.next[reverseEle] != null) {
                    node = node.next[reverseEle];
                    // +改为或‘|’速度会有提升
                    ans += (1 << i);
                } else {
                    node = node.next[curEle];
                }
            } else {
                // 当前元素位1,最好朝着0的方向前进,若0方向为null,那就只能朝着1的方向前进了
                if(node.next[reverseEle] != null) {
                    node = node.next[reverseEle];
                } else {
                    node = node.next[curEle];
                    ans += (1 << i);
                }
            }
        }
        // 将与num异或最大的数返回
        return ans;
    }
}