Facebook Pixel

2429. Minimize XOR

MediumGreedyBit Manipulation
Leetcode Link

Problem Description

You are given two positive integers num1 and num2. Your task is to find a positive integer x that satisfies two conditions:

  1. The integer x must have the exact same number of set bits (1s in binary representation) as num2
  2. The XOR operation between x and num1 should produce the smallest possible result

The XOR operation compares bits at each position - it returns 1 when bits are different and 0 when they are the same.

For example, if num1 = 3 (binary: 11) and num2 = 5 (binary: 101), then num2 has 2 set bits. We need to find an integer x with exactly 2 set bits such that x XOR num1 is minimized.

To minimize x XOR num1, we want x to be as similar to num1 as possible. The strategy is:

  • First, try to match the 1-bits in num1 by setting those same positions to 1 in x (this makes those positions become 0 after XOR)
  • If we still need more set bits to match the count from num2, add 1s in positions where num1 has 0s, starting from the least significant positions

The problem guarantees that there is a unique solution for each test case.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

To minimize x XOR num1, we need to understand what XOR does - it produces 0 when bits match and 1 when they differ. So to get the smallest XOR result, we want as many 0s as possible in the result, especially in the higher bit positions (since they contribute more to the final value).

The key insight is that we get a 0 in the XOR result when x and num1 have the same bit value at that position. Since we want to minimize the XOR result, we should prioritize matching the higher-order bits of num1 first.

Here's the greedy strategy:

  1. Match the 1-bits from high to low: Start from the most significant bits of num1. Wherever num1 has a 1, we should also put a 1 in x at that position (if we still have set bits to use). This ensures these positions become 0 after XOR, contributing nothing to the result.

  2. Handle remaining set bits: If we've matched all the 1s in num1 but still need more set bits to reach our target count (from num2), we must add 1s where num1 has 0s. To minimize impact, we add these from the least significant positions upward, since lower bits contribute less to the final value.

For example, if num1 = 1100 (binary) and we need 3 set bits total:

  • First, match the two 1s in num1: x becomes 1100 (used 2 set bits)
  • Still need 1 more set bit, so add it at the lowest available 0 position: x becomes 1101
  • 1100 XOR 1101 = 0001, which is minimal

This greedy approach works because placing 1s where num1 already has 1s "cancels them out" in XOR, while any additional 1s we're forced to add should be in the least significant positions to minimize their contribution.

Learn more about Greedy patterns.

Solution Approach

The implementation follows the greedy strategy we identified, using bit manipulation to efficiently construct the result:

Step 1: Count the required set bits

cnt = num2.bit_count()

We first determine how many 1-bits we need in our answer x by counting the set bits in num2.

Step 2: Match 1-bits from high to low

x = 0
for i in range(30, -1, -1):
    if num1 >> i & 1 and cnt:
        x |= 1 << i
        cnt -= 1

We iterate through bit positions from 30 down to 0 (covering all bits in a 32-bit integer). For each position i:

  • num1 >> i & 1 checks if bit i of num1 is 1
  • If it is 1 and we still have set bits to place (cnt > 0), we set bit i in x using x |= 1 << i
  • We decrement cnt after placing each bit

This ensures we first match all the 1-bits in num1 from most significant to least significant, minimizing the higher-order bits in the XOR result.

Step 3: Place remaining bits in 0 positions

for i in range(30):
    if num1 >> i & 1 ^ 1 and cnt:
        x |= 1 << i
        cnt -= 1

If we still need more set bits after matching all 1s in num1, we iterate from bit 0 upward:

  • num1 >> i & 1 ^ 1 checks if bit i of num1 is 0 (using XOR with 1 to flip the bit check)
  • If it's 0 and we still need set bits (cnt > 0), we set bit i in x
  • We decrement cnt for each bit placed

This places any remaining required 1-bits in the lowest available positions where num1 has 0s, minimizing their contribution to the final XOR value.

The algorithm runs in O(1) time since we iterate through at most 31 bits twice, and uses O(1) space to store the result.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through an example with num1 = 11 (binary: 1011) and num2 = 13 (binary: 1101).

Step 1: Count required set bits

  • num2 = 13 has binary representation 1101
  • Count of set bits = 3
  • So we need to construct x with exactly 3 set bits

