Maximum XOR of Two Numbers in an Array (LeetCode 421)
Given an integer array nums, find the largest value obtainable by XOR-ing any two elements nums[i] and nums[j] (the same index may be reused, i.e. 0 <= i <= j < n).
Worked Examples
nums = [3,10,5,25,2,8]28nums = [14,70,53,83,49,91,36,80,92,51,66,70]12791 XOR 36 = 127, and no other pair does better.⚖️Formal Constraints & Bounds
1 <= nums.length <= 2 * 1050 <= nums[i] <= 231 - 1
Core Insight
Build the answer bit-by-bit from the most significant bit down. At each bit position, greedily assume the answer can have that bit set, then verify by checking whether two numbers' prefixes-so-far actually XOR to that candidate (via a hash set of prefixes or a walked bit-trie). A greedy bet that isn't provably achievable is simply skipped, so the final result is guaranteed maximal in O(32*N) time.
Real-World Scenario & Production Applications
Finding the two most dissimilar hash fingerprints in a deduplication index, maximizing Hamming distance between error-correcting codewords, and IP-routing longest-prefix-divergence lookups.
Subproblems & Recurrence Decomposition3 Phases
Build the answer one bit position at a time, from the most significant bit (31) down to the least significant bit (0), growing a mask that exposes longer and longer prefixes of every number.
max_xor, mask = 0, 0
for i in range(31, -1, -1):
mask |= (1 << i)Step-by-Step Execution Trace Table
Worked trace: nums = [3, 10, 5, 25, 2, 8]
Bit i | mask | Prefix set (masked nums) | candidate = max_xor \vert (1<<i) | Verified? | max_xor after |
|---|---|---|---|---|---|
| 4 | 10000 (16) | {0, 16} | 16 | Yes (0 ^ 16 = 16) | 16 |
| 3 | 11000 (24) | {0, 8, 24} | 24 | Yes (0 ^ 24 = 24) | 24 |
| 2 | 11100 (28) | {0, 4, 8, 24} | 28 | Yes (4 ^ 24 = 28) | 28 |
| 1 | 11110 (30) | {2, 4, 8, 10, 24} | 30 | No | 28 |
| 0 | 11111 (31) | {2, 3, 5, 8, 10, 25} | 29 | No | 28 |
Final max_xor = 28, achieved by 5 XOR 25 = 00101 ^ 11001 = 11100 = 28, matching the expected output.
| 1 | max_xor, mask = 0, 0 |
| 2 | for i in range(31, -1, -1): |
| 3 | mask |= (1 << i) |
| 4 | prefixes = {num & mask for num in nums} |
| 5 | candidate = max_xor | (1 << i) |
| 6 | for p in prefixes: |
| 7 | if (candidate ^ p) in prefixes: |
| 8 | max_xor = candidate |
| 9 | break |
| 10 | return max_xor |
Conceptual Narrative
🧭 Conceptual Foundation & Pattern Intuition
Every earlier bit-manipulation archetype processed ALL 32 bits of ONE number at a time. This archetype reverses that: it processes ONE bit position at a time, across ALL numbers at once, building the answer from its most significant bit down to its least significant bit — greedily locking in every bit it can prove is achievable.
🏔️ The Analogy: Building the Tallest Answer One Floor at a Time, Verified Before You Commit
Imagine constructing a skyscraper's blueprint one floor at a time, starting from the top (bit 31) down to the ground floor (bit 0). At each floor, you ask a greedy question: "Can I add a floor here and still have this building be physically buildable with the materials (numbers) I have?" You never build a floor speculatively and tear it down later — you only ever add a floor once you've proven two of your numbers share every floor built so far except this one, where they differ.
- Trick 1 — Growing prefixes (
mask |= (1 << i)): The mask exposes just the top(32 - i)bits of every number — everything built "so far", floor by floor. - Trick 2 — Prefix set (
prefixes = {num & mask for num in nums}): Recompute every number's current prefix under the new, slightly-longer mask. - Trick 3 — The greedy bet (
candidate = max_xor | (1 << i)): Assume you CAN set this floor's bit to1in the final answer. - Trick 4 — Verify with the XOR-inverse trick (
(candidate ^ p) in prefixes): If some prefixpis in the set, andcandidate ^ pis ALSO in the set, then those two prefixes XOR together to exactlycandidate— the bet is provably achievable, somax_xorpermanently locks in that bit. If no such pair exists, the floor can't be built here, andmax_xorkeeps its bit at0for this position — never regressing, only growing.
🔑 The Key Idea in the Code
This is the hash-set version of a bit-trie walk: instead of building 32 levels of trie nodes and walking root-to-leaf per number, you replay the same greedy "does a complementary prefix exist" check using plain sets, in O(32·N) time and O(N) space.
Quadratic brute-force pair comparison: Comparing every pair of numbers takes time, which TLEs on . A Binary Trie or prefix hash set greedy bit evaluation achieves time.
Prefix set bit search property: At each bit position from bit 30 down to 0, test if a candidate maximum prefix can be formed: if
candidate ^ pexists in the prefix set,p1 ^ p2 == candidateis guaranteed.Iterating LSB to MSB instead of MSB to LSB: Greedy XOR maximization must make decisions from most significant bit (bit 30) down to least significant bit (bit 0), because a higher bit outweighs all lower bits combined.
Maximum XOR of Two Numbers — rebuilding the WHOLE prefix set every bit, not incrementally: the mask only ever grows (bits are added, never removed), so
prefixes = {num & mask for num in nums}must be recomputed fresh each iteration from the ORIGINAL numbers, not from the previous iteration's already-masked prefixes — masking an already-masked value with a larger mask is a no-op and silently breaks the invariant.Maximum XOR of Two Numbers — checking
candidate in prefixesinstead of the XOR-inverse: the verification step is NOT "is candidate itself a prefix" — it is "does some prefixphave its partnercandidate ^ palso present". Checking membership ofcandidatedirectly (rather thancandidate ^ p) looks superficially similar but never actually confirms two DIFFERENT numbers combine to the candidate value.Maximum XOR of Two Numbers — pairing a number with itself: the loop bounds
0 <= i <= j < ntechnically allowi == j, butx XOR x = 0is never the maximum unless every element is identical; a trie/hash-set implementation naturally handles this since a number's own prefix is always present in the set, but a hand-rolled O(N^2) baseline must remember not to special-case away legitimatei == jin a way that also breaksi != jpairs.
4-Phase Thought Process Model
Pattern Recognition Signals
The 10-second spot
Maximum XOR of any pair in an array, O(N) or O(N log(max_value)) time requirement, N up to 2 * 10^5 ruling out O(N^2).
Formulating the Predicate & Invariants
Turning intuition into a boolean rule
Invariant: if two numbers' prefixes under the current mask differ only in the newly-exposed bit, their XOR of that prefix equals the greedy candidate, proving the bit is achievable.
Silent Failure Traps & Edge Cases
Where confident candidates still lose points
Checking all O(N^2) pairs directly: too slow for N up to 2*10^5; must process bit positions, not pairs.
Forgetting that
maskmust grow monotonically (MSB to LSB) so earlier, more significant bit decisions are never revisited or invalidated by later ones.Comparing full untruncated numbers rather than their masked prefixes, which breaks the greedy proof.
The 60-Second Interview Pitch
Say this out loud before you type a single line
"I build the answer bit-by-bit from bit 31 down to bit 0. At each position I grow a mask to expose one more bit of every number's prefix, collect those prefixes into a set, and greedily assume the answer's bit here can be 1. I verify by checking whether some prefix p in the set has its XOR-partner (candidate ^ p) also in the set -- if so the bit is provably achievable and I lock it in. This avoids ever comparing all pairs directly, running in O(32*N) =
O(N)time andO(N)space. The same idea can be implemented as a walked bit-trie instead of a hash set."
Complexity & Mathematical Proof
O(N)
Look at the execution steps: For each of the 32 bit positions, rebuild the prefix set (O(N)) and scan it once (O(N)) to verify the greedy bet. Total work is 32 * O(N) = O(32N), which is O(N) since 32 is a fixed constant.
O(N) Auxiliary
Look at memory allocations: Call stack uses O(1) Iterative. Stores one prefix set of up to N integers per bit position (reused, not accumulated) in O(N) auxiliary space.
T(N) = 32 · O(N) = O(N)
Derivation Progression
Rebuilding the prefix set for one bit position costs O(N).
Scanning the prefix set to verify the greedy bet costs O(N) per bit position.
32 is a fixed constant (not dependent on N), so total time remains linear in N.
Variable Definitions
Total number of elements in the array
Fixed bit width of a 32-bit integer (constant, not a function of N)
Memory Architecture & Bounds
O(1) Iterative
O(N) One prefix set of up to N integers per bit position
O(1) Single integer result
Boundary Best / Worst Cases
Recurrence Tree Topology
Senior SWE Deconstruction & Hardware Caveats
The problem asks for the "maximum XOR of two numbers in an array in O(n) runtime." Comparing all pairs takes , which TLEs for . The key insight is that the most significant bit (MSB) dominates all lower bits combined: . Therefore, we can greedily determine the optimal XOR value bit-by-bit from MSB (bit 30) down to LSB (bit 0)!
, values fit in 31 non-negative bits (). An algorithm is required.
The primary trap is scanning LSB to MSB; greedy choice only works MSB to LSB because a higher bit outweighs all lower bits. Another trap is rebuilding prefixes naively without masking: mask |= (1 << i) must accumulate prefixes from bit 30 down to .
Maximum XOR of Two Numbers in an Array (LeetCode 421)
Given an integer array nums, find the largest value obtainable by XOR-ing any two elements nums[i] and nums[j] (the same index may be reused, i.e. 0 <= i <= j < n).
Worked Examples
nums = [3,10,5,25,2,8]28nums = [14,70,53,83,49,91,36,80,92,51,66,70]12791 XOR 36 = 127, and no other pair does better.⚖️Formal Constraints & Bounds
1 <= nums.length <= 2 * 1050 <= nums[i] <= 231 - 1
Core Insight
Build the answer bit-by-bit from the most significant bit down. At each bit position, greedily assume the answer can have that bit set, then verify by checking whether two numbers' prefixes-so-far actually XOR to that candidate (via a hash set of prefixes or a walked bit-trie). A greedy bet that isn't provably achievable is simply skipped, so the final result is guaranteed maximal in O(32*N) time.
Real-World Scenario & Production Applications
Finding the two most dissimilar hash fingerprints in a deduplication index, maximizing Hamming distance between error-correcting codewords, and IP-routing longest-prefix-divergence lookups.
Subproblems & Recurrence Decomposition3 Phases
Build the answer one bit position at a time, from the most significant bit (31) down to the least significant bit (0), growing a mask that exposes longer and longer prefixes of every number.
max_xor, mask = 0, 0
for i in range(31, -1, -1):
mask |= (1 << i)Step-by-Step Execution Trace Table
Worked trace: nums = [3, 10, 5, 25, 2, 8]
Bit i | mask | Prefix set (masked nums) | candidate = max_xor \vert (1<<i) | Verified? | max_xor after |
|---|---|---|---|---|---|
| 4 | 10000 (16) | {0, 16} | 16 | Yes (0 ^ 16 = 16) | 16 |
| 3 | 11000 (24) | {0, 8, 24} | 24 | Yes (0 ^ 24 = 24) | 24 |
| 2 | 11100 (28) | {0, 4, 8, 24} | 28 | Yes (4 ^ 24 = 28) | 28 |
| 1 | 11110 (30) | {2, 4, 8, 10, 24} | 30 | No | 28 |
| 0 | 11111 (31) | {2, 3, 5, 8, 10, 25} | 29 | No | 28 |
Final max_xor = 28, achieved by 5 XOR 25 = 00101 ^ 11001 = 11100 = 28, matching the expected output.
| 1 | max_xor, mask = 0, 0 |
| 2 | for i in range(31, -1, -1): |
| 3 | mask |= (1 << i) |
| 4 | prefixes = {num & mask for num in nums} |
| 5 | candidate = max_xor | (1 << i) |
| 6 | for p in prefixes: |
| 7 | if (candidate ^ p) in prefixes: |
| 8 | max_xor = candidate |
| 9 | break |
| 10 | return max_xor |
Conceptual Narrative
🧭 Conceptual Foundation & Pattern Intuition
Every earlier bit-manipulation archetype processed ALL 32 bits of ONE number at a time. This archetype reverses that: it processes ONE bit position at a time, across ALL numbers at once, building the answer from its most significant bit down to its least significant bit — greedily locking in every bit it can prove is achievable.
🏔️ The Analogy: Building the Tallest Answer One Floor at a Time, Verified Before You Commit
Imagine constructing a skyscraper's blueprint one floor at a time, starting from the top (bit 31) down to the ground floor (bit 0). At each floor, you ask a greedy question: "Can I add a floor here and still have this building be physically buildable with the materials (numbers) I have?" You never build a floor speculatively and tear it down later — you only ever add a floor once you've proven two of your numbers share every floor built so far except this one, where they differ.
- Trick 1 — Growing prefixes (
mask |= (1 << i)): The mask exposes just the top(32 - i)bits of every number — everything built "so far", floor by floor. - Trick 2 — Prefix set (
prefixes = {num & mask for num in nums}): Recompute every number's current prefix under the new, slightly-longer mask. - Trick 3 — The greedy bet (
candidate = max_xor | (1 << i)): Assume you CAN set this floor's bit to1in the final answer. - Trick 4 — Verify with the XOR-inverse trick (
(candidate ^ p) in prefixes): If some prefixpis in the set, andcandidate ^ pis ALSO in the set, then those two prefixes XOR together to exactlycandidate— the bet is provably achievable, somax_xorpermanently locks in that bit. If no such pair exists, the floor can't be built here, andmax_xorkeeps its bit at0for this position — never regressing, only growing.
🔑 The Key Idea in the Code
This is the hash-set version of a bit-trie walk: instead of building 32 levels of trie nodes and walking root-to-leaf per number, you replay the same greedy "does a complementary prefix exist" check using plain sets, in O(32·N) time and O(N) space.
Quadratic brute-force pair comparison: Comparing every pair of numbers takes time, which TLEs on . A Binary Trie or prefix hash set greedy bit evaluation achieves time.
Prefix set bit search property: At each bit position from bit 30 down to 0, test if a candidate maximum prefix can be formed: if
candidate ^ pexists in the prefix set,p1 ^ p2 == candidateis guaranteed.Iterating LSB to MSB instead of MSB to LSB: Greedy XOR maximization must make decisions from most significant bit (bit 30) down to least significant bit (bit 0), because a higher bit outweighs all lower bits combined.
Maximum XOR of Two Numbers — rebuilding the WHOLE prefix set every bit, not incrementally: the mask only ever grows (bits are added, never removed), so
prefixes = {num & mask for num in nums}must be recomputed fresh each iteration from the ORIGINAL numbers, not from the previous iteration's already-masked prefixes — masking an already-masked value with a larger mask is a no-op and silently breaks the invariant.Maximum XOR of Two Numbers — checking
candidate in prefixesinstead of the XOR-inverse: the verification step is NOT "is candidate itself a prefix" — it is "does some prefixphave its partnercandidate ^ palso present". Checking membership ofcandidatedirectly (rather thancandidate ^ p) looks superficially similar but never actually confirms two DIFFERENT numbers combine to the candidate value.Maximum XOR of Two Numbers — pairing a number with itself: the loop bounds
0 <= i <= j < ntechnically allowi == j, butx XOR x = 0is never the maximum unless every element is identical; a trie/hash-set implementation naturally handles this since a number's own prefix is always present in the set, but a hand-rolled O(N^2) baseline must remember not to special-case away legitimatei == jin a way that also breaksi != jpairs.
4-Phase Thought Process Model
Pattern Recognition Signals
The 10-second spot
Maximum XOR of any pair in an array, O(N) or O(N log(max_value)) time requirement, N up to 2 * 10^5 ruling out O(N^2).
Formulating the Predicate & Invariants
Turning intuition into a boolean rule
Invariant: if two numbers' prefixes under the current mask differ only in the newly-exposed bit, their XOR of that prefix equals the greedy candidate, proving the bit is achievable.
Silent Failure Traps & Edge Cases
Where confident candidates still lose points
Checking all O(N^2) pairs directly: too slow for N up to 2*10^5; must process bit positions, not pairs.
Forgetting that
maskmust grow monotonically (MSB to LSB) so earlier, more significant bit decisions are never revisited or invalidated by later ones.Comparing full untruncated numbers rather than their masked prefixes, which breaks the greedy proof.
The 60-Second Interview Pitch
Say this out loud before you type a single line
"I build the answer bit-by-bit from bit 31 down to bit 0. At each position I grow a mask to expose one more bit of every number's prefix, collect those prefixes into a set, and greedily assume the answer's bit here can be 1. I verify by checking whether some prefix p in the set has its XOR-partner (candidate ^ p) also in the set -- if so the bit is provably achievable and I lock it in. This avoids ever comparing all pairs directly, running in O(32*N) =
O(N)time andO(N)space. The same idea can be implemented as a walked bit-trie instead of a hash set."
Complexity & Mathematical Proof
O(N)
Look at the execution steps: For each of the 32 bit positions, rebuild the prefix set (O(N)) and scan it once (O(N)) to verify the greedy bet. Total work is 32 * O(N) = O(32N), which is O(N) since 32 is a fixed constant.
O(N) Auxiliary
Look at memory allocations: Call stack uses O(1) Iterative. Stores one prefix set of up to N integers per bit position (reused, not accumulated) in O(N) auxiliary space.
T(N) = 32 · O(N) = O(N)
Derivation Progression
Rebuilding the prefix set for one bit position costs O(N).
Scanning the prefix set to verify the greedy bet costs O(N) per bit position.
32 is a fixed constant (not dependent on N), so total time remains linear in N.
Variable Definitions
Total number of elements in the array
Fixed bit width of a 32-bit integer (constant, not a function of N)
Memory Architecture & Bounds
O(1) Iterative
O(N) One prefix set of up to N integers per bit position
O(1) Single integer result
Boundary Best / Worst Cases
Recurrence Tree Topology
Senior SWE Deconstruction & Hardware Caveats
The problem asks for the "maximum XOR of two numbers in an array in O(n) runtime." Comparing all pairs takes , which TLEs for . The key insight is that the most significant bit (MSB) dominates all lower bits combined: . Therefore, we can greedily determine the optimal XOR value bit-by-bit from MSB (bit 30) down to LSB (bit 0)!
, values fit in 31 non-negative bits (). An algorithm is required.
The primary trap is scanning LSB to MSB; greedy choice only works MSB to LSB because a higher bit outweighs all lower bits. Another trap is rebuilding prefixes naively without masking: mask |= (1 << i) must accumulate prefixes from bit 30 down to .