Maximum XOR With an Element From Array
HardPrompt
You are given an array nums consisting of non-negative integers. You are also given a queries array, where queries[i] = [xi, mi].
The answer to the ith query is the maximum bitwise XOR value of xi and any element of nums that does not exceed mi. In other words, the answer is max(nums[j] XOR xi) for all j such that nums[j] <= mi. If all elements in nums are larger than mi, then the answer is -1.
Return an integer array answer where answer.length == queries.length and answer[i] is the answer to the ith 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 not greater than 1. 0 XOR 3 = 3 and 1 XOR 3 = 2. The larger 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]
Constraints:
1 <= nums.length, queries.length <= 105queries[i].length == 20 <= nums[j], xi, mi <= 109
Approaches
2 approaches with complexity analysis and trade-offs.
This approach directly translates the problem statement into code. For each query, it iterates through the entire nums array to find the numbers that satisfy the condition nums[j] <= m_i and calculates the maximum XOR value.
Algorithm
- Initialize an
answerarray of the same size asqueries. - For each query
[xi, mi]inqueries: a. Initializemax_xor = -1. b. Iterate through each numbernuminnums. c. Ifnum <= mi: i. Calculatecurrent_xor = xi XOR num. ii. Updatemax_xor = max(max_xor, current_xor). d. Storemax_xorin theanswerarray for the current query. - Return the
answerarray.
Walkthrough
We initialize an answer array to store the results for each query. We loop through each query [x_i, m_i] from the queries array. For each query, we initialize a variable max_xor to -1. This variable will keep track of the maximum XOR value found so far for the current query. We then iterate through every number num in the nums array. Inside this inner loop, we check if num <= m_i. If the condition is met, we calculate the bitwise XOR of x_i and num. We then update max_xor to be the maximum of its current value and the newly calculated XOR value. After iterating through all numbers in nums, the value of max_xor is the answer for the current query. If no number satisfied the condition, max_xor remains -1. We store this result in our answer array. Finally, after processing all queries, we return the answer array.
class Solution { public int[] maximizeXor(int[] nums, int[][] queries) { int n = queries.length; int[] answer = new int[n]; for (int i = 0; i < n; i++) { int x = queries[i][0]; int m = queries[i][1]; int maxVal = -1; for (int num : nums) { if (num <= m) { maxVal = Math.max(maxVal, x ^ num); } } answer[i] = maxVal; } return answer; }}Complexity
Time
O(N * Q), where N is the length of `nums` and Q is the length of `queries`. For each of the Q queries, we perform a linear scan of the N elements in `nums`.
Space
O(Q) to store the result array. The auxiliary space used is O(1).
Trade-offs
Pros
Simple to understand and implement.
Requires minimal auxiliary space.
Cons
Very inefficient due to its O(N * Q) time complexity.
Will result in a 'Time Limit Exceeded' error for large inputs as specified in the problem constraints.
This approach optimizes the process by sorting both the numbers and the queries. By processing queries in increasing order of their m_i limit, we can efficiently manage the set of valid numbers using a Trie data structure. This avoids re-scanning the nums array for each query.
Algorithm
- Create an
indexedQueriesarray where each element is[x, m, original_index]. - Sort the
numsarray in ascending order. - Sort the
indexedQueriesarray based on themvalue in ascending order. - Initialize an empty Trie, an
answerarray of sizequeries.length, and a pointernumsIndex = 0. - For each
[x, m, originalIndex]insortedQueries: - While
numsIndex < nums.lengthandnums[numsIndex] <= m: -
Insert `nums[numsIndex]` into the Trie. -
Increment `numsIndex`. - If
numsIndex == 0(Trie is empty): - Set
answer[originalIndex] = -1. - Else:
- Query the Trie with
xto find the maximum XOR value. - Set
answer[originalIndex]to this maximum value. - Return
answer.
Walkthrough
The main idea is to answer queries "offline". Instead of answering them in the given order, we reorder them to process them more efficiently. We sort the nums array in ascending order. We also sort the queries array based on their m_i values. Since we need to return the answers in the original order, we augment the queries with their original indices before sorting, e.g., [x_i, m_i, original_index]. We use a Trie (Prefix Tree) to store the binary representations of numbers. Each node in the Trie has two children, representing bits 0 and 1. We iterate through the sorted queries. For each query [x, m, original_idx], we first add all numbers from the sorted nums array that are less than or equal to m into the Trie. Since both nums and queries (by m) are sorted, we can do this with a single pointer on the nums array, which advances monotonically. After updating the Trie with all numbers <= m, we query the Trie with x to find the number that gives the maximum XOR value. To do this, we traverse the Trie from the most significant bit to the least significant. At each bit position, we try to take a path that is opposite to the corresponding bit of x to maximize the XOR result. If such a path doesn't exist, we take the only available path. If the Trie is empty when we process a query (meaning no number in nums is <= m), the answer is -1. The result for each query is stored in an answer array at its original index. Finally, we return the fully populated answer array.
class TrieNode { TrieNode[] children = new TrieNode[2];} class Trie { TrieNode root = new TrieNode(); private static final int MAX_BIT = 30; public void insert(int num) { TrieNode curr = root; for (int i = MAX_BIT; i >= 0; i--) { int bit = (num >> i) & 1; if (curr.children[bit] == null) { curr.children[bit] = new TrieNode(); } curr = curr.children[bit]; } } public int getMaxXor(int num) { TrieNode curr = root; int maxXor = 0; for (int i = MAX_BIT; i >= 0; i--) { int bit = (num >> i) & 1; int oppositeBit = 1 - bit; if (curr.children[oppositeBit] != null) { maxXor |= (1 << i); curr = curr.children[oppositeBit]; } else { curr = curr.children[bit]; } } return maxXor; }} class Solution { public int[] maximizeXor(int[] nums, int[][] queries) { Arrays.sort(nums); int numQueries = queries.length; int[][] indexedQueries = new int[numQueries][3]; for (int i = 0; i < numQueries; i++) { indexedQueries[i][0] = queries[i][0]; indexedQueries[i][1] = queries[i][1]; indexedQueries[i][2] = i; } Arrays.sort(indexedQueries, (a, b) -> Integer.compare(a[1], b[1])); int[] ans = new int[numQueries]; Trie trie = new Trie(); int numsIndex = 0; for (int[] query : indexedQueries) { int x = query[0]; int m = query[1]; int originalIndex = query[2]; while (numsIndex < nums.length && nums[numsIndex] <= m) { trie.insert(nums[numsIndex]); numsIndex++; } if (numsIndex == 0) { ans[originalIndex] = -1; } else { ans[originalIndex] = trie.getMaxXor(x); } } return ans; }}Complexity
Time
O(N log N + Q log Q + (N+Q)*L), where N is the length of `nums`, Q is the length of `queries`, and L is the number of bits (constant). The complexity is dominated by the sorting steps, making it O(N log N + Q log Q).
Space
O(Q + N*L), where Q is for storing `indexedQueries` and the `ans` array, and O(N*L) is for the Trie in the worst case, where L is the number of bits (constant, e.g., 31).
Trade-offs
Pros
Highly efficient and passes the time limits for the given constraints.
The offline processing technique is a powerful pattern for similar problems where queries have range constraints.
Cons
More complex to implement compared to the brute-force approach.
Requires additional space for the Trie data structure and for storing augmented queries.
Solutions
Solution
class Trie { private Trie [] children = new Trie [ 2 ]; public void insert ( int x ) { Trie node = this ; for ( int i = 30 ; i >= 0 ; -- i ) { int v = x >> i & 1 ; if ( node . children [ v ] == null ) { node . children [ v ] = new Trie (); } node = node . children [ v ]; } } public int search ( int x ) { Trie node = this ; int ans = 0 ; for ( int i = 30 ; i >= 0 ; -- i ) { int v = x >> i & 1 ; if ( node . children [ v ^ 1 ] != null ) { ans |= 1 << i ; node = node . children [ v ^ 1 ]; } else if ( node . children [ v ] != null ) { node = node . children [ v ]; } else { return - 1 ; } } return ans ; } } class Solution { public int [] maximizeXor ( int [] nums , int [][] queries ) { Arrays . sort ( nums ); int n = queries . length ; Integer [] idx = new Integer [ n ]; for ( int i = 0 ; i < n ; ++ i ) { idx [ i ] = i ; } Arrays . sort ( idx , ( i , j ) -> queries [ i ][ 1 ] - queries [ j ][ 1 ]); int [] ans = new int [ n ]; Trie trie = new Trie (); int j = 0 ; for ( int i : idx ) { int x = queries [ i ][ 0 ], m = queries [ i ][ 1 ]; while ( j < nums . length && nums [ j ] <= m ) { trie . insert ( nums [ j ++]); } ans [ i ] = trie . search ( x ); } return ans ; } }class Trie { private: Trie * children [ 2 ]; public: Trie () : children { nullptr , nullptr } {} void insert ( int x ) { Trie * node = this ; for ( int i = 30 ; ~ i ; -- i ) { int v = ( x >> i ) & 1 ; if ( ! node -> children [ v ]) { node -> children [ v ] = new Trie (); } node = node -> children [ v ]; } } int search ( int x ) { Trie * node = this ; int ans = 0 ; for ( int i = 30 ; ~ i ; -- i ) { int v = ( x >> i ) & 1 ; if ( node -> children [ v ^ 1 ]) { ans |= 1 << i ; node = node -> children [ v ^ 1 ]; } else if ( node -> children [ v ]) { node = node -> children [ v ]; } else { return - 1 ; } } return ans ; } }; class Solution { public: vector < int > maximizeXor ( vector < int >& nums , vector < vector < int >>& queries ) { sort ( nums . begin (), nums . end ()); int n = queries . size (); vector < int > idx ( n ); iota ( idx . begin (), idx . end (), 0 ); sort ( idx . begin (), idx . end (), [ & ]( int i , int j ) { return queries [ i ][ 1 ] < queries [ j ][ 1 ]; }); vector < int > ans ( n ); Trie trie ; int j = 0 ; for ( int i : idx ) { int x = queries [ i ][ 0 ], m = queries [ i ][ 1 ]; while ( j < nums . size () && nums [ j ] <= m ) { trie . insert ( nums [ j ++ ]); } ans [ i ] = trie . search ( x ); } return ans ; } };class Trie : __slots__ = [ "children" ] def __init__ ( self ): self . children = [ None ] * 2 def insert ( self , x : int ): node = self for i in range ( 30 , - 1 , - 1 ): v = x >> i & 1 if node . children [ v ] is None : node . children [ v ] = Trie () node = node . children [ v ] def search ( self , x : int ) -> int : node = self ans = 0 for i in range ( 30 , - 1 , - 1 ): v = x >> i & 1 if node . children [ v ^ 1 ]: ans |= 1 << i node = node . children [ v ^ 1 ] elif node . children [ v ]: node = node . children [ v ] else : return - 1 return ans class Solution : def maximizeXor ( self , nums : List [ int ], queries : List [ List [ int ]]) -> List [ int ]: trie = Trie () nums . sort () j , n = 0 , len ( queries ) ans = [ - 1 ] * n for i , ( x , m ) in sorted ( zip ( range ( n ), queries ), key = lambda x : x [ 1 ][ 1 ]): while j < len ( nums ) and nums [ j ] <= m : trie . insert ( nums [ j ]) j += 1 ans [ i ] = trie . search ( x ) return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.