Step 2: Match 1-bits from high to low in num1

  • num1 = 11 has binary representation 1011
  • Scanning from high to low (bit positions 30 down to 0):
    • Bit 3: num1 has 1 β†’ set bit 3 in x, now x = 1000, cnt = 2
    • Bit 2: num1 has 0 β†’ skip
    • Bit 1: num1 has 1 β†’ set bit 1 in x, now x = 1010, cnt = 1
    • Bit 0: num1 has 1 β†’ set bit 0 in x, now x = 1011, cnt = 0

Step 3: Place remaining bits (if any)

  • We've used all 3 required bits, so cnt = 0
  • No additional bits needed

Result:

  • x = 1011 (decimal: 11)
  • x XOR num1 = 1011 XOR 1011 = 0000 = 0

The XOR result is 0, which is the minimum possible value. This makes sense because we had enough set bits in num1 (3 bits) to match our requirement exactly.


Another example: num1 = 3 (binary: 11) and num2 = 5 (binary: 101)

Step 1: num2 has 2 set bits, so we need exactly 2 set bits in x

Step 2: Match 1-bits in num1 from high to low:

  • Bit 1: num1 has 1 β†’ set bit 1 in x, now x = 10, cnt = 1
  • Bit 0: num1 has 1 β†’ set bit 0 in x, now x = 11, cnt = 0

Step 3: cnt = 0, no additional bits needed

Result: x = 11 (decimal: 3), and 3 XOR 3 = 0


Example with remaining bits: num1 = 4 (binary: 100) and num2 = 7 (binary: 111)

Step 1: num2 has 3 set bits

Step 2: Match 1-bits in num1:

  • Bit 2: num1 has 1 β†’ set bit 2 in x, now x = 100, cnt = 2

Step 3: Still need 2 more bits, add from lowest 0-positions:

  • Bit 0: num1 has 0 β†’ set bit 0 in x, now x = 101, cnt = 1
  • Bit 1: num1 has 0 β†’ set bit 1 in x, now x = 111, cnt = 0

Result: x = 111 (decimal: 7), and 100 XOR 111 = 011 = 3

Solution Implementation

