2939. Maximum Xor Product
Problem Description
You are given three integers a
, b
, and n
. Your task is to find a value x
where 0 <= x < 2^n
that maximizes the expression (a XOR x) * (b XOR x)
.
The XOR operation is the bitwise exclusive OR operation. Since the result can be very large, you need to return the answer modulo 10^9 + 7
.
The key insight is that x
can only affect the lower n
bits of both a
and b
. Any bits at position n
or higher in a
and b
remain unchanged regardless of the value of x
.
The solution works by greedily choosing bits for x
from the most significant bit (position n-1
) down to the least significant bit (position 0). For each bit position i
:
- If both
a
andb
have the same bit value at positioni
, setting the corresponding bit inx
to the opposite value will make both results have a1
at that position after XOR, maximizing the product. - If
a
andb
have different bit values at positioni
, exactly one of them will have a1
at that position after XOR. The algorithm chooses to give the1
to whichever value (ax
orbx
) is currently smaller to balance them and maximize their product.
The algorithm maintains ax
and bx
which represent (a XOR x)
and (b XOR x)
respectively, building them up bit by bit based on the greedy choices made for each bit position.
Intuition
To maximize the product (a XOR x) * (b XOR x)
, we need to think about how XOR operations work and how to make both resulting values as large as possible.
First, notice that x
can only be in the range [0, 2^n)
, which means x
can only have bits set in positions 0
to n-1
. Any bits at position n
or higher in a
and b
won't be affected by XORing with x
.
For any bit position i
(where i < n
), we can choose whether to set that bit in x
to 0
or 1
. This choice affects the i
-th bit in both (a XOR x)
and (b XOR x)
:
- If
a
has biti
as0
and we setx
's biti
to1
, then(a XOR x)
will have biti
as1
- If
a
has biti
as1
and we setx
's biti
to1
, then(a XOR x)
will have biti
as0
The key insight is that when both a
and b
have the same bit value at position i
, we can make both results have a 1
at that position by choosing the appropriate bit value for x
. This is always beneficial since having 1
s in higher bit positions increases the values more significantly.
When a
and b
have different bit values at position i
, only one of them can have a 1
at that position in the final result. Here, we face a choice: which one should get the 1
? The greedy strategy is to give the 1
to whichever value is currently smaller. This helps balance the two values, and for multiplication, having two balanced values generally gives a larger product than having one very large and one very small value (think of how 5 * 5 = 25
is larger than 9 * 1 = 9
even though the sum is the same).
By processing bits from most significant to least significant, we ensure that our decisions for higher-value bits take priority, as they have a greater impact on the final product.
Solution Approach
The implementation follows a greedy approach, processing bits from position n-1
down to 0
:
-
Extract the unchangeable parts: First, we extract the bits at positions
n
and higher from botha
andb
, since these cannot be affected byx
. We do this by:ax = (a >> n) << n
- shiftsa
right byn
bits to remove lower bits, then shifts back left to get only the higher bitsbx = (b >> n) << n
- same operation forb
-
Process each bit position from high to low: We iterate through bit positions from
n-1
down to0
:for i in range(n - 1, -1, -1):
-
Extract current bit values: For each position
i
, we check the bit values in the originala
andb
:x = a >> i & 1
- gets thei
-th bit ofa
y = b >> i & 1
- gets thei
-th bit ofb
-
Apply the greedy strategy:
-
Case 1: Same bits (
x == y
): If both have the same bit value, we can make both results have a1
at positioni
by choosing the opposite bit forx
. We set thei
-th bit to1
in bothax
andbx
:ax |= 1 << i bx |= 1 << i
-
Case 2: Different bits (
x != y
): Only one result can have a1
at positioni
. We give it to the currently smaller value to balance them:elif ax > bx: bx |= 1 << i # Give the 1 to bx since it's smaller else: ax |= 1 << i # Give the 1 to ax since it's smaller or equal
-
-
Return the result: Finally, we compute the product
ax * bx
and return it modulo10^9 + 7
:return ax * bx % mod
The time complexity is O(n)
since we process each of the n
bits once. The space complexity is O(1)
as we only use a constant amount of extra space.
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 an example with a = 12
, b = 5
, and n = 4
.
Initial Setup:
a = 12
in binary:1100
b = 5
in binary:0101
- We need to find
x
where0 ≤ x < 16
(since2^4 = 16
)
Step 1: Extract unchangeable parts
Since n = 4
, we need bits at position 4 and higher:
ax = (12 >> 4) << 4 = 0
(no bits at position 4 or higher)bx = (5 >> 4) << 4 = 0
(no bits at position 4 or higher)
Step 2: Process each bit from position 3 to 0
Bit position 3:
- Bit in
a
:(12 >> 3) & 1 = 1
- Bit in
b
:(5 >> 3) & 1 = 0
- Since bits are different, only one can have a 1 after XOR
- Currently
ax = 0
andbx = 0
(equal), so we give the 1 toax
ax = 0 | (1 << 3) = 8
,bx = 0
Bit position 2:
- Bit in
a
:(12 >> 2) & 1 = 1
- Bit in
b
:(5 >> 2) & 1 = 1
- Since bits are the same, we can make both have 1 by choosing opposite in
x
ax = 8 | (1 << 2) = 12
,bx = 0 | (1 << 2) = 4
Bit position 1:
- Bit in
a
:(12 >> 1) & 1 = 0
- Bit in
b
:(5 >> 1) & 1 = 0
- Since bits are the same, we can make both have 1 by choosing opposite in
x
ax = 12 | (1 << 1) = 14
,bx = 4 | (1 << 1) = 6
Bit position 0:
- Bit in
a
:(12 >> 0) & 1 = 0
- Bit in
b
:(5 >> 0) & 1 = 1
- Since bits are different, only one can have a 1 after XOR
- Currently
ax = 14
andbx = 6
(ax > bx), so we give the 1 tobx
ax = 14
,bx = 6 | (1 << 0) = 7
Final Result:
ax = 14
,bx = 7
- Product =
14 * 7 = 98
To verify, we can reconstruct x
:
- At position 3: we chose to make
a XOR x
have 1, sox
bit 3 = opposite ofa
bit 3 = 0 - At position 2: we chose to make both have 1, so
x
bit 2 = opposite of their common bit = 0 - At position 1: we chose to make both have 1, so
x
bit 1 = opposite of their common bit = 1 - At position 0: we chose to make
b XOR x
have 1, sox
bit 0 = opposite ofb
bit 0 = 0
Therefore, x = 0010
(binary) = 2 (decimal)
Verification: (12 XOR 2) * (5 XOR 2) = 14 * 7 = 98
✓
Solution Implementation
1class Solution:
2 def maximumXorProduct(self, a: int, b: int, n: int) -> int:
3 MOD = 10**9 + 7
4
5 # Initialize ax and bx with bits from position n and above preserved from a and b
6 # This keeps the high bits (position n and above) unchanged
7 ax = (a >> n) << n
8 bx = (b >> n) << n
9
10 # Process each bit position from n-1 down to 0
11 for i in range(n - 1, -1, -1):
12 # Extract the i-th bit from a and b
13 bit_a = (a >> i) & 1
14 bit_b = (b >> i) & 1
15
16 # If both bits are the same, set the i-th bit to 1 in both results
17 # This maximizes the product since both numbers increase
18 if bit_a == bit_b:
19 ax |= (1 << i)
20 bx |= (1 << i)
21 # If bits are different, assign the bit to make the numbers more balanced
22 # Give the bit to the smaller number to maximize the product
23 elif ax > bx:
24 bx |= (1 << i)
25 else:
26 ax |= (1 << i)
27
28 # Return the product modulo MOD
29 return (ax * bx) % MOD
30
1class Solution {
2 public int maximumXorProduct(long a, long b, int n) {
3 final int MOD = (int) 1e9 + 7;
4
5 // Initialize ax and bx by preserving bits above position n
6 // Clear the lower n bits by right shifting n positions then left shifting back
7 long ax = (a >> n) << n;
8 long bx = (b >> n) << n;
9
10 // Process each bit position from n-1 down to 0
11 for (int i = n - 1; i >= 0; --i) {
12 // Extract the i-th bit from a and b
13 long bitFromA = (a >> i) & 1;
14 long bitFromB = (b >> i) & 1;
15
16 // If both bits are the same (both 0 or both 1)
17 if (bitFromA == bitFromB) {
18 // Set the i-th bit to 1 in both ax and bx to maximize product
19 ax |= 1L << i;
20 bx |= 1L << i;
21 }
22 // If bits are different, assign 1 to the smaller number to balance the product
23 else if (ax < bx) {
24 // Set the i-th bit in ax to make it larger
25 ax |= 1L << i;
26 } else {
27 // Set the i-th bit in bx to make it larger
28 bx |= 1L << i;
29 }
30 }
31
32 // Apply modulo to prevent overflow
33 ax %= MOD;
34 bx %= MOD;
35
36 // Calculate and return the product modulo MOD
37 return (int) ((ax * bx) % MOD);
38 }
39}
40
1class Solution {
2public:
3 int maximumXorProduct(long long a, long long b, int n) {
4 const int MOD = 1000000007;
5
6 // Initialize XOR results by preserving bits above position n-1
7 // These bits cannot be modified by x (since x < 2^n)
8 long long xorResultA = (a >> n) << n; // Clear lower n bits of a
9 long long xorResultB = (b >> n) << n; // Clear lower n bits of b
10
11 // Process each bit position from (n-1) down to 0
12 // We can choose x to have any bit pattern in these positions
13 for (int bitPos = n - 1; bitPos >= 0; --bitPos) {
14 // Extract the current bit from original values a and b
15 int bitFromA = (a >> bitPos) & 1;
16 int bitFromB = (b >> bitPos) & 1;
17
18 if (bitFromA == bitFromB) {
19 // When bits are same, setting x's bit to opposite value
20 // makes both XOR results have 1 at this position
21 // This maximizes the product
22 xorResultA |= (1LL << bitPos);
23 xorResultB |= (1LL << bitPos);
24 } else {
25 // When bits are different, one XOR result gets 1, other gets 0
26 // Give the bit to the smaller number to balance the product
27 if (xorResultA < xorResultB) {
28 xorResultA |= (1LL << bitPos);
29 } else {
30 xorResultB |= (1LL << bitPos);
31 }
32 }
33 }
34
35 // Apply modulo to prevent overflow
36 xorResultA %= MOD;
37 xorResultB %= MOD;
38
39 // Return the product modulo MOD
40 return (xorResultA * xorResultB) % MOD;
41 }
42};
43
1/**
2 * Finds the maximum XOR product of two numbers after applying XOR with a value x
3 * where x is in the range [0, 2^n - 1]
4 *
5 * @param a - First number
6 * @param b - Second number
7 * @param n - Number of bits to consider for XOR operation
8 * @returns The maximum product modulo 10^9 + 7
9 */
10function maximumXorProduct(a: number, b: number, n: number): number {
11 const MOD: bigint = BigInt(1e9 + 7);
12
13 // Initialize ax and bx by preserving bits above position n
14 // Clear the lower n bits (they will be optimized)
15 let resultA: bigint = (BigInt(a) >> BigInt(n)) << BigInt(n);
16 let resultB: bigint = (BigInt(b) >> BigInt(n)) << BigInt(n);
17
18 // Iterate through each bit position from n-1 down to 0
19 for (let bitPosition: bigint = BigInt(n - 1); ~bitPosition; --bitPosition) {
20 // Extract the bit at current position from original a and b
21 const bitFromA: bigint = (BigInt(a) >> bitPosition) & 1n;
22 const bitFromB: bigint = (BigInt(b) >> bitPosition) & 1n;
23
24 // If both bits are the same, set both result bits to 1
25 // This maximizes both numbers when XOR with same bit value
26 if (bitFromA === bitFromB) {
27 resultA |= 1n << bitPosition;
28 resultB |= 1n << bitPosition;
29 }
30 // If bits differ, assign 1 to the smaller number to balance them
31 // This helps maximize the product
32 else if (resultA < resultB) {
33 resultA |= 1n << bitPosition;
34 } else {
35 resultB |= 1n << bitPosition;
36 }
37 }
38
39 // Apply modulo to prevent overflow
40 resultA %= MOD;
41 resultB %= MOD;
42
43 // Calculate and return the final product modulo MOD
44 return Number((resultA * resultB) % MOD);
45}
46
Time and Space Complexity
Time Complexity: O(n)
The algorithm iterates through a loop from n-1
down to 0
, performing constant-time operations in each iteration. The loop runs exactly n
times, where each iteration involves:
- Bit extraction operations (
a >> i & 1
andb >> i & 1
):O(1)
- Comparison operations:
O(1)
- Bitwise OR operations to set bits:
O(1)
The initial operations before the loop (computing ax
and bx
using bit shifts) are also O(1)
operations. The final multiplication and modulo operation at the end is O(1)
. Therefore, the overall time complexity is O(n)
.
Space Complexity: O(1)
The algorithm uses only a fixed amount of extra space:
- Variables
mod
,ax
,bx
: constant space - Loop variable
i
: constant space - Temporary variables
x
andy
for bit values: constant space
No additional data structures that grow with input size are used. The space usage remains constant regardless of the value of n
, resulting in O(1)
space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Bit Extraction for High Bits
A common mistake is incorrectly preserving the bits at position n
and above. Some might try:
# WRONG: This doesn't preserve high bits correctly ax = a & ~((1 << n) - 1) # Might overflow for large n
Issue: Using bit masks with large values of n
can cause issues, and the logic can be confusing.
Solution: Use the shift operations as shown in the correct implementation:
ax = (a >> n) << n # Clearly removes lower n bits then restores position bx = (b >> n) << n
2. Forgetting the Modulo Operation
The result can be extremely large, and forgetting to apply modulo can cause:
- Integer overflow in languages with fixed integer sizes
- Wrong answer on the platform
# WRONG: Missing modulo return ax * bx # CORRECT: Apply modulo return (ax * bx) % MOD
3. Incorrect Greedy Decision for Different Bits
When bits differ, a common mistake is always giving the bit to a specific number or using the wrong comparison:
# WRONG: Always favor ax regardless of current values if bit_a != bit_b: ax |= (1 << i) # WRONG: Compare original a and b instead of current ax and bx if bit_a != bit_b: if a > b: # Should be ax > bx bx |= (1 << i) else: ax |= (1 << i)
Solution: Compare the current accumulated values ax
and bx
:
if ax > bx: bx |= (1 << i) else: ax |= (1 << i)
4. Off-by-One Error in Bit Range
Processing the wrong range of bits is a common error:
# WRONG: Processes n bits instead of n-1 to 0
for i in range(n, 0, -1): # Goes from n to 1
# CORRECT: Process bits from position n-1 down to 0
for i in range(n - 1, -1, -1): # Goes from n-1 to 0
5. Not Understanding XOR Properties
Some might try complex calculations instead of using the simple property that a XOR x
at bit i
equals:
1
ifa[i] != x[i]
0
ifa[i] == x[i]
This leads to unnecessarily complex code when the greedy approach is straightforward:
- If
a[i] == b[i]
, choosex[i]
to be opposite to maximize both - If
a[i] != b[i]
, one will be 1 and one will be 0 regardless, so balance the values
Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
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
Want a Structured Path to Master System Design Too? Don’t Miss This!