Bitwise Tries: Maximum XOR
Every pair is its own question
Maximum XOR of Two Numbers in an Array asks for the largest a XOR b over any two numbers of a list. The solution everyone writes first tries every pair. Here it is against a trie of the numbers' bits, on 5,000 numbers of 30 bits, the same in every language:
import time
x = 3 # the same numbers in every language
def next_number():
global x
x = x * 48271 % 2147483647
return x
B = 30
nums = [next_number() % (1 << B) for _ in range(5000)]
print(len(nums), "numbers of", B, "bits")
start = time.perf_counter()
best_pairs, tried = 0, 0
for i in range(len(nums)): # every pair: n(n - 1) / 2 XORs
a = nums[i]
for j in range(i + 1, len(nums)):
if a ^ nums[j] > best_pairs:
best_pairs = a ^ nums[j]
tried += len(nums) - i - 1
pairs_ms = round((time.perf_counter() - start) * 1000)
start = time.perf_counter()
zero, one = [0], [0] # node k's children; 0 means none, since no node's child is the root
steps = 0
def insert(v):
global steps
node = 0
for b in range(B - 1, -1, -1): # the highest bit first
steps += 1
side = one if (v >> b) & 1 else zero
if side[node] == 0:
zero.append(0)
one.append(0)
side[node] = len(zero) - 1
node = side[node]
def best_with(v): # the largest v ^ y over the numbers in the trie
global steps
node, xor = 0, 0
for b in range(B - 1, -1, -1):
steps += 1
bit = (v >> b) & 1
other = zero if bit else one # the child for the opposite bit
if other[node]:
xor |= 1 << b
node = other[node]
else:
node = (one if bit else zero)[node]
return xor
best_trie = 0
for k, v in enumerate(nums):
if k:
best_trie = max(best_trie, best_with(v)) # against every number before it
insert(v)
trie_ms = round((time.perf_counter() - start) * 1000)
print("largest XOR:", best_pairs, "from every pair,", best_trie, "with the bit trie")
print("work:", tried, "XORs for every pair,", steps, "steps in the bit trie")
print(f"time: {pairs_ms} ms for every pair, {trie_ms} ms with the bit trie")
# 5000 numbers of 30 bits
# largest XOR: 1073741822 from every pair, 1073741822 with the bit trie
# work: 12497500 XORs for every pair, 299970 steps in the bit trie
# time: 1507 ms for every pair, 64 ms with the bit triepublic class Main {
static long x = 3; // the same numbers in every language
static final int B = 30;
static int[] zero, one; // node k's children; 0 means none, since no node's child is the root
static int size = 1;
static long steps = 0;
static int nextNumber() {
x = x * 48271 % 2147483647;
return (int) x;
}
static void insert(int v) {
int node = 0;
for (int b = B - 1; b >= 0; b--) { // the highest bit first
steps++;
int[] side = ((v >> b) & 1) == 1 ? one : zero;
if (side[node] == 0) side[node] = size++;
node = side[node];
}
}
static int bestWith(int v) { // the largest v ^ y over the numbers in the trie
int node = 0, xor = 0;
for (int b = B - 1; b >= 0; b--) {
steps++;
int bit = (v >> b) & 1;
int[] other = bit == 1 ? zero : one; // the child for the opposite bit
if (other[node] != 0) {
xor |= 1 << b;
node = other[node];
} else {
node = (bit == 1 ? one : zero)[node];
}
}
return xor;
}
public static void main(String[] args) {
int n = 5000;
int[] nums = new int[n];
for (int k = 0; k < n; k++) nums[k] = nextNumber() % (1 << B);
System.out.println(n + " numbers of " + B + " bits");
long start = System.nanoTime();
int bestPairs = 0;
long tried = 0;
for (int i = 0; i < n; i++) { // every pair: n(n - 1) / 2 XORs
int a = nums[i];
for (int j = i + 1; j < n; j++)
if ((a ^ nums[j]) > bestPairs) bestPairs = a ^ nums[j];
tried += n - i - 1;
}
long pairsMs = (System.nanoTime() - start) / 1000000;
start = System.nanoTime();
zero = new int[n * B + 1];
one = new int[n * B + 1];
int bestTrie = 0;
for (int k = 0; k < n; k++) {
if (k > 0) bestTrie = Math.max(bestTrie, bestWith(nums[k])); // against every number before it
insert(nums[k]);
}
long trieMs = (System.nanoTime() - start) / 1000000;
System.out.println("largest XOR: " + bestPairs + " from every pair, " + bestTrie + " with the bit trie");
System.out.println("work: " + tried + " XORs for every pair, " + steps + " steps in the bit trie");
System.out.println("time: " + pairsMs + " ms for every pair, " + trieMs + " ms with the bit trie");
}
}
// 5000 numbers of 30 bits
// largest XOR: 1073741822 from every pair, 1073741822 with the bit trie
// work: 12497500 XORs for every pair, 299970 steps in the bit trie
// time: 46 ms for every pair, 8 ms with the bit trie#include <bits/stdc++.h>
using namespace std;
long long x = 3; // the same numbers in every language
const int B = 30;
vector<int> zero, one; // node k's children; 0 means none, since no node's child is the root
long long steps = 0;
int next_number() {
x = x * 48271 % 2147483647;
return (int) x;
}
void insert(int v) {
int node = 0;
for (int b = B - 1; b >= 0; b--) { // the highest bit first
steps++;
vector<int>& side = ((v >> b) & 1) ? one : zero;
if (side[node] == 0) {
zero.push_back(0);
one.push_back(0);
side[node] = (int) zero.size() - 1;
}
node = side[node];
}
}
int bestWith(int v) { // the largest v ^ y over the numbers in the trie
int node = 0, xr = 0;
for (int b = B - 1; b >= 0; b--) {
steps++;
int bit = (v >> b) & 1;
vector<int>& other = bit ? zero : one; // the child for the opposite bit
if (other[node]) {
xr |= 1 << b;
node = other[node];
} else {
node = (bit ? one : zero)[node];
}
}
return xr;
}
int main() {
int n = 5000;
vector<int> nums(n);
for (int k = 0; k < n; k++) nums[k] = next_number() % (1 << B);
cout << n << " numbers of " << B << " bits\n";
auto start = chrono::steady_clock::now();
int bestPairs = 0;
long long tried = 0;
for (int i = 0; i < n; i++) { // every pair: n(n - 1) / 2 XORs
int a = nums[i];
for (int j = i + 1; j < n; j++)
if ((a ^ nums[j]) > bestPairs) bestPairs = a ^ nums[j];
tried += n - i - 1;
}
long long pairsMs = chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now() - start).count();
start = chrono::steady_clock::now();
zero.assign(1, 0);
one.assign(1, 0);
zero.reserve(n * B + 1);
one.reserve(n * B + 1);
int bestTrie = 0;
for (int k = 0; k < n; k++) {
if (k > 0) bestTrie = max(bestTrie, bestWith(nums[k])); // against every number before it
insert(nums[k]);
}
long long trieMs = chrono::duration_cast<chrono::milliseconds>(chrono::steady_clock::now() - start).count();
cout << "largest XOR: " << bestPairs << " from every pair, " << bestTrie << " with the bit trie\n";
cout << "work: " << tried << " XORs for every pair, " << steps << " steps in the bit trie\n";
cout << "time: " << pairsMs << " ms for every pair, " << trieMs << " ms with the bit trie\n";
}
// 5000 numbers of 30 bits
// largest XOR: 1073741822 from every pair, 1073741822 with the bit trie
// work: 12497500 XORs for every pair, 299970 steps in the bit trie
// time: 2 ms for every pair, 1 ms with the bit trielet x = 3; // the same numbers in every language
function nextNumber() {
x = x * 48271 % 2147483647;
return x;
}
const B = 30;
const n = 5000;
const nums = [];
for (let k = 0; k < n; k++) nums.push(nextNumber() % (1 << B));
console.log(`${n} numbers of ${B} bits`);
let start = performance.now();
let bestPairs = 0, tried = 0;
for (let i = 0; i < n; i++) { // every pair: n(n - 1) / 2 XORs
const a = nums[i];
for (let j = i + 1; j < n; j++)
if ((a ^ nums[j]) > bestPairs) bestPairs = a ^ nums[j];
tried += n - i - 1;
}
const pairsMs = Math.round(performance.now() - start);
start = performance.now();
const zero = new Int32Array(n * B + 1); // node k's children; 0 means none, since no node's child is the root
const one = new Int32Array(n * B + 1);
let size = 1, steps = 0;
function insert(v) {
let node = 0;
for (let b = B - 1; b >= 0; b--) { // the highest bit first
steps++;
const side = (v >> b) & 1 ? one : zero;
if (side[node] === 0) side[node] = size++;
node = side[node];
}
}
function bestWith(v) { // the largest v ^ y over the numbers in the trie
let node = 0, xor = 0;
for (let b = B - 1; b >= 0; b--) {
steps++;
const bit = (v >> b) & 1;
const other = bit ? zero : one; // the child for the opposite bit
if (other[node] !== 0) {
xor |= 1 << b;
node = other[node];
} else {
node = (bit ? one : zero)[node];
}
}
return xor;
}
let bestTrie = 0;
for (let k = 0; k < n; k++) {
if (k > 0) bestTrie = Math.max(bestTrie, bestWith(nums[k])); // against every number before it
insert(nums[k]);
}
const trieMs = Math.round(performance.now() - start);
console.log(`largest XOR: ${bestPairs} from every pair, ${bestTrie} with the bit trie`);
console.log(`work: ${tried} XORs for every pair, ${steps} steps in the bit trie`);
console.log(`time: ${pairsMs} ms for every pair, ${trieMs} ms with the bit trie`);
// 5000 numbers of 30 bits
// largest XOR: 1073741822 from every pair, 1073741822 with the bit trie
// work: 12497500 XORs for every pair, 299970 steps in the bit trie
// time: 24 ms for every pair, 9 ms with the bit trielet x = 3; // the same numbers in every language
function nextNumber(): number {
x = x * 48271 % 2147483647;
return x;
}
const B = 30;
const n = 5000;
const nums: number[] = [];
for (let k = 0; k < n; k++) nums.push(nextNumber() % (1 << B));
console.log(`${n} numbers of ${B} bits`);
let start = performance.now();
let bestPairs = 0, tried = 0;
for (let i = 0; i < n; i++) { // every pair: n(n - 1) / 2 XORs
const a = nums[i];
for (let j = i + 1; j < n; j++)
if ((a ^ nums[j]) > bestPairs) bestPairs = a ^ nums[j];
tried += n - i - 1;
}
const pairsMs = Math.round(performance.now() - start);
start = performance.now();
const zero = new Int32Array(n * B + 1); // node k's children; 0 means none, since no node's child is the root
const one = new Int32Array(n * B + 1);
let size = 1, steps = 0;
function insert(v: number): void {
let node = 0;
for (let b = B - 1; b >= 0; b--) { // the highest bit first
steps++;
const side = (v >> b) & 1 ? one : zero;
if (side[node] === 0) side[node] = size++;
node = side[node];
}
}
function bestWith(v: number): number { // the largest v ^ y over the numbers in the trie
let node = 0, xor = 0;
for (let b = B - 1; b >= 0; b--) {
steps++;
const bit = (v >> b) & 1;
const other = bit ? zero : one; // the child for the opposite bit
if (other[node] !== 0) {
xor |= 1 << b;
node = other[node];
} else {
node = (bit ? one : zero)[node];
}
}
return xor;
}
let bestTrie = 0;
for (let k = 0; k < n; k++) {
if (k > 0) bestTrie = Math.max(bestTrie, bestWith(nums[k])); // against every number before it
insert(nums[k]);
}
const trieMs = Math.round(performance.now() - start);
console.log(`largest XOR: ${bestPairs} from every pair, ${bestTrie} with the bit trie`);
console.log(`work: ${tried} XORs for every pair, ${steps} steps in the bit trie`);
console.log(`time: ${pairsMs} ms for every pair, ${trieMs} ms with the bit trie`);
// 5000 numbers of 30 bits
// largest XOR: 1073741822 from every pair, 1073741822 with the bit trie
// work: 12497500 XORs for every pair, 299970 steps in the bit trie
// time: 25 ms for every pair, 9 ms with the bit trieusing System;
using System.Diagnostics;
class Program {
static long x = 3; // the same numbers in every language
const int B = 30;
static int[] zero, one; // node k's children; 0 means none, since no node's child is the root
static int size = 1;
static long steps = 0;
static int NextNumber() {
x = x * 48271 % 2147483647;
return (int) x;
}
static void Insert(int v) {
int node = 0;
for (int b = B - 1; b >= 0; b--) { // the highest bit first
steps++;
int[] side = ((v >> b) & 1) == 1 ? one : zero;
if (side[node] == 0) side[node] = size++;
node = side[node];
}
}
static int BestWith(int v) { // the largest v ^ y over the numbers in the trie
int node = 0, xor = 0;
for (int b = B - 1; b >= 0; b--) {
steps++;
int bit = (v >> b) & 1;
int[] other = bit == 1 ? zero : one; // the child for the opposite bit
if (other[node] != 0) {
xor |= 1 << b;
node = other[node];
} else {
node = (bit == 1 ? one : zero)[node];
}
}
return xor;
}
static void Main() {
int n = 5000;
var nums = new int[n];
for (int k = 0; k < n; k++) nums[k] = NextNumber() % (1 << B);
Console.WriteLine($"{n} numbers of {B} bits");
var watch = Stopwatch.StartNew();
int bestPairs = 0;
long tried = 0;
for (int i = 0; i < n; i++) { // every pair: n(n - 1) / 2 XORs
int a = nums[i];
for (int j = i + 1; j < n; j++)
if ((a ^ nums[j]) > bestPairs) bestPairs = a ^ nums[j];
tried += n - i - 1;
}
long pairsMs = watch.ElapsedMilliseconds;
watch.Restart();
zero = new int[n * B + 1];
one = new int[n * B + 1];
int bestTrie = 0;
for (int k = 0; k < n; k++) {
if (k > 0) bestTrie = Math.Max(bestTrie, BestWith(nums[k])); // against every number before it
Insert(nums[k]);
}
long trieMs = watch.ElapsedMilliseconds;
Console.WriteLine($"largest XOR: {bestPairs} from every pair, {bestTrie} with the bit trie");
Console.WriteLine($"work: {tried} XORs for every pair, {steps} steps in the bit trie");
Console.WriteLine($"time: {pairsMs} ms for every pair, {trieMs} ms with the bit trie");
}
}
// 5000 numbers of 30 bits
// largest XOR: 1073741822 from every pair, 1073741822 with the bit trie
// work: 12497500 XORs for every pair, 299970 steps in the bit trie
// time: 8 ms for every pair, 3 ms with the bit triepackage main
import (
"fmt"
"time"
)
var x int64 = 3 // the same numbers in every language
const B = 30
var zero, one []int32 // node k's children; 0 means none, since no node's child is the root
var size int32 = 1
var steps int64
func nextNumber() int {
x = x * 48271 % 2147483647
return int(x)
}
func insert(v int) {
node := int32(0)
for b := B - 1; b >= 0; b-- { // the highest bit first
steps++
side := zero
if (v>>b)&1 == 1 {
side = one
}
if side[node] == 0 {
side[node] = size
size++
}
node = side[node]
}
}
func bestWith(v int) int { // the largest v ^ y over the numbers in the trie
node, xor := int32(0), 0
for b := B - 1; b >= 0; b-- {
steps++
bit := (v >> b) & 1
same, other := zero, one // the child for the same bit, and for the opposite bit
if bit == 1 {
same, other = one, zero
}
if other[node] != 0 {
xor |= 1 << b
node = other[node]
} else {
node = same[node]
}
}
return xor
}
func main() {
n := 5000
nums := make([]int, n)
for k := range nums {
nums[k] = nextNumber() % (1 << B)
}
fmt.Printf("%d numbers of %d bits\n", n, B)
start := time.Now()
bestPairs, tried := 0, int64(0)
for i := 0; i < n; i++ { // every pair: n(n - 1) / 2 XORs
a := nums[i]
for j := i + 1; j < n; j++ {
if a^nums[j] > bestPairs {
bestPairs = a ^ nums[j]
}
}
tried += int64(n - i - 1)
}
pairsMs := time.Since(start).Milliseconds()
start = time.Now()
zero = make([]int32, n*B+1)
one = make([]int32, n*B+1)
bestTrie := 0
for k, v := range nums {
if k > 0 {
if got := bestWith(v); got > bestTrie { // against every number before it
bestTrie = got
}
}
insert(v)
}
trieMs := time.Since(start).Milliseconds()
fmt.Printf("largest XOR: %d from every pair, %d with the bit trie\n", bestPairs, bestTrie)
fmt.Printf("work: %d XORs for every pair, %d steps in the bit trie\n", tried, steps)
fmt.Printf("time: %d ms for every pair, %d ms with the bit trie\n", pairsMs, trieMs)
}
// 5000 numbers of 30 bits
// largest XOR: 1073741822 from every pair, 1073741822 with the bit trie
// work: 12497500 XORs for every pair, 299970 steps in the bit trie
// time: 8 ms for every pair, 2 ms with the bit trieBoth found the same largest XOR, 1,073,741,822, in all seven languages. Every pair is 12,497,500 XORs; the bit trie took 299,970 steps, 30 to insert each number and 30 to ask with it: 42 times less work. In time that was 1,507 ms against 64 ms in Python. Compiled languages run the pairs fast at this size, 2 ms against 1 ms in C++, but the trie took less in every language, and the pairs grow with the square of the list: twice the numbers is four times the pairs, and only twice the trie's steps.
The pairs ask each number about every other one. The trie asks one question per bit instead: is there a number whose bit here is the opposite of mine?

