LeetCode 421 Maximum XOR of Two Numbers in an Array(Med, Java, O(n))

Anni HuangAnni Huang
5 min read

Problem Description

Given an integer array nums, return the maximum result of nums[i] XOR nums[j], where 0 ≤ i ≤ j < n.

Example 1:

Input: nums = [3,10,5,25,2,8]
Output: 28
Explanation: The maximum result is 5 XOR 25 = 28.

Example 2:

Input: nums = [14,70,53,83,49,91,36,80,92,51,66,70]
Output: 127

Constraints:

  • 1 <= nums.length <= 2 * 10^5
  • 0 <= nums[i] <= 2^31 - 1

Key Insight: To maximize XOR, we need bits to be as different as possible, especially in higher-order positions since they contribute exponentially more value (2³¹ > sum of all lower bits).

Algorithm Walkthrough

Bit-by-Bit Greedy Construction with XOR Property Verification

This solution builds the maximum XOR result one bit at a time from most significant bit (MSB) to least significant bit (LSB), using mathematical XOR properties to verify if each bit can be set.

Step 1: Find the Highest Significant Bit

int max = 0;
for(var num: nums) max = Math.max(max, num);
if(max == 0) return 0;

int mask = 0x40000000;  // Start from bit 30
while((mask & max) == 0) mask >>= 1;

Purpose: Optimize by skipping irrelevant high-order zero bits.

  • Find the maximum number to determine the highest bit that matters
  • Start checking from bit 30 and shift right until we find the first significant bit
  • Example: If max = 25 (11001₂), we start from bit 4 instead of bit 30

Step 2: Greedy Bit-by-Bit Construction

int ret = 0;      // Final result
int masks = 0;    // Accumulates processed bit positions
while(mask != 0) {
    masks |= mask;                    // Include current bit in mask
    if(check(nums, masks, ret | mask)) // Can we set this bit?
        ret |= mask;                  // Yes! Set this bit to 1
    mask >>= 1;                       // Move to next lower bit
}

Core Logic:

  • masks: Tracks which bit positions we've already processed
  • ret | mask: Candidate result if we set current bit to 1
  • check(): Verifies if the candidate is achievable
  • Greedy choice: Set bit to 1 if possible, otherwise leave as 0

Step 3: XOR Property Verification

private boolean check(int[] nums, int masks, int ans) {
    Set<Integer> vis = HashSet.newHashSet(nums.length);
    for(int num : nums) {
        num &= masks;                    // Keep only processed bits
        if(vis.contains(num ^ ans))      // XOR property: a⊕b=c ↔ a⊕c=b
            return true;
        vis.add(num);
    }
    return false;
}

XOR Property Used: If a ⊕ b = target, then a ⊕ target = b

Logic:

  • For each masked number a, check if the required partner b = a ⊕ target exists
  • If such a pair exists, we can achieve the target XOR value
  • num &= masks ensures we only consider bits we've decided so far

Detailed Example:

Input: nums = [3, 10, 5, 25]

Binary representations:

3  = 0001110 = 010105  = 0010125 = 11001

Step 1: Find highest bit

  • max = 25, start from bit 30, find first significant bit at position 4

Step 2: Process each bit

Iteration 1 - Bit 4:

mask = 16 (10000₂), masks = 16, candidate = 16
check([3,10,5,25], 16, 16):
  Masked: [0,0,0,16]
  For 0: check if (016)=16 exists → Yes! (25 masked = 16)
  Return true → ret = 16

Iteration 2 - Bit 3:

mask = 8 (01000₂), masks = 24, candidate = 24  
check([3,10,5,25], 24, 24):
  Masked: [0,8,0,24]
  For 0: check if (024)=24 exists → Yes!
  Return true → ret = 24

Iteration 3 - Bit 2:

mask = 4 (00100₂), masks = 28, candidate = 28
check([3,10,5,25], 28, 28):
  Masked: [0,8,4,24]  
  For 4: check if (428)=24 exists → Yes!
  Return true → ret = 28

Iterations 4-5 - Bits 1,0:

  • Both return false, so bits remain 0

Final Result: 28

Complexity Analysis

Time Complexity: O(32n) = O(n)

  • At most 32 iterations (one per bit position in a 32-bit integer)
  • Each iteration processes all n numbers once
  • HashSet operations (contains, add) are O(1) average case
  • Total: 32 × n × O(1) = O(n)

Space Complexity: O(n)

  • HashSet in check() method stores at most n masked numbers
  • All other variables use constant space
  • No additional data structures or recursion stack needed

Key Optimizations:

  • Skip high-order zero bits by finding the maximum value first
  • Process only relevant bit positions
  • Use bit masking to reduce problem scope at each iteration

Full Solution (Java)

class Solution {
    public int findMaximumXOR(int[] nums) {
        // Step 1: Find maximum value to determine highest significant bit
        int max = 0;
        for(var num: nums)
            max = Math.max(max, num);
        if(max == 0) return 0;

        // Step 2: Find the highest bit position that matters
        int mask = 0x40000000;  // Start from bit 30 (2^30)
        while((mask & max) == 0) mask >>= 1;

        // Step 3: Build result bit by bit from MSB to LSB
        int ret = 0;      // Final result
        int masks = 0;    // Accumulator for processed bit positions
        while(mask != 0) {
            masks |= mask;                        // Include current bit
            if(check(nums, masks, ret | mask))    // Can we set this bit?
                ret |= mask;                      // Yes! Set bit to 1
            mask >>= 1;                           // Move to next bit
        }
        return ret;
    }

    /**
     * Check if target XOR value is achievable using XOR property:
     * If a ⊕ b = target, then a ⊕ target = b
     * 
     * @param nums Original array
     * @param masks Bit mask for positions processed so far  
     * @param ans Target XOR value to verify
     * @return true if target is achievable, false otherwise
     */
    private boolean check(int[] nums, int masks, int ans) {
        Set<Integer> vis = HashSet.newHashSet(nums.length);
        for(int num : nums) {
            num &= masks;                    // Keep only relevant bits
            if(vis.contains(num ^ ans))      // Check if partner exists
                return true;
            vis.add(num);
        }
        return false;
    }
}

Key Algorithm Properties:

  • Greedy Approach: Makes locally optimal choices (set bit to 1 when possible)
  • Mathematical Foundation: Leverages XOR properties instead of brute force
  • Optimal Complexity: Achieves O(n) time without building complex data structures
  • Bit-level Optimization: Processes only significant bit positions
  • Space Efficient: Uses temporary HashSet rather than persistent data structures

This solution elegantly demonstrates how mathematical properties can transform a seemingly quadratic problem into an optimal linear solution.

0
Subscribe to my newsletter

Read articles from Anni Huang directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Anni Huang
Anni Huang

I am Anni HUANG, a software engineer with 3 years of experience in IDE development and Chatbot.