[每日一题]1803.统计异或值在范围内的数对有多少

1803. 统计异或值在范围内的数对有多少 给你一个整数数组 nums (下标 从 0 开始 计数)以及两个整数:low 和 high ,请返回 漂亮数对 的数目。漂亮数对 是一个形如 (i, j) 的数对,其中 0 <= i < j < nums.length 且 low <= (nums[i] XOR nums[j]) <= high 。

Solution

import java.util.Arrays;
class Solution {
    public int countPairs(int[] nums, int low, int high) {
        int count = 0;
        Arrays.sort(nums);
        for (int i = 0; i < nums.length - 1; i++) {
            for (int j = i + 1; j < nums.length; j++) {
                if(i < j){
                    int res = nums[i] ^ nums[j];
                    if (low <= res && res <= high) {
                        count++;
                    } 
                }
            }
        }
        return count;
    }
}

暴力破解笑嘻嘻,一看超过百分七。看到困难题唯唯诺诺。。。

正确的方式是采用字典树,不知道这个具体的操作,贴一位大佬的答案:

class Solution {
    // 字典树思路: 要获得异或值在区间[low, high]的所有对数;
    // 可以把数值按照顺序,以二进制的形式从高位到低位插入到字典树中,并且记录每个二进制前缀出现的次数;
    // 前缀树中可以获得 <= x的 所有数值的数量,因此可以使用 f(high) - f(low - 1); 获得该区间的值的数量
    // 前置知识:两个数的比较,就要从高到低比较每个二进制位;直到一方为1 一方为0,为1的一方更大,如果到达末尾都一模一样,说明两个数是相同的。
    
    class Trie { // 字典树结点
        Trie[] child; 
        int sum; 
        public Trie () { // 初始化Trie结点
            child = new Trie[2]; // 分别记录二进制位的0和1;
            sum = 0; // 记录当前二进制前缀出现了多少次
        }
    }

    private Trie root = null; // 字典树的根;
    private static final int DEPTH = 14; // 字典树最大的深度

    public int countPairs(int[] nums, int low, int high) {
        int ans = 0;
        root = new Trie(); // 给字典树初始化
        // 依次插入每一个数值; 后面插入的数值,就可以和前面的数值比较;
        for (int i = 1; i < nums.length; ++i) {
            insert(nums[i - 1]);
            ans += get(nums[i], high) - get(nums[i], low - 1);
        }
        return ans;
    }

    // 把数字k的二进制形式,按高到低位依次插入到字典树中;
    public void insert(int k) {
        Trie node = this.root; // 获取字典树
        for (int i = DEPTH; i >= 0; --i) {
            int bit = (k >> i) & 1; // 取数字k的第i个二进制位;
            if (node.child[bit] == null) { 
            // 如果这个位置没有值,就创建一个结点,表示bit这个位出现了
                node.child[bit] = new Trie(); 
            }
            node = node.child[bit]; // 进入到字典树下一层;
            node.sum++; // 记录当前前缀出现的次数。
        }
    }

    // 此时前缀树中已插入了一些数值,此get函数可获得满足n 与这些数值的异或 <= k的对数;
    // 字典树中的child数组,如果存在值,就代表对应二进制存在; 
    // 例如child[0]存在,表示这一层的二进制0是出现过的
    // 注意:sum 保存的是之前出现的二进制的前缀们;
    public int get(int n, int k) {
        int sum = 0; // 记录出现了多少对 异或值 <= k;
        Trie node = this.root; // 获得字典树
        for (int i = DEPTH; i >= 0; --i) { // 从高位往地位取二进制位。
            int bitn = (n >> i) & 1; // 取数值n的第i个二进制位;
            int bitk = (k >> i) & 1; // 取数值k的第i个二进制位;
            if (bitk == 1) { 
                // 如果bitk == 1,那么 在字典树中的二进制位只有与bitn相同,才能使二进制的异或等于0
                // 那就看bitn相同的二进制位是否出现过,如果出现过,说明和bitn的异或等于0,小于bitk的1;
                // 记录即可。
                if (node.child[bitn] != null) {
                    sum += node.child[bitn].sum;
                } 
                // bitn ^ 1,表示与bitn相反的二进制位, 如果与bitn相反的不存在,那就意味着字典树
                // 中没有二进制位能够与bitn异或使得结果等于1,此时n与字典树中的值的异或都小于k;
                if (node.child[bitn ^ 1] == null) {
                    return sum;
                }
                node = node.child[bitn ^ 1]; // 否则的话,就继续进入下一层判断;
            } else {
                // 如果字典树中该层的二进制和bitn相同的二进制不存在,就说明不存在异或 == 0,即
                // 不存在 == bitk的情况,那么就可以直接返回了;
                if (node.child[bitn] == null) {                        
                    return sum;
                }
                // 如果存在,继续进入下一层比较。
                node = node.child[bitn];
            }
        }
        sum += node.sum; 
        // 如果字典树中的数值与n的异或,存在等于k的情况,就会来到字典树的末尾;
        return sum;
    }
}