A trie of bits
A number is a word of bits. Pick a width B, enough for the largest number (30 bits here, 31 for any non-negative 32-bit integer), and insert each number's bits from the highest to the lowest, padding small numbers with 0s on the left. Every node then has at most two children, one for a 0 bit and one for a 1, and every number is a path of exactly B steps.
To find the number y in the trie with the largest v XOR y, walk v's bits from the highest and take the child for the opposite bit whenever it exists. An opposite bit puts a 1 in that place of the XOR. Where the opposite child is missing, take the same bit, and that place of the XOR is 0. Here it is on five numbers of 5 bits:
B = 5 # every number here is below 2^5 = 32
class Node:
def __init__(self):
self.children = [None, None] # the 0 child and the 1 child
self.number = None # at the bottom: the number these bits spell
def insert(root, v):
node = root
for b in range(B - 1, -1, -1): # the highest bit first
bit = (v >> b) & 1
if node.children[bit] is None:
node.children[bit] = Node()
node = node.children[bit]
node.number = v
def best_partner(root, v): # the number y in the trie with the largest v ^ y
node = root
for b in range(B - 1, -1, -1):
bit = (v >> b) & 1
if node.children[1 - bit] is not None: # the opposite bit is there: take it
node = node.children[1 - bit]
else:
node = node.children[bit]
return node.number
nums = [12, 5, 26, 18, 9]
root = Node()
for v in nums:
insert(root, v)
print("numbers:", ", ".join(f"{v} ({v:05b})" for v in nums))
largest = 0
for v in nums:
y = best_partner(root, v)
largest = max(largest, v ^ y)
print(f"{v}: best partner {y}, XOR {v ^ y} = {v ^ y:05b}")
print("the largest XOR:", largest)
# numbers: 12 (01100), 5 (00101), 26 (11010), 18 (10010), 9 (01001)
# 12: best partner 18, XOR 30 = 11110
# 5: best partner 26, XOR 31 = 11111
# 26: best partner 5, XOR 31 = 11111
# 18: best partner 12, XOR 30 = 11110
# 9: best partner 18, XOR 27 = 11011
# the largest XOR: 31import java.util.*;
public class Main {
static final int B = 5; // every number here is below 2^5 = 32
static class Node {
final Node[] children = new Node[2]; // the 0 child and the 1 child
int number; // at the bottom: the number these bits spell
}
static String bits(int v) {
return String.format("%" + B + "s", Integer.toBinaryString(v)).replace(' ', '0');
}
static void insert(Node root, int v) {
Node node = root;
for (int b = B - 1; b >= 0; b--) { // the highest bit first
int bit = (v >> b) & 1;
if (node.children[bit] == null) node.children[bit] = new Node();
node = node.children[bit];
}
node.number = v;
}
static int bestPartner(Node root, int v) { // the number y in the trie with the largest v ^ y
Node node = root;
for (int b = B - 1; b >= 0; b--) {
int bit = (v >> b) & 1;
if (node.children[1 - bit] != null) node = node.children[1 - bit]; // the opposite bit is there: take it
else node = node.children[bit];
}
return node.number;
}
public static void main(String[] args) {
int[] nums = {12, 5, 26, 18, 9};
Node root = new Node();
for (int v : nums) insert(root, v);
StringJoiner line = new StringJoiner(", ");
for (int v : nums) line.add(v + " (" + bits(v) + ")");
System.out.println("numbers: " + line);
int largest = 0;
for (int v : nums) {
int y = bestPartner(root, v);
largest = Math.max(largest, v ^ y);
System.out.println(v + ": best partner " + y + ", XOR " + (v ^ y) + " = " + bits(v ^ y));
}
System.out.println("the largest XOR: " + largest);
}
}
// numbers: 12 (01100), 5 (00101), 26 (11010), 18 (10010), 9 (01001)
// 12: best partner 18, XOR 30 = 11110
// 5: best partner 26, XOR 31 = 11111
// 26: best partner 5, XOR 31 = 11111
// 18: best partner 12, XOR 30 = 11110
// 9: best partner 18, XOR 27 = 11011
// the largest XOR: 31#include <bits/stdc++.h>
using namespace std;
const int B = 5; // every number here is below 2^5 = 32
struct Node {
Node* children[2] = {}; // the 0 child and the 1 child
int number = 0; // at the bottom: the number these bits spell
};
string bits(int v) {
return bitset<B>(v).to_string();
}
void insert(Node* root, int v) {
Node* node = root;
for (int b = B - 1; b >= 0; b--) { // the highest bit first
int bit = (v >> b) & 1;
if (!node->children[bit]) node->children[bit] = new Node();
node = node->children[bit];
}
node->number = v;
}
int bestPartner(Node* root, int v) { // the number y in the trie with the largest v ^ y
Node* node = root;
for (int b = B - 1; b >= 0; b--) {
int bit = (v >> b) & 1;
if (node->children[1 - bit]) node = node->children[1 - bit]; // the opposite bit is there: take it
else node = node->children[bit];
}
return node->number;
}
int main() {
vector<int> nums = {12, 5, 26, 18, 9};
Node* root = new Node();
for (int v : nums) insert(root, v);
cout << "numbers: ";
for (size_t i = 0; i < nums.size(); i++) cout << (i ? ", " : "") << nums[i] << " (" << bits(nums[i]) << ")";
cout << "\n";
int largest = 0;
for (int v : nums) {
int y = bestPartner(root, v);
largest = max(largest, v ^ y);
cout << v << ": best partner " << y << ", XOR " << (v ^ y) << " = " << bits(v ^ y) << "\n";
}
cout << "the largest XOR: " << largest << "\n";
}
// numbers: 12 (01100), 5 (00101), 26 (11010), 18 (10010), 9 (01001)
// 12: best partner 18, XOR 30 = 11110
// 5: best partner 26, XOR 31 = 11111
// 26: best partner 5, XOR 31 = 11111
// 18: best partner 12, XOR 30 = 11110
// 9: best partner 18, XOR 27 = 11011
// the largest XOR: 31const B = 5; // every number here is below 2^5 = 32
class Node {
constructor() {
this.children = [null, null]; // the 0 child and the 1 child
this.number = null; // at the bottom: the number these bits spell
}
}
const bits = (v) => v.toString(2).padStart(B, "0");
function insert(root, v) {
let node = root;
for (let b = B - 1; b >= 0; b--) { // the highest bit first
const bit = (v >> b) & 1;
if (node.children[bit] === null) node.children[bit] = new Node();
node = node.children[bit];
}
node.number = v;
}
function bestPartner(root, v) { // the number y in the trie with the largest v ^ y
let node = root;
for (let b = B - 1; b >= 0; b--) {
const bit = (v >> b) & 1;
if (node.children[1 - bit] !== null) node = node.children[1 - bit]; // the opposite bit is there: take it
else node = node.children[bit];
}
return node.number;
}
const nums = [12, 5, 26, 18, 9];
const root = new Node();
for (const v of nums) insert(root, v);
console.log("numbers:", nums.map((v) => `${v} (${bits(v)})`).join(", "));
let largest = 0;
for (const v of nums) {
const y = bestPartner(root, v);
largest = Math.max(largest, v ^ y);
console.log(`${v}: best partner ${y}, XOR ${v ^ y} = ${bits(v ^ y)}`);
}
console.log("the largest XOR:", largest);
// numbers: 12 (01100), 5 (00101), 26 (11010), 18 (10010), 9 (01001)
// 12: best partner 18, XOR 30 = 11110
// 5: best partner 26, XOR 31 = 11111
// 26: best partner 5, XOR 31 = 11111
// 18: best partner 12, XOR 30 = 11110
// 9: best partner 18, XOR 27 = 11011
// the largest XOR: 31const B = 5; // every number here is below 2^5 = 32
class BitNode {
children: (BitNode | null)[] = [null, null]; // the 0 child and the 1 child
number = 0; // at the bottom: the number these bits spell
}
const bits = (v: number): string => v.toString(2).padStart(B, "0");
function insert(root: BitNode, v: number): void {
let node = root;
for (let b = B - 1; b >= 0; b--) { // the highest bit first
const bit = (v >> b) & 1;
if (node.children[bit] === null) node.children[bit] = new BitNode();
node = node.children[bit]!;
}
node.number = v;
}
function bestPartner(root: BitNode, v: number): number { // the number y in the trie with the largest v ^ y
let node = root;
for (let b = B - 1; b >= 0; b--) {
const bit = (v >> b) & 1;
if (node.children[1 - bit] !== null) node = node.children[1 - bit]!; // the opposite bit is there: take it
else node = node.children[bit]!;
}
return node.number;
}
const nums = [12, 5, 26, 18, 9];
const root = new BitNode();
for (const v of nums) insert(root, v);
console.log("numbers:", nums.map((v) => `${v} (${bits(v)})`).join(", "));
let largest = 0;
for (const v of nums) {
const y = bestPartner(root, v);
largest = Math.max(largest, v ^ y);
console.log(`${v}: best partner ${y}, XOR ${v ^ y} = ${bits(v ^ y)}`);
}
console.log("the largest XOR:", largest);
// numbers: 12 (01100), 5 (00101), 26 (11010), 18 (10010), 9 (01001)
// 12: best partner 18, XOR 30 = 11110
// 5: best partner 26, XOR 31 = 11111
// 26: best partner 5, XOR 31 = 11111
// 18: best partner 12, XOR 30 = 11110
// 9: best partner 18, XOR 27 = 11011
// the largest XOR: 31using System;
using System.Linq;
class Node {
public readonly Node[] Children = new Node[2]; // the 0 child and the 1 child
public int Number; // at the bottom: the number these bits spell
}
class Program {
const int B = 5; // every number here is below 2^5 = 32
static string Bits(int v) => Convert.ToString(v, 2).PadLeft(B, '0');
static void Insert(Node root, int v) {
Node node = root;
for (int b = B - 1; b >= 0; b--) { // the highest bit first
int bit = (v >> b) & 1;
if (node.Children[bit] == null) node.Children[bit] = new Node();
node = node.Children[bit];
}
node.Number = v;
}
static int BestPartner(Node root, int v) { // the number y in the trie with the largest v ^ y
Node node = root;
for (int b = B - 1; b >= 0; b--) {
int bit = (v >> b) & 1;
if (node.Children[1 - bit] != null) node = node.Children[1 - bit]; // the opposite bit is there: take it
else node = node.Children[bit];
}
return node.Number;
}
static void Main() {
int[] nums = {12, 5, 26, 18, 9};
var root = new Node();
foreach (int v in nums) Insert(root, v);
Console.WriteLine("numbers: " + string.Join(", ", nums.Select(v => $"{v} ({Bits(v)})")));
int largest = 0;
foreach (int v in nums) {
int y = BestPartner(root, v);
largest = Math.Max(largest, v ^ y);
Console.WriteLine($"{v}: best partner {y}, XOR {v ^ y} = {Bits(v ^ y)}");
}
Console.WriteLine($"the largest XOR: {largest}");
}
}
// numbers: 12 (01100), 5 (00101), 26 (11010), 18 (10010), 9 (01001)
// 12: best partner 18, XOR 30 = 11110
// 5: best partner 26, XOR 31 = 11111
// 26: best partner 5, XOR 31 = 11111
// 18: best partner 12, XOR 30 = 11110
// 9: best partner 18, XOR 27 = 11011
// the largest XOR: 31package main
import (
"fmt"
"strings"
)
const B = 5 // every number here is below 2^5 = 32
type Node struct {
children [2]*Node // the 0 child and the 1 child
number int // at the bottom: the number these bits spell
}
func insert(root *Node, v int) {
node := root
for b := B - 1; b >= 0; b-- { // the highest bit first
bit := (v >> b) & 1
if node.children[bit] == nil {
node.children[bit] = &Node{}
}
node = node.children[bit]
}
node.number = v
}
func bestPartner(root *Node, v int) int { // the number y in the trie with the largest v ^ y
node := root
for b := B - 1; b >= 0; b-- {
bit := (v >> b) & 1
if node.children[1-bit] != nil { // the opposite bit is there: take it
node = node.children[1-bit]
} else {
node = node.children[bit]
}
}
return node.number
}
func main() {
nums := []int{12, 5, 26, 18, 9}
root := &Node{}
for _, v := range nums {
insert(root, v)
}
shown := []string{}
for _, v := range nums {
shown = append(shown, fmt.Sprintf("%d (%05b)", v, v))
}
fmt.Println("numbers:", strings.Join(shown, ", "))
largest := 0
for _, v := range nums {
y := bestPartner(root, v)
if v^y > largest {
largest = v ^ y
}
fmt.Printf("%d: best partner %d, XOR %d = %05b\n", v, y, v^y, v^y)
}
fmt.Println("the largest XOR:", largest)
}
// numbers: 12 (01100), 5 (00101), 26 (11010), 18 (10010), 9 (01001)
// 12: best partner 18, XOR 30 = 11110
// 5: best partner 26, XOR 31 = 11111
// 26: best partner 5, XOR 31 = 11111
// 18: best partner 12, XOR 30 = 11110
// 9: best partner 18, XOR 27 = 11011
// the largest XOR: 315 is 00101, and the walk wants 1, 1, 0, 1, 0: 26, 11010, has all five, so 5 XOR 26 is 11111, 31, the largest XOR there can be in 5 bits. 12, 01100, wants 1, 0, 0, 1, 1: the trie has 1 then 0 (18 and 26 start 1, only 18 goes on with 0), then 18's 0 and 1, and at the last bit 18 has 0 where 12 wanted 1, so the walk takes it anyway: 12 XOR 18 is 11110, 30. No pair beats 31, and the walks found it.
Choose the Opposite Bit. Walk the trie yourself for each number: at every level, choose the branch, and see the XOR build up one bit at a time.

