1707. 与数组中元素的最大异或值
思路:
这个题可以在421. 数组中两个数的最大异或值建立的字典树基础上完成。
这道题多了两个要求,第一个是nums里小于queries[i][1]得元素与queries[i][0]的元素相异或并取最大值,返回的结果数组中结果的存放顺序要按照queries的顺序。
那么我们考虑第一个要求,如果我们每次都遍历nums来找出小于queries[i][1]的元素的话时间复杂度就会增加可以推测时间复杂度为O(nm),那么我们就考虑对nums排序了,那么递增的比较只需要遍历一遍。那queries要不要处理呢?因为我们已经对nums排序了,如果遇到的queries[i][1]大于queries[i+1][1]的情况,那么我们指向nums的指针,当前指向的元素就可能大于queries[i+1][1],而queries[i+1][1]可能小于全部的nums元素,为了只遍历一遍,但这里可以多加一个判断如果queries[i+1][1]<nums[0]就加入-1,但运行的时候还是出错了,不知道啥情况。为了处理更为方便,我们还是对queries按照queries[i][1]的大小进行排序。这样queries就能按顺序的让nums元素递增的加入字典树,遍历的次数减少了。
但答案数组需要我们按照queries原来的顺序返回答案,那么我们该如何做呢,我们可以在每个queries元素加上当前的位置信息,最后加入结果数组的时候就按照这个信息赋值即可。
代码:
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; //返回得到的异或值
}
vector<int> maximizeXor(vector<int> &nums, vector<vector<int>> &queries) {
sort(nums.begin(),nums.end());
int lenq = queries.size();
for(int i=0;i<lenq;++i){ //加上位置信息 queries[i][2]=i
queries[i].push_back(i);
}
sort(queries.begin(),queries.end(),[](auto &x,auto &y){return x[1]<y[1];}); //按照queries[i][1]进行排序
vector<int> res(lenq);
trie *cur = root;
int idx=0,n=nums.size();
for(auto& q: queries){
int x=q[0],y=q[1],qid=q[2];
while(idx<n && nums[idx]<=y){ //只要满足nums小于queries[i][1]就加入进字典树里
addtotrie(nums[idx]);
++idx;
}
if(idx==0) res[qid] = -1;
else res[qid] = retxornum(x); //加入满足queries的nums数后就进行求最大的异或值
}
return res;
}
};
上述是离线+字典树,另外一种称为 在线+字典树。
在线比离线多一个信息,就是每个节点存放了以他的根节点的子树的最小的数min,这些数都是nums里面的,所以我们传入参数求最大异或时,再加一个条件就是当前的数小于该节点存放min的值我们就能走,这样就比离线的方法更快,因为不用排序了,在加数进树里的同时就明确了哪些节点的数会满足条件,所以性能会更优。
但不知道为啥用上面的代码直接改会有莫名的问题,找不出就直接看官方题解的学习了,顺便学了更为简洁的写法
代码:
class trie{
public:
const int HIGH_BIT = 30;
trie* children[2] = {}; //这里优化的特别好,通过数组的0,1位置代表了0和1
int min =INT_MAX;
void addtotrie(int num){
trie* cur = this;
cur->min = std::min(num,cur->min);
for(int i=HIGH_BIT-1;i>=0;i--){
int bit = (num>>i)&1; //如果是bit=0就用children[0]的位置,bit=1,就用[1]
if(!cur->children[bit]){
cur->children[bit] = new trie();
}
cur = cur->children[bit];
cur->min = std::min(cur->min,num); //对该节点存放以它为根节点的子树的最小值
}
}
int retmaxxor(int num,int limit){
trie* cur =this;
if(cur->min>limit) return -1;
int res = 0;
for(int i = HIGH_BIT-1;i>=0;--i){
int bit = (num >> i)&1;
//bit ^ 1是因为如果bit=0,我们应该找1,所以0 ^ 1=1.如果bit=1我们应该找0,1 ^ 0 =1
if(cur->children[bit^1]!=nullptr&&cur->children[bit^1]->min<=limit){ //两个条件,新条件就是如果该支路min值小于limit那么就满足条件能走下去
res |= 1<<i; //这里注意优先级就能理解了。1先右移i位在和res相异或,任何数与1或都能将该位置1。要注意这里是求的异或的结果,而不与num异或的值,所以最理想的情况就是异或结果全为1,所以直接用1左移相应的位即可,如果没有理想情况,则左移1不会填充所有的位,其中会有0.
bit ^= 1;
}
cur=cur->children[bit];
}
return res;
}
};
class Solution {
public:
vector<int> maximizeXor(vector<int>& nums, vector<vector<int>>& queries) {
trie* t =new trie();
int lenq = queries.size();
for(int num : nums){
t->addtotrie(num);
}
vector<int> res(lenq);
for(int i=0;i<lenq;++i){
res[i] = t->retmaxxor(queries[i][0],queries[i][1]);
}
return res;
}
};