1class Solution:
2    def minimizeXor(self, num1: int, num2: int) -> int:
3        # Count the number of set bits (1s) in num2
4        # This is the number of set bits that x must have
5        remaining_bits = num2.bit_count()
6      
7        # Initialize result x to 0
8        x = 0
9      
10        # First pass: Set bits in x where num1 has 1s (from MSB to LSB)
11        # This minimizes XOR by matching 1s in num1
12        for bit_position in range(30, -1, -1):
13            # Check if current bit in num1 is set and we still need to set bits
14            if (num1 >> bit_position) & 1 and remaining_bits > 0:
15                # Set the bit at current position in x
16                x |= 1 << bit_position
17                remaining_bits -= 1
18      
19        # Second pass: If we still need more set bits, set them where num1 has 0s (from LSB to MSB)
20        # Setting bits where num1 has 0s increases XOR, so we do this from LSB for minimal increase
21        for bit_position in range(30):
22            # Check if current bit in num1 is 0 and we still need to set bits
23            if ((num1 >> bit_position) & 1) ^ 1 and remaining_bits > 0:
24                # Set the bit at current position in x
25                x |= 1 << bit_position
26                remaining_bits -= 1
27      
28        return x
29
1class Solution {
2    public int minimizeXor(int num1, int num2) {
3        // Count the number of set bits (1s) in num2
4        // The result must have the same number of set bits
5        int setBitsCount = Integer.bitCount(num2);
6      
7        // Initialize the result value
8        int result = 0;
9      
10        // Phase 1: Set bits from high to low where num1 has 1s
11        // This minimizes XOR by matching the most significant bits first
12        for (int bitPosition = 30; bitPosition >= 0 && setBitsCount > 0; bitPosition--) {
13            // Check if num1 has a 1 at the current bit position
14            if ((num1 >> bitPosition & 1) == 1) {
15                // Set this bit in the result to minimize XOR difference
16                result |= 1 << bitPosition;
17                setBitsCount--;
18            }
19        }
20      
21        // Phase 2: If we still need more set bits, add them from low to high
22        // Choose positions where num1 has 0s to minimize the XOR value
23        for (int bitPosition = 0; setBitsCount > 0; bitPosition++) {
24            // Check if num1 has a 0 at the current bit position
25            if ((num1 >> bitPosition & 1) == 0) {
26                // Set this bit in the result
27                result |= 1 << bitPosition;
28                setBitsCount--;
29            }
30        }
31      
32        return result;
33    }
34}
35
1class Solution {
2public:
3    int minimizeXor(int num1, int num2) {
4        // Count the number of set bits in num2
5        int bitsToSet = __builtin_popcount(num2);
6        int result = 0;
7      
8        // Phase 1: Set bits in result where num1 has set bits (from MSB to LSB)
9        // This minimizes XOR by matching as many significant bits as possible
10        for (int bitPos = 30; bitPos >= 0 && bitsToSet > 0; --bitPos) {
11            // Check if the bit at position bitPos is set in num1
12            if ((num1 >> bitPos) & 1) {
13                // Set the corresponding bit in result
14                result |= (1 << bitPos);
15                --bitsToSet;
16            }
17        }
18      
19        // Phase 2: If we still need to set more bits, set them where num1 has 0s
20        // Start from LSB to minimize the value increase
21        for (int bitPos = 0; bitsToSet > 0; ++bitPos) {
22            // Check if the bit at position bitPos is NOT set in num1
23            if (((num1 >> bitPos) & 1) == 0) {
24                // Set the corresponding bit in result
25                result |= (1 << bitPos);
26                --bitsToSet;
27            }
28        }
29      
30        return result;
31    }
32};
33
1/**
2 * Minimizes XOR between a number and num1 while maintaining the same number of set bits as num2
3 * @param num1 - The target number to minimize XOR with
4 * @param num2 - The number whose set bit count to match
5 * @returns The number x that minimizes XOR with num1 and has same number of set bits as num2
6 */
7function minimizeXor(num1: number, num2: number): number {
8    // Count the number of set bits (1s) in num2
9    let setBitCount: number = 0;
10    while (num2 > 0) {
11        // Clear the rightmost set bit using Brian Kernighan's algorithm
12        num2 &= num2 - 1;
13        setBitCount++;
14    }
15  
16    // Initialize the result number
17    let result: number = 0;
18  
19    // First pass: Set bits in result where num1 has set bits (from MSB to LSB)
20    // This minimizes XOR by matching as many 1s as possible
21    for (let bitPosition: number = 30; bitPosition >= 0 && setBitCount > 0; bitPosition--) {
22        // Check if the current bit position in num1 is set
23        if ((num1 >> bitPosition) & 1) {
24            // Set the same bit position in result
25            result |= 1 << bitPosition;
26            setBitCount--;
27        }
28    }
29  
30    // Second pass: If we still need more set bits, set them in positions where num1 has 0s
31    // Start from LSB to minimize the value while maintaining the required bit count
32    for (let bitPosition: number = 0; setBitCount > 0; bitPosition++) {
33        // Check if the current bit position in num1 is not set
34        if (!((num1 >> bitPosition) & 1)) {
35            // Set this bit position in result
36            result |= 1 << bitPosition;
37            setBitCount--;
38        }
39    }
40  
41    return result;
42}
43

Time and Space Complexity

The time complexity is O(log n), where n is the maximum value of num1 and num2. This is because:

  • The first loop iterates from bit position 30 down to 0, which is O(31) = O(1) iterations in terms of constant operations, but represents O(log n) in terms of the number of bits needed to represent the maximum value n
  • The second loop iterates from bit position 0 to 29, which similarly is O(30) = O(1) constant iterations, but represents O(log n) bit positions
  • The bit_count() operation on num2 also takes O(log n) time as it needs to count set bits in the binary representation
  • Each iteration performs constant time operations: bit shifting (>>), bitwise AND (&), bitwise OR (|), and bitwise XOR (^)

The space complexity is O(1) because:

  • Only a fixed number of integer variables are used: cnt and x
  • The loop variable i also uses constant space
  • No additional data structures that grow with input size are created
  • All operations are performed in-place using bitwise operations

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Bit Range or Off-by-One Errors