Why the highest bit decides
Taking the opposite bit at the top costs nothing, whatever it does to the bits below. A 1 in bit k is worth , and all the bits under it together are worth at most : 10000 is 16, 01111 is 15. So once the places above are settled, a number with the opposite bit in this place beats every number with the same bit, whatever their lower bits are, and the walk can settle each place for good before looking at the next.
That's also why the trie holds the bits from the highest down. Read from the lowest bit, the same choices settle the cheap bits first, and a cheap 1 can lead the walk away from an expensive one.

Numbers under a limit
Maximum XOR With an Element From Array adds a limit to each query: the largest x XOR y with y at most m, or −1 if no number is that small. A trie of all the numbers would offer some that are too big. Instead, answer the queries offline: sort the numbers, sort the queries by their limit, and before each query, insert the numbers its limit now lets in. Each number goes in once, and each query sees exactly the numbers it's allowed. Then put the answers back in the order they were asked:
B = 5
def insert(root, v):
node = root
for b in range(B - 1, -1, -1):
bit = (v >> b) & 1
if node[bit] is None:
node[bit] = [None, None]
node = node[bit]
def best_xor(root, x): # the largest x ^ y over the numbers in the trie
node, xor = root, 0
for b in range(B - 1, -1, -1):
bit = (x >> b) & 1
if node[1 - bit] is not None:
xor |= 1 << b
node = node[1 - bit]
else:
node = node[bit]
return xor
nums = sorted([12, 5, 26, 18, 9])
queries = [(5, 20), (7, 10), (30, 4), (1, 30)] # (x, limit): the largest x ^ y with y at most the limit
answers = [0] * len(queries)
root = [None, None] # a node is its two children
k = 0
for q in sorted(range(len(queries)), key=lambda q: queries[q][1]): # the smallest limit first
x, limit = queries[q]
added = []
while k < len(nums) and nums[k] <= limit: # every number the limit now lets in
insert(root, nums[k])
added.append(str(nums[k]))
k += 1
answers[q] = best_xor(root, x) if k else -1 # no number at or under the limit
print(f"limit {limit}: added {' '.join(added) or 'nothing'}, x = {x}: {answers[q]}")
print("answers in the order asked:", " ".join(map(str, answers)))
# limit 4: added nothing, x = 30: -1
# limit 10: added 5 9, x = 7: 14
# limit 20: added 12 18, x = 5: 23
# limit 30: added 26, x = 1: 27
# answers in the order asked: 23 14 -1 27import java.util.*;
public class Main {
static final int B = 5;
static class Node {
final Node[] children = new Node[2];
}
static void insert(Node root, int v) {
Node node = root;
for (int b = B - 1; b >= 0; b--) {
int bit = (v >> b) & 1;
if (node.children[bit] == null) node.children[bit] = new Node();
node = node.children[bit];
}
}
static int bestXor(Node root, int x) { // the largest x ^ y over the numbers in the trie
Node node = root;
int xor = 0;
for (int b = B - 1; b >= 0; b--) {
int bit = (x >> b) & 1;
if (node.children[1 - bit] != null) {
xor |= 1 << b;
node = node.children[1 - bit];
} else {
node = node.children[bit];
}
}
return xor;
}
public static void main(String[] args) {
int[] nums = {12, 5, 26, 18, 9};
Arrays.sort(nums);
int[][] queries = {{5, 20}, {7, 10}, {30, 4}, {1, 30}}; // (x, limit): the largest x ^ y with y at most the limit
Integer[] order = {0, 1, 2, 3};
Arrays.sort(order, Comparator.comparingInt(q -> queries[q][1])); // the smallest limit first
int[] answers = new int[queries.length];
Node root = new Node();
int k = 0;
for (int q : order) {
int x = queries[q][0], limit = queries[q][1];
StringJoiner added = new StringJoiner(" ");
while (k < nums.length && nums[k] <= limit) { // every number the limit now lets in
insert(root, nums[k]);
added.add(String.valueOf(nums[k]));
k++;
}
answers[q] = k > 0 ? bestXor(root, x) : -1; // no number at or under the limit
String shown = added.length() == 0 ? "nothing" : added.toString();
System.out.println("limit " + limit + ": added " + shown + ", x = " + x + ": " + answers[q]);
}
StringJoiner all = new StringJoiner(" ");
for (int a : answers) all.add(String.valueOf(a));
System.out.println("answers in the order asked: " + all);
}
}
// limit 4: added nothing, x = 30: -1
// limit 10: added 5 9, x = 7: 14
// limit 20: added 12 18, x = 5: 23
// limit 30: added 26, x = 1: 27
// answers in the order asked: 23 14 -1 27#include <bits/stdc++.h>
using namespace std;
const int B = 5;
struct Node {
Node* children[2] = {};
};
void insert(Node* root, int v) {
Node* node = root;
for (int b = B - 1; b >= 0; b--) {
int bit = (v >> b) & 1;
if (!node->children[bit]) node->children[bit] = new Node();
node = node->children[bit];
}
}
int bestXor(Node* root, int x) { // the largest x ^ y over the numbers in the trie
Node* node = root;
int xr = 0;
for (int b = B - 1; b >= 0; b--) {
int bit = (x >> b) & 1;
if (node->children[1 - bit]) {
xr |= 1 << b;
node = node->children[1 - bit];
} else {
node = node->children[bit];
}
}
return xr;
}
int main() {
vector<int> nums = {12, 5, 26, 18, 9};
sort(nums.begin(), nums.end());
vector<pair<int, int>> queries = {{5, 20}, {7, 10}, {30, 4}, {1, 30}}; // (x, limit): the largest x ^ y with y at most the limit
vector<int> order = {0, 1, 2, 3};
sort(order.begin(), order.end(), [&](int a, int b) { return queries[a].second < queries[b].second; }); // the smallest limit first
vector<int> answers(queries.size());
Node* root = new Node();
size_t k = 0;
for (int q : order) {
auto [x, limit] = queries[q];
string added;
while (k < nums.size() && nums[k] <= limit) { // every number the limit now lets in
insert(root, nums[k]);
added += (added.empty() ? "" : " ") + to_string(nums[k]);
k++;
}
answers[q] = k > 0 ? bestXor(root, x) : -1; // no number at or under the limit
cout << "limit " << limit << ": added " << (added.empty() ? "nothing" : added) << ", x = " << x << ": " << answers[q] << "\n";
}
cout << "answers in the order asked:";
for (int a : answers) cout << " " << a;
cout << "\n";
}
// limit 4: added nothing, x = 30: -1
// limit 10: added 5 9, x = 7: 14
// limit 20: added 12 18, x = 5: 23
// limit 30: added 26, x = 1: 27
// answers in the order asked: 23 14 -1 27const B = 5;
function insert(root, v) {
let node = root;
for (let b = B - 1; b >= 0; b--) {
const bit = (v >> b) & 1;
if (node[bit] === null) node[bit] = [null, null];
node = node[bit];
}
}
function bestXor(root, x) { // the largest x ^ y over the numbers in the trie
let node = root, xor = 0;
for (let b = B - 1; b >= 0; b--) {
const bit = (x >> b) & 1;
if (node[1 - bit] !== null) {
xor |= 1 << b;
node = node[1 - bit];
} else {
node = node[bit];
}
}
return xor;
}
const nums = [12, 5, 26, 18, 9].sort((a, b) => a - b);
const queries = [[5, 20], [7, 10], [30, 4], [1, 30]]; // (x, limit): the largest x ^ y with y at most the limit
const answers = new Array(queries.length).fill(0);
const root = [null, null]; // a node is its two children
let k = 0;
for (const q of [0, 1, 2, 3].sort((a, b) => queries[a][1] - queries[b][1])) { // the smallest limit first
const [x, limit] = queries[q];
const added = [];
while (k < nums.length && nums[k] <= limit) { // every number the limit now lets in
insert(root, nums[k]);
added.push(nums[k]);
k++;
}
answers[q] = k > 0 ? bestXor(root, x) : -1; // no number at or under the limit
console.log(`limit ${limit}: added ${added.join(" ") || "nothing"}, x = ${x}: ${answers[q]}`);
}
console.log("answers in the order asked:", answers.join(" "));
// limit 4: added nothing, x = 30: -1
// limit 10: added 5 9, x = 7: 14
// limit 20: added 12 18, x = 5: 23
// limit 30: added 26, x = 1: 27
// answers in the order asked: 23 14 -1 27const B = 5;
type BitNode = [BitNode | null, BitNode | null]; // a node is its two children
function insert(root: BitNode, v: number): void {
let node = root;
for (let b = B - 1; b >= 0; b--) {
const bit = (v >> b) & 1;
if (node[bit] === null) node[bit] = [null, null];
node = node[bit]!;
}
}
function bestXor(root: BitNode, x: number): number { // the largest x ^ y over the numbers in the trie
let node = root, xor = 0;
for (let b = B - 1; b >= 0; b--) {
const bit = (x >> b) & 1;
if (node[1 - bit] !== null) {
xor |= 1 << b;
node = node[1 - bit]!;
} else {
node = node[bit]!;
}
}
return xor;
}
const nums = [12, 5, 26, 18, 9].sort((a, b) => a - b);
const queries = [[5, 20], [7, 10], [30, 4], [1, 30]]; // (x, limit): the largest x ^ y with y at most the limit
const answers: number[] = new Array(queries.length).fill(0);
const root: BitNode = [null, null];
let k = 0;
for (const q of [0, 1, 2, 3].sort((a, b) => queries[a][1] - queries[b][1])) { // the smallest limit first
const [x, limit] = queries[q];
const added: number[] = [];
while (k < nums.length && nums[k] <= limit) { // every number the limit now lets in
insert(root, nums[k]);
added.push(nums[k]);
k++;
}
answers[q] = k > 0 ? bestXor(root, x) : -1; // no number at or under the limit
console.log(`limit ${limit}: added ${added.join(" ") || "nothing"}, x = ${x}: ${answers[q]}`);
}
console.log("answers in the order asked:", answers.join(" "));
// limit 4: added nothing, x = 30: -1
// limit 10: added 5 9, x = 7: 14
// limit 20: added 12 18, x = 5: 23
// limit 30: added 26, x = 1: 27
// answers in the order asked: 23 14 -1 27using System;
using System.Collections.Generic;
using System.Linq;
class Node {
public readonly Node[] Children = new Node[2];
}
class Program {
const int B = 5;
static void Insert(Node root, int v) {
Node node = root;
for (int b = B - 1; b >= 0; b--) {
int bit = (v >> b) & 1;
if (node.Children[bit] == null) node.Children[bit] = new Node();
node = node.Children[bit];
}
}
static int BestXor(Node root, int x) { // the largest x ^ y over the numbers in the trie
Node node = root;
int xor = 0;
for (int b = B - 1; b >= 0; b--) {
int bit = (x >> b) & 1;
if (node.Children[1 - bit] != null) {
xor |= 1 << b;
node = node.Children[1 - bit];
} else {
node = node.Children[bit];
}
}
return xor;
}
static void Main() {
int[] nums = {12, 5, 26, 18, 9};
Array.Sort(nums);
int[][] queries = {new[] {5, 20}, new[] {7, 10}, new[] {30, 4}, new[] {1, 30}}; // (x, limit): the largest x ^ y with y at most the limit
int[] order = Enumerable.Range(0, queries.Length).OrderBy(q => queries[q][1]).ToArray(); // the smallest limit first
var answers = new int[queries.Length];
var root = new Node();
int k = 0;
foreach (int q in order) {
int x = queries[q][0], limit = queries[q][1];
var added = new List<int>();
while (k < nums.Length && nums[k] <= limit) { // every number the limit now lets in
Insert(root, nums[k]);
added.Add(nums[k]);
k++;
}
answers[q] = k > 0 ? BestXor(root, x) : -1; // no number at or under the limit
string shown = added.Count == 0 ? "nothing" : string.Join(" ", added);
Console.WriteLine($"limit {limit}: added {shown}, x = {x}: {answers[q]}");
}
Console.WriteLine("answers in the order asked: " + string.Join(" ", answers));
}
}
// limit 4: added nothing, x = 30: -1
// limit 10: added 5 9, x = 7: 14
// limit 20: added 12 18, x = 5: 23
// limit 30: added 26, x = 1: 27
// answers in the order asked: 23 14 -1 27package main
import (
"fmt"
"sort"
"strconv"
"strings"
)
const B = 5
type Node struct {
children [2]*Node
}
func insert(root *Node, v int) {
node := root
for b := B - 1; b >= 0; b-- {
bit := (v >> b) & 1
if node.children[bit] == nil {
node.children[bit] = &Node{}
}
node = node.children[bit]
}
}
func bestXor(root *Node, x int) int { // the largest x ^ y over the numbers in the trie
node, xor := root, 0
for b := B - 1; b >= 0; b-- {
bit := (x >> b) & 1
if node.children[1-bit] != nil {
xor |= 1 << b
node = node.children[1-bit]
} else {
node = node.children[bit]
}
}
return xor
}
func main() {
nums := []int{12, 5, 26, 18, 9}
sort.Ints(nums)
queries := [][2]int{{5, 20}, {7, 10}, {30, 4}, {1, 30}} // (x, limit): the largest x ^ y with y at most the limit
order := []int{0, 1, 2, 3}
sort.Slice(order, func(a, b int) bool { return queries[order[a]][1] < queries[order[b]][1] }) // the smallest limit first
answers := make([]int, len(queries))
root := &Node{}
k := 0
for _, q := range order {
x, limit := queries[q][0], queries[q][1]
added := []string{}
for k < len(nums) && nums[k] <= limit { // every number the limit now lets in
insert(root, nums[k])
added = append(added, strconv.Itoa(nums[k]))
k++
}
if k > 0 {
answers[q] = bestXor(root, x)
} else {
answers[q] = -1 // no number at or under the limit
}
shown := strings.Join(added, " ")
if shown == "" {
shown = "nothing"
}
fmt.Printf("limit %d: added %s, x = %d: %d\n", limit, shown, x, answers[q])
}
all := []string{}
for _, a := range answers {
all = append(all, strconv.Itoa(a))
}
fmt.Println("answers in the order asked:", strings.Join(all, " "))
}
// limit 4: added nothing, x = 30: -1
// limit 10: added 5 9, x = 7: 14
// limit 20: added 12 18, x = 5: 23
// limit 30: added 26, x = 1: 27
// answers in the order asked: 23 14 -1 27The smallest limit, 4, came first and let nothing in: −1. The limit 10 let in 5 and 9, and 7's best among them was 7 XOR 9, 14. The limit 20 added 12 and 18, and 5's best was 5 XOR 18, 23: 26 would have made 31, but it's over the limit. 26 came in last, for 1: 1 XOR 26 is 27.

