LeetCode每日一题——1707. 与数组中元素的最大异或值(字典树)
Posted 小鹏说
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode每日一题——1707. 与数组中元素的最大异或值(字典树)相关的知识,希望对你有一定的参考价值。
前言
本文需要读者了解字典树的相关知识,建议读者先认真看懂这篇博客 字典树(前缀树),在充分理解该题做法后继续阅读。
题目描述
给你一个由非负整数组成的数组 nums 。另有一个查询数组 queries ,其中 queries[i] = [xi, mi] 。
第 i 个查询的答案是 xi 和任何 nums 数组中不超过 mi 的元素按位异或(XOR)得到的最大值。换句话说,答案是 max(nums[j] XOR xi) ,其中所有 j 均满足 nums[j] <= mi 。如果 nums 中的所有元素都大于 mi,最终答案就是 -1 。
返回一个整数数组 answer 作为查询的答案,其中 answer.length == queries.length 且 answer[i] 是第 i 个查询的答案。
示例 1:
输入:nums = [0,1,2,3,4], queries = [[3,1],[1,3],[5,6]]
输出:[3,3,7]
解释:
1) 0 和 1 是仅有的两个不超过 1 的整数。0 XOR 3 = 3 而 1 XOR 3 = 2 。二者中的更大值是 3 。
2) 1 XOR 2 = 3.
3) 5 XOR 2 = 7.
示例 2:
输入:nums = [5,2,4,6,6,3], queries = [[12,4],[8,1],[6,3]]
输出:[15,-1,5]
提示:
1 <= nums.length, queries.length <= 105
queries[i].length == 2
0 <= nums[j], xi, mi <= 109
解题思路
离线询问 + 字典树:即先将字典树构造好。
我们先将每个询问已经它对应的下标用map集合存储起来,因为我们后面需要对询问数组queries按后一个数进行升序排序,目的的方便后面构造字典树,排好序之后,就可以来实现Trie数类,Trie类中主要有两个方法,一个插入insert和一个查询
getValue,然后就可以开始循环扫描一遍询问,此时的询问的数是从小到大排序的,所以前面的询问会先将小的数先构造到数中,后面的询问只需要在这个基础之上继续添加后面的数即可。每当添加完所有小于等于当前询问的那个数后,就开始查询此时树中的所有元素与当前询问的前一个数(queries[i][0])的异或最大值,最后返回res数组即可。
这里需要注意的是:返回的结果要存在对应询问的下标位置。
启发
通过这题,我又学会了一个位运算的小技巧:就是给出一个十进制的想要确定它的第i位是0还是1,可以将这个数向右带符号移动 i 位,然后与1做按位与运算就可以确定了。
公式:val >> i & 1
AC代码
1 class Solution { 2 private Trie root = new Trie(); 3 4 public int[] maximizeXor(int[] nums, int[][] queries) { 5 int n = queries.length; 6 int[] res = new int[n]; 7 8 // 先用map集合将每个询问的下标都存起来 9 Map<int[], Integer> map = new HashMap<>(); 10 for (int i = 0; i < n; i++) { 11 map.put(queries[i], i); 12 } 13 14 // 排序 15 Arrays.sort(nums); 16 // 按后一个数进行升序排序,这里是为了方便建树 17 Arrays.sort(queries, new Comparator<int[]>() { 18 @Override 19 public int compare(int[] o1, int[] o2) { 20 return o1[1] - o2[1]; 21 } 22 }); 23 24 int index = 0; 25 // 扫描询问 26 for (int i = 0; i < n; i++) { 27 int x = queries[i][0]; 28 int m = queries[i][1]; 29 // 循环添加,构建trie树 30 while (index < nums.length && nums[index] <= m) { 31 root.insert(nums[index]); 32 index++; 33 } 34 // 添加完此次的元素之后,开始查询 35 // 这里的if只要树中添加过一个元素之后,就永远都进不去了 36 if (index == 0) { 37 // 如果此次没有一个数小于等于m,那就将此时的异或最大值设置为-1 38 res[map.get(queries[i])] = -1; 39 } else { 40 res[map.get(queries[i])] = root.getValue(x); 41 } 42 } 43 44 return res; 45 } 46 47 48 } 49 50 /* 51 Trie树 52 */ 53 class Trie { 54 public static final int L = 31; 55 Trie[] children = new Trie[2]; 56 57 public void insert(int val) { 58 Trie p = this; 59 for (int i = L; i >= 0; i--) { 60 int bit = (val >> i) & 1; 61 if (p.children[bit] == null) { 62 // 没有孩子,new一个 63 p.children[bit] = new Trie(); 64 } 65 // 如果有,直接指向该孩子 66 p = p.children[bit]; 67 } 68 } 69 70 /*** 71 * 返回异或的最大值 72 * @param val 73 * @return 74 */ 75 public int getValue(int val) { 76 int ans = 0; 77 Trie p = this; 78 for (int i = L; i >= 0; i--) { 79 // 将第i位移动到第一位(0000000000000000000000000....001,这里的1就是第一位,最前面的0就是第三十二位) 80 int bit = (val >> i) & 1; 81 // 先考虑取反,如果没有,那就只能取同的,即该位最后只能位0(异或:同为0,异为1) 82 if (p.children[bit ^ 1] != null) { 83 ans += (int) Math.pow(2, i); 84 // 更新方向 85 bit ^= 1; 86 } 87 // 向后移动 88 p = p.children[bit]; 89 } 90 91 return ans; 92 } 93 }
以上是关于LeetCode每日一题——1707. 与数组中元素的最大异或值(字典树)的主要内容,如果未能解决你的问题,请参考以下文章