CMD Guide
HomeDSABit Manipulation

medium Maximum XOR of Two Numbers in an Array

Maximum XOR of Two Numbers in an Array

Problem. Given an array nums of non-negative integers, return the maximum value of nums[i] ^ nums[j] over all pairs (i may equal j, though the max never comes from a number XOR-ed with itself — that is 0). For nums = [3, 10, 5, 25] the answer is 28, from 5 ^ 25. (LeetCode 421.)

This is the module’s payoff page: it fuses two ideas — XOR (from the XOR deep-dive) and a binary trie — into a greedy that beats the obvious quadratic scan. The whole trick rests on one fact about XOR: 1 ^ 0 = 1 but 1 ^ 1 = 0 ^ 0 = 0. So to make a high bit of the XOR equal to 1, the two numbers must differ at that bit — and because a higher bit outweighs every lower bit combined, you should chase differences from the top down. That is a greedy, and a trie is the data structure that answers “does a number differing from me at this bit exist?” in O(1) per bit.

The naive baseline & its cost

Try every pair: two nested loops, compute nums[i] ^ nums[j], keep the max. Correct and five lines — but O(n2) time. LeetCode allows n up to 2 × 105, so n2 = 4 × 1010 — far too slow. We need to drop the n2.

The bit insight — greedy from the most significant bit

Fix the number of bits L (here L = 32 for a 32-bit int). Build the answer one bit at a time, from bit L−1 down to bit 0. At each bit you want the XOR to be 1 if possible, because setting a higher bit is worth more than anything you could gain in all the lower bits combined (2k > 2k−1 + … + 20). To make bit k of some pair’s XOR equal 1, you need two numbers whose bit k differs.

A binary trie makes this searchable. Insert every number as a root-to-leaf path of L bits, MSB first — child 0 or child 1 at each level. Then, for each number x, walk the trie from the root: at each bit prefer the child labelled with the opposite of x’s bit (that edge means “a stored number differs here” → this XOR bit can be 1). If that opposite child exists, take it and set the bit in the running answer; otherwise you are forced down the same-bit child and that XOR bit is 0. Do this for every x and keep the best. Because the number itself is in the trie, the forced branch always exists — no dead ends.

Traced example — nums = [3, 10, 5, 25]

Five bits suffice (25 = 11001):

valuebit4 (16)bit3 (8)bit2 (4)bit1 (2)bit0 (1)
300011
1001010
500101
2511001

All four are inserted into the trie. Now query with x = 5 (00101), walking MSB→LSB and preferring the opposite bit. “Available?” asks whether any stored number shares the prefix chosen so far and has the desired bit next:

bitx’s bitwant (opposite)available?partner bit takenXOR bitrunning XOR
4 (16)01yes (25)1116
3 (8)01yes (25: 11…)1124
2 (4)10yes (25: 110…)0128
1 (2)01no (only 25 in this branch, its bit1=0)0028
0 (1)10no (25’s bit0=1)1028

The greedy locks onto partner 25 and reports 5 ^ 25 = 28 (00101 ^ 11001 = 11100). Querying the other numbers never beats it (e.g. x = 25 symmetrically finds 5 and also yields 28), so the global maximum is 28 — matching the brute-force check over all six pairs.

Java: bit-trie greedy

public class MaximumXor {
    static class TrieNode {
        TrieNode[] child = new TrieNode[2];   // child[0], child[1]
    }

    public int findMaximumXOR(int[] nums) {
        TrieNode root = new TrieNode();
        final int HIGH = 31;                  // bits 31..0 of a 32-bit int

        // Insert every number, most-significant bit first.
        for (int num : nums) {
            TrieNode node = root;
            for (int b = HIGH; b >= 0; b--) {
                int bit = (num >> b) & 1;
                if (node.child[bit] == null) node.child[bit] = new TrieNode();
                node = node.child[bit];
            }
        }

        int best = 0;
        // For each number, greedily prefer the opposite bit to maximize the XOR.
        for (int num : nums) {
            TrieNode node = root;
            int cur = 0;
            for (int b = HIGH; b >= 0; b--) {
                int bit  = (num >> b) & 1;
                int want = bit ^ 1;           // opposite bit -> XOR bit of 1
                if (node.child[want] != null) {
                    cur |= (1 << b);         // we can set this XOR bit
                    node = node.child[want];
                } else {
                    node = node.child[bit];   // forced: same bit -> XOR bit 0
                }
            }
            best = Math.max(best, cur);
        }
        return best;
    }

