1829. Maximum XOR for Each Query
Problem Description
You have a sorted array nums
containing n
non-negative integers and an integer maximumBit
. You need to perform n
queries on this array.
For each query:
- Find a non-negative integer
k
wherek < 2^maximumBit
such that the XOR of all elements in the current array withk
gives the maximum possible value. In other words, findk
that maximizes:nums[0] XOR nums[1] XOR ... XOR nums[nums.length-1] XOR k
- After finding
k
for the current query, remove the last element from the array before proceeding to the next query
The goal is to return an array answer
where answer[i]
contains the value of k
found in the i
-th query.
Key Points:
- Each query operates on a progressively smaller array (removing the last element after each query)
- The value
k
must be less than2^maximumBit
- You want to maximize the XOR result in each query
- The process repeats exactly
n
times (once for each element originally in the array)
For example, if you start with nums = [0,1,2,3]
and maximumBit = 2
:
- First query: Find
k
that maximizes XOR with[0,1,2,3]
, then remove3
- Second query: Find
k
that maximizes XOR with[0,1,2]
, then remove2
- Third query: Find
k
that maximizes XOR with[0,1]
, then remove1
- Fourth query: Find
k
that maximizes XOR with[0]
, then remove0
Intuition
To maximize the XOR result, we need to understand how XOR works at the bit level. XOR gives us 1
when bits are different and 0
when bits are same. So to get the maximum value, we want as many 1
s as possible in the higher bit positions.
Let's say we have the XOR of all current elements as xs
. To maximize xs XOR k
, we should make k
have opposite bits to xs
wherever possible. This way:
- If
xs
has a0
at positioni
, we setk
to have1
at positioni
→ resulting in0 XOR 1 = 1
- If
xs
has a1
at positioni
, we setk
to have0
at positioni
→ resulting in1 XOR 0 = 1
This gives us the maximum number of 1
s in the result.
Since we're constrained by k < 2^maximumBit
, we can only flip bits from position maximumBit-1
down to position 0
. We examine each bit position from high to low: if the XOR sum has a 0
at that position, we set the corresponding bit in k
to 1
.
The clever optimization here is that instead of recalculating the XOR sum from scratch for each query, we can maintain a running XOR sum. When we "remove" the last element for the next query, we simply XOR the running sum with that element (since a XOR a = 0
, this effectively removes it from the XOR chain).
By processing the array in reverse order and maintaining the XOR sum, we can efficiently find the optimal k
for each query state without redundant calculations.
Learn more about Prefix Sum patterns.
Solution Approach
The implementation follows these steps:
-
Calculate Initial XOR Sum: First, compute the XOR of all elements in
nums
and store it asxs
. This representsnums[0] XOR nums[1] XOR ... XOR nums[n-1]
. -
Process Array in Reverse: Iterate through the array from the last element to the first. This aligns with the requirement to remove the last element after each query.
-
Find Optimal k for Each Query:
- For each iteration, initialize
k = 0
- Examine each bit position from
maximumBit - 1
down to0
- Check if the bit at position
i
inxs
is0
using(xs >> i & 1) == 0
- If it's
0
, set the corresponding bit ink
to1
usingk |= 1 << i
- This ensures
xs XOR k
has a1
at that bit position, maximizing the result
- For each iteration, initialize
-
Update XOR Sum: After finding
k
for the current state, updatexs
by XORing it with the current elementx
. This effectively "removes"x
from the XOR sum since(a XOR b) XOR b = a
. -
Build Answer Array: Append each
k
to the answer array and return it after processing all elements.
The algorithm processes each element once and checks maximumBit
bits for each element, giving us a time complexity of O(n × maximumBit)
. The space complexity is O(1)
excluding the output array.
The key insight is using the property that XOR is its own inverse: removing an element from an XOR sum is the same as XORing the sum with that element again.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with nums = [0,1,1,3]
and maximumBit = 2
.
Initial Setup:
- First, calculate the XOR sum of all elements:
xs = 0 XOR 1 XOR 1 XOR 3 = 3
(in binary:11
) - We'll process the array in reverse order:
3 → 1 → 1 → 0
Query 1 (array state: [0,1,1,3]
, xs = 3
which is 11
in binary):
- We need to find
k < 2^2 = 4
that maximizes3 XOR k
- Check bit positions from
maximumBit - 1 = 1
down to0
:- Bit 1:
xs
has1
at position 1 (11
→ second bit is1
), so keepk
's bit 1 as0
- Bit 0:
xs
has1
at position 0 (11
→ first bit is1
), so keepk
's bit 0 as0
- Bit 1:
- Result:
k = 0
(binary:00
) - Verify:
3 XOR 0 = 3
(this gives us11
in binary) - Update
xs
for next query:xs = 3 XOR 3 = 0
(remove last element3
)
Query 2 (array state: [0,1,1]
, xs = 0
which is 00
in binary):
- Find
k
that maximizes0 XOR k
- Check bit positions:
- Bit 1:
xs
has0
at position 1, so setk
's bit 1 to1
- Bit 0:
xs
has0
at position 0, so setk
's bit 0 to1
- Bit 1:
- Result:
k = 3
(binary:11
) - Verify:
0 XOR 3 = 3
(maximized) - Update
xs
:xs = 0 XOR 1 = 1
(remove element1
)
Query 3 (array state: [0,1]
, xs = 1
which is 01
in binary):
- Find
k
that maximizes1 XOR k
- Check bit positions:
- Bit 1:
xs
has0
at position 1, so setk
's bit 1 to1
- Bit 0:
xs
has1
at position 0, so keepk
's bit 0 as0
- Bit 1:
- Result:
k = 2
(binary:10
) - Verify:
1 XOR 2 = 3
(gives us11
in binary) - Update
xs
:xs = 1 XOR 1 = 0
(remove element1
)
Query 4 (array state: [0]
, xs = 0
which is 00
in binary):
- Find
k
that maximizes0 XOR k
- Check bit positions:
- Bit 1:
xs
has0
at position 1, so setk
's bit 1 to1
- Bit 0:
xs
has0
at position 0, so setk
's bit 0 to1
- Bit 1:
- Result:
k = 3
(binary:11
) - Verify:
0 XOR 3 = 3
(maximized)
Final Answer: [0, 3, 2, 3]
The key insight demonstrated here is that we're essentially finding the bitwise complement of xs
within the constraint of maximumBit
. By processing in reverse and updating the XOR sum incrementally, we avoid recalculating the XOR from scratch for each query.
Solution Implementation
1from typing import List
2from functools import reduce
3from operator import xor
4
5class Solution:
6 def getMaximumXor(self, nums: List[int], maximumBit: int) -> List[int]:
7 """
8 Find the maximum XOR value for each query by removing elements from the end.
9
10 Args:
11 nums: List of non-negative integers
12 maximumBit: Maximum number of bits to consider
13
14 Returns:
15 List of k values that maximize XOR for each query
16 """
17 result = []
18
19 # Calculate XOR of all elements in nums
20 cumulative_xor = reduce(xor, nums)
21
22 # Process queries by removing elements from the end
23 for num in reversed(nums):
24 # Find k that maximizes cumulative_xor XOR k
25 # where k < 2^maximumBit
26 k = 0
27
28 # Check each bit position from most significant to least
29 for bit_position in range(maximumBit - 1, -1, -1):
30 # If current bit in cumulative_xor is 0, set it to 1 in k
31 # This maximizes the XOR result
32 if (cumulative_xor >> bit_position & 1) == 0:
33 k |= 1 << bit_position
34
35 result.append(k)
36
37 # Remove current element from cumulative XOR for next query
38 cumulative_xor ^= num
39
40 return result
41
1class Solution {
2 public int[] getMaximumXor(int[] nums, int maximumBit) {
3 int n = nums.length;
4
5 // Calculate XOR of all elements in the array
6 int xorSum = 0;
7 for (int num : nums) {
8 xorSum ^= num;
9 }
10
11 // Result array to store maximum XOR values
12 int[] result = new int[n];
13
14 // Process each query by removing elements from the end
15 for (int i = 0; i < n; i++) {
16 // Get the element to be removed (from the end of current array)
17 int elementToRemove = nums[n - i - 1];
18
19 // Find k that maximizes XOR with current xorSum
20 // k must be less than 2^maximumBit
21 int k = 0;
22
23 // Check each bit position from most significant to least significant
24 for (int bitPosition = maximumBit - 1; bitPosition >= 0; bitPosition--) {
25 // If the bit at current position in xorSum is 0,
26 // set the corresponding bit in k to 1 to maximize XOR
27 if (((xorSum >> bitPosition) & 1) == 0) {
28 k |= (1 << bitPosition);
29 }
30 // If the bit is already 1, keeping k's bit as 0 maximizes XOR
31 }
32
33 // Store the maximum XOR value for this query
34 result[i] = k;
35
36 // Remove the last element from xorSum for next iteration
37 xorSum ^= elementToRemove;
38 }
39
40 return result;
41 }
42}
43
1class Solution {
2public:
3 vector<int> getMaximumXor(vector<int>& nums, int maximumBit) {
4 // Calculate XOR of all elements in the array
5 int xorSum = 0;
6 for (int& num : nums) {
7 xorSum ^= num;
8 }
9
10 int n = nums.size();
11 vector<int> result(n);
12
13 // Process queries in reverse order (removing elements from the end)
14 for (int i = 0; i < n; ++i) {
15 // Get the element to be removed in this iteration
16 int elementToRemove = nums[n - i - 1];
17
18 // Find k that maximizes (xorSum XOR k)
19 // k must be less than 2^maximumBit
20 int k = 0;
21
22 // Check each bit position from most significant to least significant
23 for (int bitPos = maximumBit - 1; bitPos >= 0; --bitPos) {
24 // If the bit at position bitPos in xorSum is 0,
25 // set the corresponding bit in k to 1 to maximize XOR
26 if ((xorSum >> bitPos & 1) == 0) {
27 k |= (1 << bitPos);
28 }
29 }
30
31 // Store the result for this query
32 result[i] = k;
33
34 // Remove the element from xorSum for the next iteration
35 xorSum ^= elementToRemove;
36 }
37
38 return result;
39 }
40};
41
1/**
2 * Finds the maximum XOR values for each query by removing elements from the end
3 * @param nums - Array of non-negative integers
4 * @param maximumBit - Maximum number of bits to consider
5 * @returns Array of maximum XOR values for each query
6 */
7function getMaximumXor(nums: number[], maximumBit: number): number[] {
8 // Calculate XOR of all elements in the array
9 let cumulativeXor: number = 0;
10 for (const num of nums) {
11 cumulativeXor ^= num;
12 }
13
14 const arrayLength: number = nums.length;
15 const result: number[] = new Array(arrayLength);
16
17 // Process each query by removing elements from the end
18 for (let i = 0; i < arrayLength; i++) {
19 // Get the element to be removed (from the end of array)
20 const elementToRemove: number = nums[arrayLength - i - 1];
21
22 // Find k that maximizes XOR with current cumulative XOR
23 let maxXorValue: number = 0;
24
25 // Check each bit position from most significant to least significant
26 for (let bitPosition = maximumBit - 1; bitPosition >= 0; bitPosition--) {
27 // If bit at current position is 0, set it to 1 in k for maximum XOR
28 if (((cumulativeXor >> bitPosition) & 1) === 0) {
29 maxXorValue |= (1 << bitPosition);
30 }
31 }
32
33 // Store the maximum XOR value for this query
34 result[i] = maxXorValue;
35
36 // Remove the element from cumulative XOR for next iteration
37 cumulativeXor ^= elementToRemove;
38 }
39
40 return result;
41}
42
Time and Space Complexity
The time complexity is O(n × m)
, where n
is the length of the array nums
and m
is the value of maximumBit
.
- The initial XOR reduction of all elements in
nums
takesO(n)
time. - The main loop iterates through all
n
elements ofnums
in reverse. - For each element, we have an inner loop that iterates
maximumBit
times (frommaximumBit - 1
down to0
). - Inside the inner loop, bit operations (shift, AND, OR) are performed in
O(1)
time. - Therefore, the total time complexity is
O(n) + O(n × m) = O(n × m)
.
The space complexity is O(1)
when ignoring the space consumption of the answer array ans
. The only extra space used is for:
- The variable
xs
to store the running XOR result - The variable
k
to build each answer element - The loop variable
i
All these variables use constant space regardless of input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Understanding of XOR Maximization
A common mistake is thinking that we need to try all possible values of k
from 0
to 2^maximumBit - 1
to find the maximum XOR. This brute force approach would be inefficient and unnecessary.
Why it's wrong: Testing all possible k
values would result in O(n × 2^maximumBit) time complexity, which becomes impractical for larger values of maximumBit
.
Correct approach: To maximize XOR, we should set bits in k
wherever the cumulative XOR has a 0
bit. This is because 0 XOR 1 = 1
and 1 XOR 0 = 1
, both giving us 1
in the result, while 1 XOR 1 = 0
would give us 0
.
2. Misunderstanding the Bit Constraint
Some might interpret k < 2^maximumBit
as meaning we can use all bits up to position maximumBit
(inclusive), leading to checking bit positions from maximumBit
down to 0
.
Why it's wrong: If maximumBit = 3
, then k < 2^3 = 8
, meaning k
can be at most 7
(binary: 111
). This uses bits at positions 0, 1, and 2 only. Checking bit position 3 would be incorrect.
Correct approach: Always iterate from maximumBit - 1
down to 0
, not from maximumBit
down to 0
.
3. Inefficient XOR Calculation
Recalculating the XOR of remaining elements from scratch after each query:
# Wrong approach
for i in range(len(nums)):
current_xor = 0
for j in range(len(nums) - i):
current_xor ^= nums[j]
# Find k...
Why it's wrong: This results in O(n²) time complexity just for calculating XOR sums.
Correct approach: Use the XOR property that a XOR a = 0
. We can maintain a running XOR and remove elements by XORing again with the element being removed.
4. Processing Order Confusion
Processing the array from beginning to end while trying to remove from the end:
# Wrong approach
cumulative_xor = reduce(xor, nums)
for i in range(len(nums)):
# Find k for current cumulative_xor
# Remove nums[i] - but we should remove from the end!
cumulative_xor ^= nums[i]
Why it's wrong: The problem specifically states to remove the last element after each query, not the first.
Correct approach: Either iterate through reversed(nums)
or use indices counting backward from len(nums) - 1
to 0
.
5. Bit Manipulation Errors
Using incorrect bit operations when building k
:
# Wrong approach if (cumulative_xor >> bit_position & 1) == 0: k += 1 << bit_position # Using addition instead of OR
Why it's wrong: Addition can cause carry-over effects if the bit is already set, though in this specific case it won't happen since we're only setting bits that aren't already set.
Correct approach: Use bitwise OR (k |= 1 << bit_position
) to ensure we're setting individual bits without affecting others.
Depth first search is equivalent to which of the tree traversal order?
Recommended Readings
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!