Binary Trie
- Introduction
-
Setting
Given an array , find the maximum XOR of two numbers from array . -
Brute-force Solution
A trivial brute-force approach is : iterate over each pair of numbers and take the maximum XOR among all pairs. -
Idea
A binary trie stores all the numbers in a trie data structure. For each number, we can find the maximum XOR against all other numbers in in time, where is the number of bits. This reduces the overall complexity to .
-
-
Core Idea
-
Property of Bits and XOR
For two numbers and , if the highest set bit of is greater than that of , then , e.g., . So to maximize the XOR of two numbers, we should prioritize making the highest bits equal to 1. For example, if , , and , then , because and differ at a higher bit position than and do. -
Binary Trie
A binary trie stores each number bit by bit from the most significant bit (MSB) to the least significant bit (LSB). To find the maximum XOR of a number against any number in , we traverse the trie from the MSB. At each level, we try to follow the opposite bit of ’s current bit. If an opposite bit exists, which mean we find at least a number in that have bit opposite to current bit, we take that path, this sets a 1 in the XOR result at this position, which is always better than setting a 0. If the opposite bit does not exist, we have no choice but to use the same bit. We repeat this for each bit until we reach the LSB.
-
-
Implementation
CPP// Binary Trie // O(n) insert, remove, maxXor// O(n) in memorystruct TrieNode { TrieNode* c[2] = {}; int count = 0; // count of numbers passing through this node TrieNode() : count(0) {}};struct Trie { TrieNode* root; Trie() { root = new TrieNode(); } // Insert a word into the Trie void insert(int x) { TrieNode* current = root; for(int i=31; i>=0; i--) { int bit = (x >> i) & 1; if (!current->c[bit]) { current->c[bit] = new TrieNode(); } current = current->c[bit]; current->count++; } } void remove(int x) { TrieNode* current = root; for(int i=31; i>=0; i--) { int bit = (x >> i) & 1; current = current->c[bit]; current->count--; } } int maxXor(int x) { TrieNode* current = root; int maxXor = 0; for(int i=31; i>=0; i--) { int bit = (x >> i) & 1; int desiredBit = 1 - bit; // we want the opposite bit for max XOR if (current->c[desiredBit] && current->c[desiredBit]->count > 0) { maxXor |= (1 << i); // set the ith bit in maxXor current = current->c[desiredBit]; } else { current = current->c[bit]; // go to the same bit if desired bit is not available } } return maxXor; }}; -
Complexity
- Inserting a single number takes
, where is the number of bits (32 in the implementation above). Inserting all numbers takes . - Finding the maximum XOR for a single number takes
. Finding the overall maximum XOR across all numbers takes .
- Inserting a single number takes
-
Example
-
Maximum XOR of Two Numbers in an Array (Easy)
solution:
CPPclass Solution {public: int findMaximumXOR(vector<int>& v) { int n = v.size(); Trie trie; int a = 0; rep(i,0,n) trie.insert(v[i]); rep(i,0,n) a = max(a,trie.maxXor(v[i])); return a; }}; -
Maximum Subarray XOR with Bounded Range (Hard)
solution:
CPPclass Solution {public: int maxXor(vector<int>& v, int k) { int n = v.size(); deque<int> maxq, minq; Trie trie; trie.insert(0); vi p(n+1); rep(i,1,n+1){ p[i] = p[i-1] ^ v[i-1]; } int l = 0; int a = 0; rep(r,0,n){ while(not maxq.empty() and maxq.back()<v[r]) maxq.pop_back(); while(not minq.empty() and minq.back()>v[r]) minq.pop_back(); maxq.pb(v[r]); minq.pb(v[r]); while(l<r and maxq.front()-minq.front()>k){ if(maxq.front() == v[l]) maxq.pop_front(); if(minq.front() == v[l]) minq.pop_front(); trie.remove(p[l]); l++; } a = max(a,trie.maxXor(p[r+1])); trie.insert(p[r+1]); } return a; }};
-