A common mistake is using an incorrect range for bit positions. Since the problem states positive integers and typical constraints go up to 10^9 (which requires 30 bits), using range(31) or range(32) might seem correct, but could lead to issues:

Pitfall Example:

# Incorrect: might check unnecessary bits
for i in range(31, -1, -1):  # Checking bit 31 when not needed
    if num1 >> i & 1 and cnt:
        x |= 1 << i
        cnt -= 1

Solution: Determine the actual bit range needed. For most problems with constraint up to 10^9, 30 bits suffice:

# Correct: Check only necessary bits (0-29 for numbers up to 10^9)
for i in range(30, -1, -1):
    if num1 >> i & 1 and cnt:
        x |= 1 << i
        cnt -= 1

2. Operator Precedence Confusion

Bitwise operators have lower precedence than comparison operators, which can cause unexpected behavior:

Pitfall Example:

# Incorrect: This evaluates as (num1 >> i) & (1 and cnt)
if num1 >> i & 1 and cnt:  # Works but unclear
    x |= 1 << i
  
# Even worse: forgetting parentheses in XOR check
if num1 >> i & 1 ^ 1 and cnt:  # XOR has lower precedence than &

Solution: Use parentheses for clarity and correctness:

# Clear and correct
if ((num1 >> i) & 1) and cnt > 0:
    x |= 1 << i
    cnt -= 1

# For checking if bit is 0
if ((num1 >> i) & 1) == 0 and cnt > 0:
    x |= 1 << i
    cnt -= 1

3. Not Handling Edge Cases

Failing to consider when num2 has more or fewer set bits than num1:

Pitfall Example:

# Might not work if num2.bit_count() > num1.bit_count()
# Only tries to match existing 1s in num1
for i in range(30, -1, -1):
    if num1 >> i & 1:
        x |= 1 << i
# Forgets to add remaining bits in 0 positions

Solution: Always implement both phases - matching 1s and adding to 0s:

cnt = num2.bit_count()
x = 0

# Phase 1: Match 1s in num1
for i in range(30, -1, -1):
    if (num1 >> i) & 1 and cnt > 0:
        x |= 1 << i
        cnt -= 1

# Phase 2: Add remaining bits to 0 positions
for i in range(30):
    if ((num1 >> i) & 1) == 0 and cnt > 0:
        x |= 1 << i
        cnt -= 1

4. Using Wrong Direction in Second Pass

Setting bits in the wrong order when adding to 0 positions:

Pitfall Example:

# Incorrect: Adding from MSB to LSB in second pass
for i in range(30, -1, -1):  # Wrong direction!
    if ((num1 >> i) & 1) == 0 and cnt > 0:
        x |= 1 << i
        cnt -= 1

Solution: Add remaining bits from LSB to MSB to minimize the XOR value:

# Correct: Add from LSB (0) to MSB (29)
for i in range(30):
    if ((num1 >> i) & 1) == 0 and cnt > 0:
        x |= 1 << i
        cnt -= 1

This ensures that any additional 1s contribute the smallest possible values to the XOR result.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Given a sorted array of integers and an integer called target, find the element that equals to the target and return its index. Select the correct code that fills the ___ in the given code snippet.

1def binary_search(arr, target):
2    left, right = 0, len(arr) - 1
3    while left ___ right:
4        mid = (left + right) // 2
5        if arr[mid] == target:
6            return mid
7        if arr[mid] < target:
8            ___ = mid + 1
9        else:
10            ___ = mid - 1
11    return -1
12
1public static int binarySearch(int[] arr, int target) {
2    int left = 0;
3    int right = arr.length - 1;
4
5    while (left ___ right) {
6        int mid = left + (right - left) / 2;
7        if (arr[mid] == target) return mid;
8        if (arr[mid] < target) {
9            ___ = mid + 1;
10        } else {
11            ___ = mid - 1;
12        }
13    }
14    return -1;
15}
16
1function binarySearch(arr, target) {
2    let left = 0;
3    let right = arr.length - 1;
4
5    while (left ___ right) {
6        let mid = left + Math.trunc((right - left) / 2);
7        if (arr[mid] == target) return mid;
8        if (arr[mid] < target) {
9            ___ = mid + 1;
10        } else {
11            ___ = mid - 1;
12        }
13    }
14    return -1;
15}
16

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More