Tries · Trie Fundamentals

Binary Trie Max XOR

Storing each number's fixed-width binary representation as a root-to-leaf path in a 2-way (bit) trie, then for any query number, greedily walking toward the OPPOSITE bit at every level whenever that branch exists - since XOR-ing two differing bits produces a 1, greedily maximizing differing bits at the most significant positions first maximizes the resulting XOR value.

  • 5 min read
  • 7 practice questions
  • 2 graded problems
On this page
  1. Description
  2. The approach
  3. A second flavor: queries with a limit, answered offline
  4. Where you'll see it
  5. Which tool for which job?
  6. Common mistakes
  7. Check yourself
  8. Key takeaways
  9. Quick revise
  10. Practice

Description

The largest XOR of two numbers in an array: all pairs is O(n^2). XOR rewards differing bits, and the highest bit matters most - a 1 at bit i beats any combination of lower bits. Storing numbers bit by bit in a trie lets a greedy walk pick, for each number, the partner that differs at the highest possible bit.

This skill covers the binary trie: fixed-width bits inserted from the top, and a greedy walk that takes the opposite bit whenever it exists. It also covers answering queries that come with a limit offline, by sorting the queries and inserting values as the limit grows.

We'll go from simple to more technical:

  1. The pattern: maximum XOR of two numbers with a bit trie and a greedy walk.
  2. A second flavor: queries with a limit, answered offline in sorted order.
  3. Where bit tries run (IP routing, Kademlia's XOR distance).
  4. What breaks it: inconsistent widths, too few bits, online order, empty tries.

The approach

Java

static final int HIGH_BIT = 29;                       // values < 2^30: bits 29..0

void insert(int num) {
    BitNode node = root;
    for (int b = HIGH_BIT; b >= 0; b--) {             // most significant bit first
        int bit = (num >> b) & 1;
        if (node.next[bit] == null) node.next[bit] = new BitNode();
        node = node.next[bit];
    }
}

int maxXorWith(int num) {
    BitNode node = root;
    int result = 0;
    for (int b = HIGH_BIT; b >= 0; b--) {
        int bit = (num >> b) & 1;
        if (node.next[1 - bit] != null) {             // the opposite bit gives a 1 here
            result |= 1 << b;
            node = node.next[1 - bit];
        } else {
            node = node.next[bit];
        }
    }
    return result;
}

Python

HIGH_BIT = 29                                      # values < 2^30: bits 29..0


def insert(num: int) -> None:
    node = root
    for b in range(HIGH_BIT, -1, -1):              # most significant bit first
        bit = (num >> b) & 1
        if node.next[bit] is None:
            node.next[bit] = BitNode()
        node = node.next[bit]


def max_xor_with(num: int) -> int:
    node = root
    result = 0
    for b in range(HIGH_BIT, -1, -1):
        bit = (num >> b) & 1
        if node.next[1 - bit] is not None:         # the opposite bit gives a 1 here
            result |= 1 << b
            node = node.next[1 - bit]
        else:
            node = node.next[bit]
    return result

Insert every number, then take the best maxXorWith over all of them: O(n x bits). The greedy is exact because 2^b is larger than the sum of all lower bits (2^b - 1), so winning the highest possible bit is always right. Every number must be inserted with the same width, starting at the highest bit any value can have - for values below 2^30 that's bit 29. Starting too low (say bit 15) silently drops the high bits of large values.

A second flavor: queries with a limit, answered offline

"For each query (x, m), the maximum of x XOR v over array values v <= m." Rebuilding the trie per query is O(n) each. Instead, sort the values, sort the queries by m, and insert values into the trie only as the limit grows:

Java

Arrays.sort(values);
Integer[] order = /* query indices sorted by m */;
int p = 0;
for (int qi : order) {
    while (p < values.length && values[p] <= queries[qi][1]) insert(values[p++]);   // everything allowed so far
    answers[qi] = (p == 0) ? -1 : maxXorWith(queries[qi][0]);                       // nothing <= m: -1
}

Python

values.sort()
order = sorted(range(len(queries)), key=lambda i: queries[i][1])   # query indices sorted by m
p = 0
for qi in order:
    x, m = queries[qi]
    while p < len(values) and values[p] <= m:
        insert(values[p])                          # everything allowed so far
        p += 1
    answers[qi] = -1 if p == 0 else max_xor_with(x)   # nothing <= m: -1

When query qi is answered, the trie contains exactly the values <= its limit. Processing queries in input order breaks that: a large limit inserts values that a later query with a small limit must not see. Answers are written back by the original index.

Where you'll see it

IP routers store network prefixes in binary tries (Patricia tries) and walk destination addresses bit by bit; XOR-distance lookups in the Kademlia DHT (used by BitTorrent's peer discovery) organize peers by the length of the shared bit prefix, the same structure.

Which tool for which job?

You needUse
The max XOR of two numbersa bit trie and a greedy walk
The same without a triebit-by-bit prefix hash sets
The max XOR of any subarrayprefix XORs inserted into a bit trie
How many pairs have XOR below ka bit trie with subtree counts
Queries with a limit on the valuesoffline: sort queries and values, insert as the limit grows
The max XOR of any subseta linear basis over GF(2), not a trie

Common mistakes

MistakeWhat happensDo this instead
Different widths per numberbits land at the wrong depthsthe same fixed width for every number
Starting below the highest bitstarting at bit 15 gave 30 instead of 1048606start at the top bit the constraints allow
Limited queries in input ordervalues over a later query's limit are already insort queries by limit
Querying an empty triea null walk, or a made-up answerreturn -1 when nothing is allowed
A pair trie for "any subset"[1, 2, 4] gives 6 instead of 7a linear basis

Check yourself

Try to answer before opening each one - recalling beats re-reading.

1. Why is taking the opposite bit at the highest possible position always right?

Show answer

A 1 at bit b is worth 2^b, more than all lower bits together (2^b - 1). No choice at lower bits can make up for losing a higher one.

2. Why must every number be inserted with the same width?

Show answer

Depth in the trie stands for bit position. If some numbers start at a lower bit, their bits sit at the wrong depths and the greedy compares bits of different significance.

3. Why sort the queries by their limit in the offline version?

Show answer

So values can be inserted once, in increasing order, as the limit grows - the trie then holds exactly the values allowed for the current query. In input order, a large limit would insert values a later small-limit query must not use.

Key takeaways

  • Insert fixed-width bits from the top; prefer the opposite bit at each level.
  • The greedy is exact: 2^b > 2^b - 1.
  • Start at the highest bit the values can have.
  • Limited queries: sort values and queries, insert as the limit grows, answer by original index.

Quick revise

The whole skill on one page, for the night before an interview.

This page puts the lesson and the deep dive together. Skim it before practice or an interview, and go back to the full lesson for anything you don't recognize.

Pick the tool

You needUse
Max pair XORbit trie, greedy
No trieprefix hash sets
Max subarray XORprefix XORs in a trie
Pairs with XOR < ksubtree counts
Limited queriesoffline, sorted by limit
Max subset XORlinear basis

Snippets worth knowing by heart

Java

for (int b = HIGH_BIT; b >= 0; b--) {
    int bit = (num >> b) & 1;
    if (node.next[1 - bit] != null) { result |= 1 << b; node = node.next[1 - bit]; }
    else node = node.next[bit];
}

Python

basis = [0] * 30                              # max XOR of any subset
for x in nums:
    for b in range(29, -1, -1):
        if (x >> b) & 1:
            if not basis[b]:
                basis[b] = x
                break
            x ^= basis[b]
best = 0
for b in range(29, -1, -1):
    best = max(best, best ^ basis[b])

The traps

  • Inconsistent widths: bits land at the wrong depths. Insert every number with the same fixed width.
  • Too few bits: starting below the highest bit misses the big XORs. Start at the top bit the constraints allow.
  • Online order for limited queries: values over a later limit are already inserted. Sort queries by limit.
  • Empty tries: the walk hits null or invents an answer. Return -1 when nothing is allowed.
  • Pairs versus subsets: a pair trie can't XOR three values together. Use a linear basis for any subset.

Staff-level points, one line each

  • Prefix hash sets matched the trie and all pairs on 300 arrays.
  • 100,000 random 30-bit numbers: 1,450,445 trie nodes.
  • Subarray XOR = pair XOR of prefix XORs; it matched brute force.
  • Subtree counts count pairs with XOR below k; they matched brute force.
  • [1, 2, 4]: best pair 6, best subset 7 (linear basis).
  • Starting at bit 15 instead of 29 gave 30 instead of 1048606.

Say it in an interview

  • "A higher bit outweighs all lower bits together, so the greedy from the top is exact."
  • "Every number goes in with the same width, from the highest possible bit."
  • "Limited queries: sort by limit and insert as it grows."
  • "Any subset is a different problem: a linear basis over GF(2)."

Practice

This skill comes with 7 multiple-choice questions that tell you which misconception a wrong answer reveals and 2 graded problems. Your answers are graded and your mastery is tracked, so you'll know when the skill holds up. Running code and saving progress need a free account.

Practice Binary Trie Max XOR free

Maximum XOR of Two Numbers in an Array

HardProCoding problemJava · Python

Given an integer array nums, return the maximum value of nums[i] XOR nums[j] over all pairs i, j (i may equal j).

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Maximum XOR With an Element From Array

HardProCoding problemJava · Python

Answer queries about the array nums. Each query xi mi asks: among the elements of nums that are at most mi, which gives the largest xi XOR element? Answer with that value, or -1 if no element is small enough.

The full statement, the examples and the hidden test suite are part of AI Proof Engineers Pro.

Practice Binary Trie Max XOR free

No card needed. Lessons, practice questions and easy problems are free forever.

Free plan, no card · 7-day Pro trial · Cancel anytime