What it costs
| Every pair | The bit trie | |
|---|---|---|
| Largest XOR of n numbers | O(n²) XORs | O(n · B): B steps to insert, B to ask |
| Memory | O(1) | up to n · B + 1 nodes |
| q queries with a limit | O(q · n) | O((n + q) · B) after sorting |
B is the width, fixed by the largest value the problem allows: 30 or 31 for numbers below or . Each node has only two children, so the trie can live in two arrays of child indexes, one for 0 and one for 1, as the first example did, instead of an object per node.
Spotting a bit trie
Clues that a trie of bits is the tool:
- The largest (or smallest) XOR of two numbers, or of a number with any of a set.
- XOR questions asked over and over against a growing set of numbers.
- A limit on the numbers a query may use: sort and insert as the limit grows.
Near misses:
- The XOR of everything, or of a range: a running XOR (prefix XOR) answers it, no trie.
- One XOR question on a small list: every pair is fine when n is a few hundred.
- The largest sum or AND of two numbers: XOR's "opposite bit" rule doesn't carry over.
Mistakes that look right
Two of these are run below in every language. The others are just as common:
- Inserting and walking from the lowest bit: the walk settles cheap bits first and can miss the best partner.
- A width too small: bits above it never enter the trie, and the XOR loses them.
- Queries with a limit answered against all the numbers: the walk takes numbers over the limit.
- Asking against an empty trie: the first number has nothing to pair with, and the walk falls off a missing child. Ask from the second number on, as the first example does.
def best_xor(nums, v, width, lowest_first):
"""a trie of the numbers' bits, then the walk that takes the opposite bit where it can"""
order = list(range(width)) if lowest_first else list(range(width - 1, -1, -1))
root = [None, None]
for y in nums:
node = root
for b in order:
bit = (y >> b) & 1
if node[bit] is None:
node[bit] = [None, None]
node = node[bit]
node, xor = root, 0
for b in order:
bit = (v >> b) & 1
if node[1 - bit] is not None:
xor |= 1 << b
node = node[1 - bit]
else:
node = node[bit]
return xor
nums = [12, 5, 26, 18, 9]
for name, width, lowest_first in (("from the highest bit", 5, False), ("from the lowest bit", 5, True),
("with 4 bits for 5-bit numbers", 4, False)):
got = {v: best_xor(nums, v, width, lowest_first) for v in nums}
print(f"{name}: 12 gets {got[12]}, 18 gets {got[18]}, the largest {max(got.values())}")
# from the highest bit: 12 gets 30, 18 gets 30, the largest 31
# from the lowest bit: 12 gets 5, 18 gets 23, the largest 31
# with 4 bits for 5-bit numbers: 12 gets 14, 18 gets 14, the largest 15public class Main {
/** a trie of the numbers' bits, then the walk that takes the opposite bit where it can */
static int bestXor(int[] nums, int v, int width, boolean lowestFirst) {
int[] order = new int[width];
for (int k = 0; k < width; k++) order[k] = lowestFirst ? k : width - 1 - k;
Object[] root = new Object[2];
for (int y : nums) {
Object[] node = root;
for (int b : order) {
int bit = (y >> b) & 1;
if (node[bit] == null) node[bit] = new Object[2];
node = (Object[]) node[bit];
}
}
Object[] node = root;
int xor = 0;
for (int b : order) {
int bit = (v >> b) & 1;
if (node[1 - bit] != null) {
xor |= 1 << b;
node = (Object[]) node[1 - bit];
} else {
node = (Object[]) node[bit];
}
}
return xor;
}
public static void main(String[] args) {
int[] nums = {12, 5, 26, 18, 9};
String[] names = {"from the highest bit", "from the lowest bit", "with 4 bits for 5-bit numbers"};
int[] widths = {5, 5, 4};
boolean[] lowest = {false, true, false};
for (int k = 0; k < 3; k++) {
int largest = 0;
for (int v : nums) largest = Math.max(largest, bestXor(nums, v, widths[k], lowest[k]));
System.out.println(names[k] + ": 12 gets " + bestXor(nums, 12, widths[k], lowest[k]) + ", 18 gets "
+ bestXor(nums, 18, widths[k], lowest[k]) + ", the largest " + largest);
}
}
}
// from the highest bit: 12 gets 30, 18 gets 30, the largest 31
// from the lowest bit: 12 gets 5, 18 gets 23, the largest 31
// with 4 bits for 5-bit numbers: 12 gets 14, 18 gets 14, the largest 15#include <bits/stdc++.h>
using namespace std;
struct Node {
Node* children[2] = {};
};
/** a trie of the numbers' bits, then the walk that takes the opposite bit where it can */
int bestXor(const vector<int>& nums, int v, int width, bool lowestFirst) {
vector<int> order;
for (int k = 0; k < width; k++) order.push_back(lowestFirst ? k : width - 1 - k);
Node* root = new Node();
for (int y : nums) {
Node* node = root;
for (int b : order) {
int bit = (y >> b) & 1;
if (!node->children[bit]) node->children[bit] = new Node();
node = node->children[bit];
}
}
Node* node = root;
int xr = 0;
for (int b : order) {
int bit = (v >> b) & 1;
if (node->children[1 - bit]) {
xr |= 1 << b;
node = node->children[1 - bit];
} else {
node = node->children[bit];
}
}
return xr;
}
int main() {
vector<int> nums = {12, 5, 26, 18, 9};
vector<tuple<string, int, bool>> ways = {
{"from the highest bit", 5, false}, {"from the lowest bit", 5, true}, {"with 4 bits for 5-bit numbers", 4, false}};
for (auto& [name, width, lowest] : ways) {
int largest = 0;
for (int v : nums) largest = max(largest, bestXor(nums, v, width, lowest));
cout << name << ": 12 gets " << bestXor(nums, 12, width, lowest) << ", 18 gets "
<< bestXor(nums, 18, width, lowest) << ", the largest " << largest << "\n";
}
}
// from the highest bit: 12 gets 30, 18 gets 30, the largest 31
// from the lowest bit: 12 gets 5, 18 gets 23, the largest 31
// with 4 bits for 5-bit numbers: 12 gets 14, 18 gets 14, the largest 15/** a trie of the numbers' bits, then the walk that takes the opposite bit where it can */
function bestXor(nums, v, width, lowestFirst) {
const order = Array.from({ length: width }, (_, k) => (lowestFirst ? k : width - 1 - k));
const root = [null, null];
for (const y of nums) {
let node = root;
for (const b of order) {
const bit = (y >> b) & 1;
if (node[bit] === null) node[bit] = [null, null];
node = node[bit];
}
}
let node = root, xor = 0;
for (const b of order) {
const bit = (v >> b) & 1;
if (node[1 - bit] !== null) {
xor |= 1 << b;
node = node[1 - bit];
} else {
node = node[bit];
}
}
return xor;
}
const nums = [12, 5, 26, 18, 9];
for (const [name, width, lowestFirst] of [["from the highest bit", 5, false], ["from the lowest bit", 5, true],
["with 4 bits for 5-bit numbers", 4, false]]) {
const largest = Math.max(...nums.map((v) => bestXor(nums, v, width, lowestFirst)));
console.log(`${name}: 12 gets ${bestXor(nums, 12, width, lowestFirst)}, 18 gets ${bestXor(nums, 18, width, lowestFirst)}, the largest ${largest}`);
}
// from the highest bit: 12 gets 30, 18 gets 30, the largest 31
// from the lowest bit: 12 gets 5, 18 gets 23, the largest 31
// with 4 bits for 5-bit numbers: 12 gets 14, 18 gets 14, the largest 15type BitNode = [BitNode | null, BitNode | null];
/** a trie of the numbers' bits, then the walk that takes the opposite bit where it can */
function bestXor(nums: number[], v: number, width: number, lowestFirst: boolean): number {
const order = Array.from({ length: width }, (_, k) => (lowestFirst ? k : width - 1 - k));
const root: BitNode = [null, null];
for (const y of nums) {
let node = root;
for (const b of order) {
const bit = (y >> b) & 1;
if (node[bit] === null) node[bit] = [null, null];
node = node[bit]!;
}
}
let node = root, xor = 0;
for (const b of order) {
const bit = (v >> b) & 1;
if (node[1 - bit] !== null) {
xor |= 1 << b;
node = node[1 - bit]!;
} else {
node = node[bit]!;
}
}
return xor;
}
const nums = [12, 5, 26, 18, 9];
const ways: [string, number, boolean][] = [["from the highest bit", 5, false], ["from the lowest bit", 5, true],
["with 4 bits for 5-bit numbers", 4, false]];
for (const [name, width, lowestFirst] of ways) {
const largest = Math.max(...nums.map((v) => bestXor(nums, v, width, lowestFirst)));
console.log(`${name}: 12 gets ${bestXor(nums, 12, width, lowestFirst)}, 18 gets ${bestXor(nums, 18, width, lowestFirst)}, the largest ${largest}`);
}
// from the highest bit: 12 gets 30, 18 gets 30, the largest 31
// from the lowest bit: 12 gets 5, 18 gets 23, the largest 31
// with 4 bits for 5-bit numbers: 12 gets 14, 18 gets 14, the largest 15using System;
using System.Linq;
class Node {
public readonly Node[] Children = new Node[2];
}
class Program {
/** a trie of the numbers' bits, then the walk that takes the opposite bit where it can */
static int BestXor(int[] nums, int v, int width, bool lowestFirst) {
int[] order = Enumerable.Range(0, width).Select(k => lowestFirst ? k : width - 1 - k).ToArray();
var root = new Node();
foreach (int y in nums) {
Node node = root;
foreach (int b in order) {
int bit = (y >> b) & 1;
if (node.Children[bit] == null) node.Children[bit] = new Node();
node = node.Children[bit];
}
}
Node at = root;
int xor = 0;
foreach (int b in order) {
int bit = (v >> b) & 1;
if (at.Children[1 - bit] != null) {
xor |= 1 << b;
at = at.Children[1 - bit];
} else {
at = at.Children[bit];
}
}
return xor;
}
static void Main() {
int[] nums = {12, 5, 26, 18, 9};
var ways = new (string, int, bool)[] {
("from the highest bit", 5, false), ("from the lowest bit", 5, true), ("with 4 bits for 5-bit numbers", 4, false)};
foreach (var (name, width, lowest) in ways) {
int largest = nums.Max(v => BestXor(nums, v, width, lowest));
Console.WriteLine($"{name}: 12 gets {BestXor(nums, 12, width, lowest)}, 18 gets {BestXor(nums, 18, width, lowest)}, the largest {largest}");
}
}
}
// from the highest bit: 12 gets 30, 18 gets 30, the largest 31
// from the lowest bit: 12 gets 5, 18 gets 23, the largest 31
// with 4 bits for 5-bit numbers: 12 gets 14, 18 gets 14, the largest 15package main
import "fmt"
type Node struct {
children [2]*Node
}
// bestXor builds a trie of the numbers' bits, then walks it taking the opposite bit where it can
func bestXor(nums []int, v, width int, lowestFirst bool) int {
order := make([]int, width)
for k := range order {
if lowestFirst {
order[k] = k
} else {
order[k] = width - 1 - k
}
}
root := &Node{}
for _, y := range nums {
node := root
for _, b := range order {
bit := (y >> b) & 1
if node.children[bit] == nil {
node.children[bit] = &Node{}
}
node = node.children[bit]
}
}
node, xor := root, 0
for _, b := range order {
bit := (v >> b) & 1
if node.children[1-bit] != nil {
xor |= 1 << b
node = node.children[1-bit]
} else {
node = node.children[bit]
}
}
return xor
}
func main() {
nums := []int{12, 5, 26, 18, 9}
ways := []struct {
name string
width int
lowest bool
}{{"from the highest bit", 5, false}, {"from the lowest bit", 5, true}, {"with 4 bits for 5-bit numbers", 4, false}}
for _, way := range ways {
largest := 0
for _, v := range nums {
if got := bestXor(nums, v, way.width, way.lowest); got > largest {
largest = got
}
}
fmt.Printf("%s: 12 gets %d, 18 gets %d, the largest %d\n", way.name, bestXor(nums, 12, way.width, way.lowest),
bestXor(nums, 18, way.width, way.lowest), largest)
}
}
// from the highest bit: 12 gets 30, 18 gets 30, the largest 31
// from the lowest bit: 12 gets 5, 18 gets 23, the largest 31
// with 4 bits for 5-bit numbers: 12 gets 14, 18 gets 14, the largest 15From the lowest bit, 12's best came out as 5 and 18's as 23, where 30 was there for both. The largest still came out 31, but only because 5 and 26 differ in every bit. With 4 bits for these 5-bit numbers, the top bit, worth 16, never entered the trie, and the largest came out 15.
Check yourself
Quiz
1 / 6Multiple choice
Multiple choice
Multiple choice
Multiple choice
Multiple choice
Multiple choice
Practice
The section's problems for this lesson, each mapped to the idea it trains:
- Bitwise Foundations for Tries: the bit trie and the opposite-bit walk, from scratch.
- Maximum XOR of Two Numbers in an Array: insert each number and ask with it.
- Maximum XOR With an Element From Array: queries with a limit, sorted and answered offline.