Title Description
This is "1707. The maximum XOR value of the element in the array" on LeetCode. The difficulty is "difficult".
Tag: "persistent dictionary tree", "binary"
Give you an array of nonnegative integers
nums. There is another query array
queries, where
queries[i] = [xi, mi].
The first
iThe answer to a query is
x_iAnd any
numsNo more than
m_iThe maximum value obtained by bitwise XOR (XOR) of the element.
In other words, the answer is max (Num [J] \ {XOR} x_i), where all
jAll meet
nums[j] <= m_i. If
numsAll elements in are greater than
m_i, the final answer is
-1.
Returns an array of integers
answerAs the answer to the query, where
answer.length == queries.lengthAnd
answer[i]Yes
iAnswer to a query.
Example 1:
Input: nums = [0,1,2,3,4], queries = [[3,1],[1,3],[5,6]] Output:[3,3,7] Explanation: 1) 0 And 1 are the only two integers that do not exceed 1. 0 XOR 3 = 3 And 1 XOR 3 = 2 . The greater of the two is 3. 2) 1 XOR 2 = 3. 3) 5 XOR 2 = 7.
Example 2:
Input: nums = [5,2,4,6,6,3], queries = [[12,4],[8,1],[6,3]] Output:[15,-1,5]
Tips:
- 1 <= nums.length, queries.length <=
- queries[i].length == 2
- 0 <= nums[j], xi, mi <=
Basic analysis
Please make sure that you have finished before doing this question 421. Maximum XOR value of two numbers in the array.
Given all the questions in advance, we can use the offline idea (adjust the answer order of the questions) to solve them.
There are two offline ways to solve this problem.
Ordinary Trie
The basic idea of the first method is: do not put all the numbers at one time, but put the numbers that need to participate in the screening every time
Trie, and then 421. Maximum XOR value of two numbers in the array Similar greedy search logic.
Specifically, we can handle it according to the following logic:
- Sort nums from small to large, and sort the second dimension of queries from small to large (save the original subscript mapping relationship before sorting).
- Process all queries in sort order [i]:
- Before answering each query, store a value less than or equal to queries[i][1]
. Since we have sorted nums in advance, this process only needs to maintain a pointer that moves to the right on nums.
- Then, using the greedy idea, query the maximum value that can be found in each query [i] [0], and calculate the XOR and (this process is similar to 421. Maximum XOR value of two numbers in the array Consistent).
- Find the subscript of the current query in the original query sequence and save the answer.
code:
class Solution { static int N = (int)1e5 * 32; static int[][] trie = new int[N][2]; static int idx = 0; public Solution() { for (int i = 0; i <= idx; i++) { Arrays.fill(trie[i], 0); } idx = 0; } void add(int x) { int p = 0; for (int i = 31; i >= 0; i--) { int u = (x >> i) & 1; if (trie[p][u] == 0) trie[p][u] = ++idx; p = trie[p][u]; } } int getVal(int x) { int ans = 0; int p = 0; for (int i = 31; i >= 0; i--) { int a = (x >> i) & 1, b = 1 - a; if (trie[p][b] != 0) { p = trie[p][b]; ans = ans | (b << i); } else { p = trie[p][a]; ans = ans | (a << i); } } return ans ^ x; } public int[] maximizeXor(int[] nums, int[][] qs) { int m = nums.length, n = qs.length; // Use a hash table to save the original order Map<int[], Integer> map = new HashMap<>(); for (int i = 0; i < n; i++) map.put(qs[i], i); // Sort nums and queries[x][1] from small to large Arrays.sort(nums); Arrays.sort(qs, (a, b)->a[1]-b[1]); int[] ans = new int[n]; int loc = 0; // Record which positions in num have been put into Trie for (int[] q : qs) { int x = q[0], limit = q[1]; // Store the number less than or equal to limit in Trie while (loc < m && nums[loc] <= limit) add(nums[loc++]); if (loc == 0) { ans[map.get(q)] = -1; } else { ans[map.get(q)] = getVal(x); } } return ans; } }
- Time complexity: let the length of nums be m and the length of qs be n. The sorting complexity is
and
O(n\log{n}); Insert all numbers
TrieAnd from
TrieThe complexity of finding in is
O(Len),
Lenby
32. The overall complexity is
O(m\log{m} + n\log{n} + (m + n) * Len)=
O(m * \max(\log{m}, Len) + n * \max(\log{n}, Len)).
- Space complexity:
. among
CIs constant, fixed to
1e5 * 32 * 2.
Count trie & two points
Another way to "increase the difficulty" is to turn the whole process over: deposit all the information at one time
TrieAnd then change the number of no longer participating from
TrieRemove from. Compared with solution one, this requires us to
TrieAdd a "delete / count" function, and need to implement dichotomy to find the upper bound subscript of the removed element.
Specifically, we can handle it according to the following logic:
- Sort nums from large to small, and sort the second dimension of queries from large to small (save the original subscript mapping of the query before sorting).
- Process all queries in sort order [i]:
- Before answering each query, find the first subscript in nums that satisfies "less than or equal to queries[i][1] through" bisection ", and then change the number before the subscript from
Remove from. Similarly, in this process, we need to use a pointer to record the subscript position of the last deletion to avoid repeated deletion.
- Then use the greedy idea to query the maximum value that each query [i] [0] can find. Note that this is to judge whether the current node has been counted. If not, it returns
.
- Find the subscript of the current query in the original query sequence and save the answer.
code:
class Solution { static int N = (int)1e5 * 32; static int[][] trie = new int[N][2]; static int[] cnt = new int[N]; static int idx = 0; public Solution() { for (int i = 0; i <= idx; i++) { Arrays.fill(trie[i], 0); cnt[i] = 0; } idx = 0; } // Deposit (v = 1) / delete (v = -1) a number x to Trie void add(int x, int v) { int p = 0; for (int i = 31; i >= 0; i--) { int u = (x >> i) & 1; if (trie[p][u] == 0) trie[p][u] = ++idx; p = trie[p][u]; cnt[p] += v; } } int getVal(int x) { int ans = 0; int p = 0; for (int i = 31; i >= 0; i--) { int a = (x >> i) & 1, b = 1 - a; if (cnt[trie[p][b]] != 0) { p = trie[p][b]; ans = ans | (b << i); } else if (cnt[trie[p][a]] != 0) { p = trie[p][a]; ans = ans | (a << i); } else { return -1; } } return ans ^ x; } public int[] maximizeXor(int[] nums, int[][] qs) { int n = qs.length; // Use a hash table to save the original order Map<int[], Integer> map = new HashMap<>(); for (int i = 0; i < n; i++) map.put(qs[i], i); // In descending order sort(nums); Arrays.sort(qs, (a, b)->b[1]-a[1]); // Store all data in Trie for (int i : nums) add(i, 1); int[] ans = new int[n]; int left = -1; // In nums, the values subscript "less than or equal to" left have been removed from Trie for (int[] q : qs) { int x = q[0], limit = q[1]; // Binary finds the right boundary of the element to be deleted and removes all values before its right boundary from Trie. int right = getRight(nums, limit); for (int i = left + 1; i < right; i++) add(nums[i], -1); left = right - 1; ans[map.get(q)] = getVal(x); } return ans; } // Find the right boundary to be deleted int getRight(int[] nums, int limit) { int l = 0, r = nums.length - 1; while (l < r) { int mid = l + r >> 1; if (nums[mid] <= limit) { r = mid; } else { l = mid + 1; } } return nums[r] <= limit ? r : r + 1; } // Sort nums in descending order (Java does not have Api to directly support the reverse order of basic type int, which can be ignored in other languages) void sort(int[] nums) { Arrays.sort(nums); int l = 0, r = nums.length - 1; while (l < r) { int c = nums[r]; nums[r--] = nums[l]; nums[l++] = c; } } }
- Time complexity: let the length of nums be m and the length of qs be n, constant
. The sorting complexity is
O(m\log{m})and
O(n\log{n}); Insert all numbers
TrieThe complexity of is
O(m * Len); Each query needs to go through "dichotomy" to find the boundary, and the complexity is
O(n\log{m}); In the worst case, all numbers will start from
TrieIs marked for deletion with complexity of
O(m * Len). The overall complexity is
O(m\log{m} + n\log{n} + n\log{m} + mLen)=
O(m * \max(\log{m}, Len) + n * \max(\log{m}, \log{n})).
- Space complexity:
. among
CIs constant, fixed to
1e5 * 32 * 3.
explain
Both of these methods adopt "array implementation", and because of the large data range, static is used to optimize the creation of large arrays. The specific "optimization reason" and "class implementation Trie method" can be found in the problem solution 208. Implement Trie (prefix tree) Check. I won't repeat it here.
last
This is No.1707 of our "brush through LeetCode" series. The series began on January 1, 2021. As of the starting date, there are 1916 questions on LeetCode, some of which are locked questions. We will brush all the questions without locks first.
In this series of articles, in addition to explaining the problem-solving ideas, we will also give the most concise code as far as possible. If the general explanation is involved, the corresponding code template will also be.
In order to facilitate students to debug and submit code on the computer, I have established a relevant warehouse: https://github.com/SharingSource/LogicStack-LeetCode .
In the warehouse address, you can see the problem solution link of the series of articles, the corresponding code of the series of articles, the original problem link of LeetCode and other preferred problem solutions.