携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第19天,点击查看活动详情
每日刷题 2022.08.17
- leetcode原题链接:leetcode.cn/problems/ms…
- 难度:中等
- 方法: 字典树
题目
- 给定一个整数数组
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 = [0]
输出: 0
- 示例3
输入: nums = [2,4]
输出: 6
- 示例4
输入: nums = [8,10,2]
输出: 10
- 示例5
输入: nums = [14,70,53,83,49,91,36,80,92,51,66,70]
输出: 127
提示
1 <= nums.length <= 2 * 10^50 <= nums[i] <= 2^31 - 1
解题思路
- 根据题意可知:需要找个整个
nums数组中的两个数异或的最大值,且下标需要满足0<=i<=j<n. - 查看数据的范围,数组长度最大是:
2 * 10 ^ 5,如果暴力求解的话,双层for循环对于每一个数都需要遍历一遍数组中的另一个数,那么时间复杂度就是:4 * 10 ^ 10,显然会超时。因此暴力模拟的方式被排除了。那么想想如何优化呢?
字典树
- 可以将
nums中的每一个数拆分成31位,将每一个按位存储在字典树中。因为需要获得两个数异或的最大值,异或:相异为1,相同为0。由此可见最高位的1应该被尽可能的保留,因为其能使最终的结果更大。那么就将nums数组中的每一个数值,从高位到低位依次存储在字典树tree中。 - 接着在遍历
nums数组中的每个数值(记为:cur),将cur的转换成二进制,且需要从高位往低位进行遍历,依次去字典树中查找与cur当前的二进制位不同的数值。 - 例如(字典树中选择的时候尽可能的满足当前位能取到的最大值):
cur当前位为1,那么字典树中对应的位需要找0;如果字典树不存在0,那么就只能找和自己相同的1 - 同样的如果
cur当前位为0,那么字典树中对应的位需要找1;如果字典树中不存在1,那么就只能找和自己相同的0 - 还存在一个问题?如何记录当前的两个数的异或值呢?
- 在遍历
cur的每一位在字典树中查找值时,初始化一个变量res,每次在字典树中找到与之匹配的位时,就将当前的res左移1位,加上当前的位数值即可。 - 最终将数组中的每一个数值计算出的自身最大异或值,取一个最大的返回即可。
- 在遍历
最近学习的常用的位运算
- 使用数值将一个字符串中出现过的所有字符都记录下来。
// 巧妙的使用一个cur数值记录下字符串中出现过的所有字符
let bit = one.charCodeAt() -'a'.charCodeAt();
// 将1左移bit位,然后与cur或,就可以使cur记录下来one代表的字符
// 或运算:只要有1个为真,即为真,两个都为0,才为0;
cur |= (1 << bit);
-
如下图所示:得到
g表示的二进制后,与0原先的值相或,就可以将字符串中所有出现的字符统计到一个二进制数组中。(只有出现的字符,才会为1) -
将十进制的数值获取其每一个二进制位
// cur表示一个十进制数值,将cur移动几位(30位),依次获取每一位的值
// &1 一定不能丢,这是获取二进制下的最后一位的值
cur = (cur >> i) & 1;
cur = cur >> 1; // 整除,就不需要写成cur = Math.floor(cur / 2);
AC代码
/**
* @param {number[]} nums
* @return {number}
*/
var findMaximumXOR = function(nums) {
// 因为是存在顺序的,所以要按照顺序来插入字典树中
let n = nums.length, maxx = 0, root = {};
for(let i = 0; i < n; i++) {
maxx = Math.max(maxx, check(nums[i]));
addTrie(nums[i]);
}
return maxx;
// 将当前的节点插入到字典树中
function addTrie(cur) {
let node = root;
for(let i = 30; i >= 0; i--) {
let bit = (cur >> i) & 1;
if(!node[bit]) node[bit] = {};
node = node[bit];
}
}
// 判断当前节点的最大异或和
function check(cur) {
// 如果字典数为空
let node = root, res = 0;
if(Object.keys(node).length === 0) return res;
// if(root === {}) return res;
for(let i = 30; i >= 0; i--) {
// console.log(cur >> i, (cur >> i) & 1)
// &1表示取最后一位
let bit = (cur >> i) & 1;
if(node[1] && (bit ^ 1 === 1)) {
// 相异为1,选择另一条路走
// bit 为 0
res = (res << 1) + 1;
node = node[1];
} else if(node[0] && (bit ^ 0 === 1)) {
// 相同为0,bit = 1
res = (res << 1) + 1;
node = node[0];
} else {
// 其他的情况,例如:bit为1,但是不存在0或者bit为0,但是不存在1
// 就只能选择和自己相同的
res = res << 1;
node = node[bit];
}
}
return res;
}
};