421. 数组中两个数的最大异或值

思路:
暴力很明显,两重for循环即可完成。
如何优化成O(n),自己想了想,两数异或的结果是在(X-Y,X+Y)之间的,然后我就直接for找到一个最大的数,然后用其他的数与他异或取最大的,还是错了,如2,10,8,2 ^ 8 =10,10 ^2 =8。
然后就看了题解,用了字典树的结构。
字典树简单来说就是把一整个数据顺序分成子数据,通过前部子数据的信息不断找到后面的数据。对于一个字,如果采用拼音,例如王,那么我们在拼音中寻找就是从w->a->n->g,这样寻找然后在叶子节点就存放着王这个字,那么对于一串2进制数也是一样的,例如寻找0110,那么就是从0->1->1->0寻找。
那么我们如何将字典树运用在这呢?
我们将所有nums里的元素转化成二进制数,然后存放在字典树,然后再将nums里的数在树里寻找最大的异或值。这样我们的时间复杂度就优化成了O(nlogc),这里的c就是字典树的深度,当字典树的深度为常数,那么就是O(n)了。n就是两次遍历nums的时间消耗。
那么我们主要问题有两个,如何将nums的元素转化成二进制数并存放在树里,如何将nums的数在树里寻找最大的异或值。
然后需要注意的是,如果要获取最大值,我们需要从高位往低位寻找异或值,这和我们的寻找方法有关。
我们寻找最大的异或值就是要寻找该二进制位不同的数,特别是高位不同的,异或后的结果越大。所以传入的数的某一位是0,那么我们应该找1来异或,所以我们就会进入到1,对于1来说又会有1,0,所以有可能我们高位选了1,后面的位因为没有1而导致全部走了0,但高位选了1仍然是最大的。如果从低位开始找,可能会低位很多1,但错过了高位的1,他就可能不是最大的。故此我们要从高位往低位匹配。
将十进制数转为成二进制数,我们可以考虑如何获取十进制数的每一位,又因为我们是从高位找,所以我们将传入的数右移30位,29位...并于1相与,那么我们就可以获得右移后的最低位,再存入树里,那么一个十进制数就转化为二进制数存入树里了。
如何在树里寻找最大的异或值,那么我们就根据以下规则:
当前位为0,我们就要寻找1,因为0 ^ 1=1,如果没有1,就只能走0
当前位为1,我们就要寻找0,因为1 ^ 0=1,如果唯有0,就只能走1
按此寻找我们就能获得两个数,与传入的数异或得到最大的数,和异或的结果。这里我们还需要用一个变量nowxor来存储异或的结果
方法是:
遇0找到1,nowxor = nowxor2+1,这样就相当于该位异或为1,那么我们原来找到的二进制数就升了一位并且最低位是1就是 乘2+1了,例如原来是10,找到1,就是101,化成十进制是不是2+1?同理遇1找到0.
遇0找到0,nowxor = nowxor*2,这样就是该位相同的数异或为0,那么我们原来找得到二进制数升了一位,但最低位就为0。例如 01,找到0,010,化成十进制不就只是乘了2吗。同理遇1找到1.
(这里多说一下,如何找到最该数异或得到最大的数)就是我们每次找的0和1组成的数就是了,例如0110,我们最理想的情况找的就是1001。符合遇0找1,遇1找0.

代码如下:

class Solution {
private:
//我们不需要给树的节点赋值,只要创建就说明有1或者0
struct trie{
    trie* left = nullptr;  //左用来代表0
    trie* right = nullptr; //右用来代表1
    trie(){}
};

trie* root = new trie();
static const int HIGH_BIT = 30;

public:
    void addtotrie(int num){  //用来将十进制数转为二进制数存入树里
        trie* cur = root;
        for(int i = HIGH_BIT;i>=0;--i){
            int bit = (num >> i)&1; //从高位不断获取HIGH_BIT-i位的二进制数
            if(bit == 0){     //如果这个位二进制数为0
                if(!cur->left){  //没0就创建0,并移动到0处
                    cur->left = new trie();
                }
                cur = cur->left;
            }
            else if(bit == 1){ //同上
                if(!cur->right){
                    cur->right = new trie();
                }
                 cur = cur->right;
            }
        }
    }

    int retxornum(int num){  //用来找到num异或的最大值
        trie* cur = root;
        int nowxor=0;
        for(int i= HIGH_BIT;i>=0;--i){
            int bit = (num >> i)&1;
            if(bit == 0){
                if(cur->right){   //遇0找1
                    cur = cur->right;
                    nowxor = nowxor*2+1;
                }
                else {  //没有1就找0
                    cur = cur ->left;
                    nowxor = nowxor*2;
                }
            }
            else if(bit == 1){  //遇1找0
                if(cur->left){
                    cur = cur->left;
                    nowxor = nowxor*2+1;
                }
                else{ //没有0就找1
                    cur = cur->right;
                    nowxor = nowxor*2;
                }
            }
        }
        return nowxor; //返回得到的异或值
    }

    int findMaximumXOR(vector<int>& nums) {
       int n=nums.size();
       int res=0;
       for(int i=0;i<n;++i){
           addtotrie(nums[i]);  //将所有nums的元素加入树中
       }
       for(int i=0;i<n;++i){
           res= max(res,retxornum(nums[i]));  //再将nums中的元素寻找其最大的异或值,并取最大的。
       }
       return res;
    }
};
上一篇:Trie 字典树


下一篇:leetcode 字典树 每日一题 2021.5.16 208. 实现 Trie (前缀树)(字典树)