Coding Interview Patterns Module 19 – Tries
Binary tries for maximum XOR
Find the largest XOR of two numbers in an array with a binary trie, one greedy walk of 30 bits per number, then answer XOR queries under a limit.
What you will learn
- Build a binary trie that stores integers bit by bit, the most significant bit first
- Find the maximum XOR of two numbers of an array in O(n × b) with a greedy walk
- Prove that taking the opposite bit at every level gives the largest XOR
- Answer XOR queries with a value limit, offline by sorting and online with subtree minimums
- Apply the trie to XORs of subarrays, the smallest XOR of a pair and counting pairs below a bound
Before you start
What you’ll learn · Before you start
What you will learn
- Build a binary trie that stores integers bit by bit, the most significant bit first
- Find the maximum XOR of two numbers of an array in O(n × b) with a greedy walk
- Prove that taking the opposite bit at every level gives the largest XOR
- Answer XOR queries with a value limit, offline by sorting and online with subtree minimums
- Apply the trie to XORs of subarrays, the smallest XOR of a pair and counting pairs below a bound
Before you start
On this page
A binary trie stores integers as paths of bits, the most significant bit first, so two numbers share a path for as long as their leading bits agree. It solves the maximum XOR of two numbers in an array in O(n × b) time for b-bit numbers: insert the numbers, and for each one walk down the trie, taking the opposite bit at every level where that child exists. The greedy choice works because a 1 in a higher bit of a XOR is worth more than all the lower bits together. For 30-bit numbers that is 30 steps per number, where checking every pair costs up to n − 1 XORs per number.
The problem: the most different pair of device IDs
The problem
A sensor network gives each of its devices a 30-bit ID. To test how its radios detect errors, an engineer wants
the two devices whose IDs differ most in the binary sense: the pair whose XOR is largest. Write max_xor(ids),
which returns the largest a ^ b over two devices at different positions of the list. Limits: 2 ≤ n ≤ 200,000 and
0 ≤ ID < 2³⁰; IDs may repeat.
[41, 18, 7, 33, 60, 12]gives 59, from 41 and 18: 101001 XOR 010010 = 111011.[5, 9, 12, 3]gives 15, from 12 and 3: 1100 XOR 0011 = 1111, the largest four-bit value.[7, 7]gives 0: two devices with the same ID differ nowhere.
At every level the walk takes the opposite bit when that child exists
Text description of the diagram
The diagram is a binary trie of the numbers 3, 5, 9 and 12, written with four bits each: 0011, 0101, 1001 and 1100. Each box is a node labelled with the bits on its path from the root, and each line is labelled with the bit it adds, 0 or 1.
- The root has two children: 0, the start of 0011 and 0101, and 1, the start of 1001 and 1100.
- Below 0 are 00 and 01; below 1 are 10 and 11. Each of these four has one child, and each of those one leaf: 001 then 0011 = 3, 010 then 0101 = 5, 100 then 1001 = 9, and 110 then 1100 = 12.
- The walk for the query 12 = 1100 is drawn with thick lines and shaded boxes. Its first bit is 1, so it wants the child 0, which exists. Its second bit is 1, so it wants 0 again, and 00 exists. Its third and fourth bits are 0, so it wants 1 twice, and 001 and 0011 exist.
Every bit the walk wanted was there, so all four bits of the XOR are 1: 1100 XOR 0011 = 1111 = 15, the largest XOR of 12 with any of the four numbers.
The brute force, and why it fails at these sizes
XOR every pair and keep the largest:
"""The brute force for the most different pair of device IDs: the XOR of every pair."""
def max_xor_brute(ids):
"""The largest a ^ b over pairs of different positions, the pair itself, and how many pairs were tried."""
best, pair, tried = -1, None, 0
for i in range(len(ids)):
for j in range(i + 1, len(ids)):
tried += 1
if ids[i] ^ ids[j] > best:
best, pair = ids[i] ^ ids[j], (ids[i], ids[j])
return best, pair, tried
if __name__ == "__main__":
for ids in ([41, 18, 7, 33, 60, 12], [5, 9, 12, 3], [7, 7]):
best, (a, b), tried = max_xor_brute(ids)
width = max(a, b, 1).bit_length()
print(f"{ids}")
print(f" {a} ^ {b} = {best}: {a:0{width}b} ^ {b:0{width}b} = {best:0{width}b}, {tried} pair{'s' * (tried != 1)} tried")
n = 200_000
print()
print(f"At the limits: {n:,} IDs make {n * (n - 1) // 2:,} pairs") Output
[41, 18, 7, 33, 60, 12] 41 ^ 18 = 59: 101001 ^ 010010 = 111011, 15 pairs tried [5, 9, 12, 3] 12 ^ 3 = 15: 1100 ^ 0011 = 1111, 6 pairs tried [7, 7] 7 ^ 7 = 0: 111 ^ 111 = 000, 1 pair tried At the limits: 200,000 IDs make 19,999,900,000 pairs
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 brute.py
Runs on this device, in your browser. The first run downloads Python (about 13.5 MB), which is kept for the next runs.
Your run, in this browser
It is O(n²): n(n − 1)/2 = 19,999,900,000 pairs at n = 200,000. At the 10⁸ simple steps a second that the constraints lesson plans with, compiled code needs more than three minutes, and Python far longer.
What to notice
A XOR is large when its high bits are 1, and the top bit decides on its own: bit 29 is worth 2²⁹, more than 2²⁹ − 1, the sum of all the bits below it. So the best partner for a number differs from it as early as possible: first a partner whose top bit differs, then, among those, one whose next bit differs too, and so on. Numbers that share their top bits share a path in a binary trie, so each of those questions is a single step down it.
The trie and the greedy walk, step by step Pro: opens with a Pro, Premium or Ultimate pass
In this part:
- Why the opposite bit is always the right choice
- A dry run on 12 and on 5
- The code in five languages
- Speed and memory, measured
- Queries with a value limit, offline and online
- Variants: subarrays, the smallest XOR and pairs below a bound
- Edge cases and common bugs
- Exercise
- The largest XOR with a number under a limit
- A practice set with sample answers
- Check yourself
- Interview questions
- Why does the walk for the largest XOR take the opposite bit at the top level even when that branch has poor lower bits?
- Queries (x, m) arrive one at a time and must be answered at once: the largest x ^ y over stored y with y ≤ m, or −1. Numbers may also be added between queries. Sorting the queries is impossible. How do you answer each one in O(bits), and why is your walk never stuck?
- Given n ≤ 2 × 10⁵ values below 2³⁰, find the largest XOR of a run of consecutive values. Explain why checking every run is too slow, how prefix XORs turn the question into a pair question, and what the trie then costs.
- A service keeps a binary trie of up to 10 million 32-bit numbers to answer largest-XOR queries, and may use 256 MB. Estimate the trie's nodes and bytes, decide whether it fits, and describe what you would change if it does not.
- Key takeaways
7 runnable examples (Build and run all three, C++, Go, Java, JavaScript, Python) · 1 exercise with sample tests · 3 interview answers · about 27 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
References
- Bitwise operations on integer types (The Python Standard Library) (Python Software Foundation)
- int.bit_length (The Python Standard Library) (Python Software Foundation)
- array: efficient arrays of numeric values (The Python Standard Library) (Python Software Foundation)
- ECMAScript language specification: the signed right shift operator (>>) (Ecma International (TC39))
- The Java Language Specification, Java SE 25: 15.19 Shift Operators (Oracle)
- Bitwise shift operators (cppreference.com) (cppreference.com)
- The Go Programming Language Specification: Arithmetic operators (The Go Authors)
- trie (Dictionary of Algorithms and Data Structures) (National Institute of Standards and Technology)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress