🧠
Sorting + Offline Queries + Trie Insertion
💡 This approach introduces sorting and a bitwise trie to efficiently answer queries offline, which is a common technique to handle constraints and optimize lookups.
Intuition
Sort nums and queries by m_i, then incrementally insert nums elements into a trie as m_i increases, allowing quick max XOR queries for each x_i.
Algorithm
- Sort nums in ascending order.
- Sort queries by their m_i value, keeping track of original indices.
- Initialize an empty bitwise trie.
- Iterate over queries in ascending order of m_i, inserting all nums elements ≤ m_i into the trie.
- For each query, find the maximum XOR of x_i with elements in the trie; if none, return -1.
- Restore answers to original query order.
💡 Sorting queries and nums allows incremental trie building, avoiding repeated work and enabling efficient max XOR lookups.
class TrieNode:
def __init__(self):
self.children = [None, None]
class Trie:
def __init__(self):
self.root = TrieNode()
def insert(self, num):
node = self.root
for i in range(31, -1, -1):
bit = (num >> i) & 1
if not node.children[bit]:
node.children[bit] = TrieNode()
node = node.children[bit]
def maxXor(self, num):
node = self.root
if not node.children[0] and not node.children[1]:
return -1
max_xor = 0
for i in range(31, -1, -1):
bit = (num >> i) & 1
toggled_bit = 1 - bit
if node.children[toggled_bit]:
max_xor |= (1 << i)
node = node.children[toggled_bit]
else:
node = node.children[bit]
return max_xor
def maximizeXor(nums, queries):
nums.sort()
queries = sorted([(x, m, i) for i, (x, m) in enumerate(queries)], key=lambda x: x[1])
trie = Trie()
res = [-1] * len(queries)
idx = 0
n = len(nums)
for x, m, i in queries:
while idx < n and nums[idx] <= m:
trie.insert(nums[idx])
idx += 1
res[i] = trie.maxXor(x)
return res
# Example usage
if __name__ == '__main__':
nums = [0,1,2,3,4]
queries = [[3,1],[1,3],[5,6]]
print(maximizeXor(nums, queries))
Line Notes
nums.sort()Sort nums to insert elements incrementally based on m_i
queries = sorted(..., key=lambda x: x[1])Sort queries by m_i to process in order
while idx < n and nums[idx] <= m:Insert all nums elements ≤ current query's m_i
res[i] = trie.maxXor(x)Query trie for max XOR with x_i among inserted nums
import java.util.*;
class TrieNode {
TrieNode[] children = new TrieNode[2];
}
class Trie {
TrieNode root = new TrieNode();
void insert(int num) {
TrieNode node = root;
for (int i = 31; i >= 0; i--) {
int bit = (num >> i) & 1;
if (node.children[bit] == null) node.children[bit] = new TrieNode();
node = node.children[bit];
}
}
int maxXor(int num) {
TrieNode node = root;
if (node.children[0] == null && node.children[1] == null) return -1;
int maxXor = 0;
for (int i = 31; i >= 0; i--) {
int bit = (num >> i) & 1;
int toggledBit = 1 - bit;
if (node.children[toggledBit] != null) {
maxXor |= (1 << i);
node = node.children[toggledBit];
} else {
node = node.children[bit];
}
}
return maxXor;
}
}
public class Solution {
public int[] maximizeXor(int[] nums, int[][] queries) {
Arrays.sort(nums);
int n = nums.length, q = queries.length;
int[][] qWithIndex = new int[q][3];
for (int i = 0; i < q; i++) {
qWithIndex[i][0] = queries[i][0];
qWithIndex[i][1] = queries[i][1];
qWithIndex[i][2] = i;
}
Arrays.sort(qWithIndex, Comparator.comparingInt(a -> a[1]));
Trie trie = new Trie();
int[] res = new int[q];
Arrays.fill(res, -1);
int idx = 0;
for (int[] query : qWithIndex) {
int x = query[0], m = query[1], i = query[2];
while (idx < n && nums[idx] <= m) {
trie.insert(nums[idx]);
idx++;
}
res[i] = trie.maxXor(x);
}
return res;
}
public static void main(String[] args) {
Solution sol = new Solution();
int[] nums = {0,1,2,3,4};
int[][] queries = {{3,1},{1,3},{5,6}};
System.out.println(Arrays.toString(sol.maximizeXor(nums, queries)));
}
}
Line Notes
Arrays.sort(nums);Sort nums to insert incrementally based on m_i
Arrays.sort(qWithIndex, Comparator.comparingInt(a -> a[1]));Sort queries by m_i for offline processing
while (idx < n && nums[idx] <= m) {Insert all nums elements ≤ current query's m_i
res[i] = trie.maxXor(x);Query trie for max XOR with x_i among inserted nums
#include <bits/stdc++.h>
using namespace std;
struct TrieNode {
TrieNode* children[2] = {nullptr, nullptr};
};
class Trie {
public:
TrieNode* root;
Trie() { root = new TrieNode(); }
void insert(int num) {
TrieNode* node = root;
for (int i = 31; i >= 0; i--) {
int bit = (num >> i) & 1;
if (!node->children[bit]) node->children[bit] = new TrieNode();
node = node->children[bit];
}
}
int maxXor(int num) {
TrieNode* node = root;
if (!node->children[0] && !node->children[1]) return -1;
int max_xor = 0;
for (int i = 31; i >= 0; i--) {
int bit = (num >> i) & 1;
int toggled_bit = 1 - bit;
if (node->children[toggled_bit]) {
max_xor |= (1 << i);
node = node->children[toggled_bit];
} else {
node = node->children[bit];
}
}
return max_xor;
}
};
vector<int> maximizeXor(vector<int>& nums, vector<vector<int>>& queries) {
sort(nums.begin(), nums.end());
int n = (int)nums.size(), q = (int)queries.size();
vector<array<int,3>> qWithIndex(q);
for (int i = 0; i < q; i++) {
qWithIndex[i] = {queries[i][0], queries[i][1], i};
}
sort(qWithIndex.begin(), qWithIndex.end(), [](auto& a, auto& b) { return a[1] < b[1]; });
Trie trie;
vector<int> res(q, -1);
int idx = 0;
for (auto& query : qWithIndex) {
int x = query[0], m = query[1], i = query[2];
while (idx < n && nums[idx] <= m) {
trie.insert(nums[idx]);
idx++;
}
res[i] = trie.maxXor(x);
}
return res;
}
int main() {
vector<int> nums = {0,1,2,3,4};
vector<vector<int>> queries = {{3,1},{1,3},{5,6}};
vector<int> ans = maximizeXor(nums, queries);
for (int v : ans) cout << v << ' ';
cout << '\n';
return 0;
}
Line Notes
sort(nums.begin(), nums.end());Sort nums for incremental insertion
sort(qWithIndex.begin(), qWithIndex.end(), ...);Sort queries by m_i for offline processing
while (idx < n && nums[idx] <= m) {Insert all nums elements ≤ current query's m_i
res[i] = trie.maxXor(x);Query trie for max XOR with x_i among inserted nums
class TrieNode {
constructor() {
this.children = [null, null];
}
}
class Trie {
constructor() {
this.root = new TrieNode();
}
insert(num) {
let node = this.root;
for (let i = 31; i >= 0; i--) {
let bit = (num >> i) & 1;
if (!node.children[bit]) node.children[bit] = new TrieNode();
node = node.children[bit];
}
}
maxXor(num) {
let node = this.root;
if (!node.children[0] && !node.children[1]) return -1;
let maxXor = 0;
for (let i = 31; i >= 0; i--) {
let bit = (num >> i) & 1;
let toggledBit = 1 - bit;
if (node.children[toggledBit]) {
maxXor |= (1 << i);
node = node.children[toggledBit];
} else {
node = node.children[bit];
}
}
return maxXor;
}
}
var maximizeXor = function(nums, queries) {
nums.sort((a,b) => a-b);
let qWithIndex = queries.map((q,i) => [q[0], q[1], i]);
qWithIndex.sort((a,b) => a[1] - b[1]);
let trie = new Trie();
let res = new Array(queries.length).fill(-1);
let idx = 0;
for (let [x, m, i] of qWithIndex) {
while (idx < nums.length && nums[idx] <= m) {
trie.insert(nums[idx]);
idx++;
}
res[i] = trie.maxXor(x);
}
return res;
};
// Example usage
const nums = [0,1,2,3,4];
const queries = [[3,1],[1,3],[5,6]];
console.log(maximizeXor(nums, queries));
Line Notes
nums.sort((a,b) => a-b);Sort nums to insert incrementally based on m_i
qWithIndex.sort((a,b) => a[1] - b[1]);Sort queries by m_i for offline processing
while (idx < nums.length && nums[idx] <= m) {Insert all nums elements ≤ current query's m_i
res[i] = trie.maxXor(x);Query trie for max XOR with x_i among inserted nums
TimeO((n + q) * log(maxNum))
SpaceO(n * log(maxNum))
Sorting nums and queries takes O(n log n + q log q). Each nums element is inserted once into the trie with depth ~32 bits, and each query is answered in O(log maxNum).
💡 For n and q up to 10^5 and 32-bit numbers, this approach is efficient enough to run within time limits.
Interview Verdict: Accepted
This approach balances preprocessing and query answering efficiently, making it suitable for large inputs.