leetcode 421. Maximum XOR of Two Numbers in an Array

来源:互联网 发布:文言文辞职信走红网络 编辑:程序博客网 时间:2024/05/23 02:04

本题也是一个经典问题,流行的解法有两种,其中一种使用了字典树(Trie Tree)的数据结构,这种数据结构有很多引申的用途,一些经典面试题中也会用到,比如腾讯的QQ号查找算法实现,归属地算法等,都是字典树的典型应用(关于QQ号的查询有人文章里提到使用二叉查找的方式,个人觉得并没有字典树的方式快),因此会重点说明字典树的AC解。

题目描述

Given a non-empty array of numbers, a0, a1, a2, … , an-1, where 0 ≤ ai < 231.

Find the maximum result of ai XOR aj, where 0 ≤ i, j < n.

Could you do this in O(n) runtime?

解题思路

题目说的很清楚,找出一个非空数组的任意两个数的异或最大值,要求O(n)时间复杂度。
关于字典树:字典树的基本结构在网上有很多介绍,这里不做解释,字典树的基本思想就是空间换时间。
关于异或:这类型的题目遇到很多了,基本运算规则就是位运算了,对于一个给定的数组,数组的每个数都是确定的值,因此可以很容易构建出关于这个数组的二进制表达数的字典树,字典树的根节点不保存数据,因此从树的第一层到最后一层,分别对应二进制的最高位到最低位,这样一颗对应于原数组的字典树就创建出来了。
查询过程:对于数组中的每个数,都执行一次查询过程,找到的最大值即为所求。

代码实现(C++11):
1.字典树结构:

struct Trie;struct Trie {    Trie* next[2];    bool isExist = false;    int num = 0;    Trie() {        next[0] = nullptr;        next[1] = nullptr;        isExist = false;        num = 0;    }};

2.建树和搜索:

        int findMaximumXOR(vector<int>& nums) {            Trie* root;            root = new Trie();            // so we create the trie tree            for(auto &val:nums) {                 auto temp = root;                for(int i = 31;i>=0;--i) {                    int t = (val>>i)&1;                    if(temp->next[t]==nullptr) {                        temp->next[t] = new Trie();                    }                    temp = temp->next[t];                }                temp->isExist = true;          }          //we search from the top root          int maxVal = -1;          for(auto &val:nums) {              int tempMax = 0;               auto temp = root;              for(int i = 31; i>=0;--i) {                  int t = (val>>i)&1;                  if(temp->next[t^1]!=nullptr) {                      tempMax+=(1<<i);                      temp = temp->next[t^1];                  }else {                      temp = temp->next[t&1];                  }              }              maxVal = max(maxVal,tempMax);          }           return maxVal;    }

第二种解法

让我们再次回到题目描述,我们要求数组中任意两个数异或的最大值,不考虑字典树的情况下,这确实是个令人头疼的问题,但是我们可以换种角度考虑问题,既然我们知道异或是按位进行,那么不管是什么样的数异或,他们的最大值不会超过2的31次方,也就是说即使我们从最高位开始计算,也只需要循环32次而已,这就给了我们一种思路,从最高位开始,我们假设有两个数的异或在该位上为1,而对于异或操作,有公式
1. a1^a2 = max
2. a1^a2^a2 = a1,故有
3. a1 = max^a2
也就是说,如果存在这样的max,我们可以通过公式3得到一个存在于数组中的数,如果不存在,则该数不存在于数组中,max中该位自然为0,在代码实现中,我们使用了位操作运算中经常使用的mask来表示已经操作过的位(数组中的所有数),因为可能存在的不同数在某一位上的重复位,我们使用set数据结构存储所有已访问过当前位的数,并通过公式3来做判断。具体的代码如下(C++ 11):

int solution(vector<int>& nums) {int mask = 0,max = 0;set<int> mySet;for(int i = 31;i>=0;--i){    //mask first    mask = mask|1<<i;    for(auto&val:nums) {        mySet.insert(val&mask);    }    auto temp = max|1<<i;    for(auto &val:mySet) {        if(mySet.find(val^temp)!=mySet.end()){            max = temp;            break;        }    }    mySet.clear();}return max;}

两种方法各有优势,但大体思路是一致的,就代码量来看,方法2的代码更少且更容易理解,但是本题引申出的字典树是很重要的数据结构,需要掌握。

0 0