    public static void main(String[] args) {
        MaximumXor s = new MaximumXor();
        System.out.println(s.findMaximumXOR(new int[]{3, 10, 5, 25}));       // 28
        System.out.println(s.findMaximumXOR(new int[]{3, 10, 5, 25, 2, 8})); // 28
        System.out.println(s.findMaximumXOR(new int[]{0}));                  // 0
    }
}

Go: bit-trie greedy

package main

import "fmt"

type trieNode struct {
	child [2]*trieNode // child[0], child[1]
}

func findMaximumXOR(nums []int) int {
	root := &trieNode{}
	const high = 31 // bits 31..0 of a 32-bit value

	// Insert every number, most-significant bit first.
	for _, num := range nums {
		node := root
		for b := high; b >= 0; b-- {
			bit := (num >> b) & 1
			if node.child[bit] == nil {
				node.child[bit] = &trieNode{}
			}
			node = node.child[bit]
		}
	}

	best := 0
	for _, num := range nums {
		node := root
		cur := 0
		for b := high; b >= 0; b-- {
			bit := (num >> b) & 1
			want := bit ^ 1 // opposite bit -> XOR bit of 1
			if node.child[want] != nil {
				cur |= 1 << b // we can set this XOR bit
				node = node.child[want]
			} else {
				node = node.child[bit] // forced: same bit -> XOR bit 0
			}
		}
		if cur > best {
			best = cur
		}
	}
	return best
}

func main() {
	fmt.Println(findMaximumXOR([]int{3, 10, 5, 25}))       // 28
	fmt.Println(findMaximumXOR([]int{3, 10, 5, 25, 2, 8})) // 28
	fmt.Println(findMaximumXOR([]int{0}))                  // 0
}

Why the greedy is optimal (not just a heuristic)

An exchange argument. Suppose the true maximum XOR has its highest 1 at bit k. The greedy processes bits top-down, so it reaches bit k with the same prefix (all higher XOR bits were 0 for the optimum, and the greedy also could not have set them — no pair differs there, or it would have set an even higher bit). At bit k the optimum differs, so some stored number provides the opposite bit; the trie’s opposite child therefore exists and the greedy takes it, setting bit k. Any XOR with a 1 at bit k already exceeds every XOR whose top 1 is below k (place value 2k dominates the sum of all lower bits), so fixing the highest attainable bit first can never be a mistake. Induct down the remaining bits within the branch. The greedy’s result is thus ≥ the optimum, hence equal.

Complexity

Judgment layer — when the trie is worth it

Edge cases & traps

Takeaways


Maximum-XOR (LeetCode 421) developed from the XOR place-value greedy and the binary-trie structure, with an exchange-argument optimality proof; all samples were compiled and run, and the answer cross-checked against brute force over all pairs.

🧩 Pattern · Bit Manipulation

Recognize it: Presence / pairing / parity with XOR and masks → think in bits.

→ more Bit Manipulation problems, grouped
🤖 Don't fully get this? Learn it with Claude

Stuck on Maximum XOR of Two Numbers in an Array? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🪜 Hint ladder (no spoilers)

Progressively stronger hints — you still solve it.

I'm working on the problem **Maximum XOR of Two Numbers in an Array** (DSA). Give me a HINT LADDER: start with the tiniest nudge, then wait. Only reveal the next, stronger hint when I ask. Do NOT show the full solution unless I type 'show solution'. Keep me doing the thinking. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🎨 Explain the approach visually

See the technique, not just code.

Explain the optimal approach to **Maximum XOR of Two Numbers in an Array** with a VISUAL walkthrough: trace it on a small concrete example using ASCII art / a step-by-step diagram, narrate what changes each step, then give time & space complexity with a one-line derivation. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🔍 Review my solution

Catch bugs, edge cases, sub-optimality.

I'll paste my solution to **Maximum XOR of Two Numbers in an Array**. Review it for correctness, missed edge cases, and time/space complexity, then coach me toward the optimal — don't just rewrite it. Ask me to paste my code now. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🔁 Drill the pattern

Lock in recognition with look-alikes.

Give me 2 problems that use the SAME underlying pattern as **Maximum XOR of Two Numbers in an Array**. For each, let me attempt first, then review my answer and name the trigger signal that reveals the pattern. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes