Create the root node of the trie with no children and zero count. This node will be the starting point for all insertions and queries.
💡 The root node is essential as the entry point for representing all numbers in binary form within the trie.
Line:root = TrieNode()
💡 The trie starts empty, ready to build paths for numbers bit by bit.
insert
Insert Number 1 - Bit 20 (Most Significant Bit)
Start inserting number 1 into the trie from the highest bit (bit 20). Extract bit 20 (which is 0) and create a child node for bit 0 since it does not exist yet.
💡 Inserting bits from high to low builds the binary representation path for the number in the trie.
Line:bit = (num >> i) & 1
if not node.children[bit]:
node.children[bit] = TrieNode()
node = node.children[bit]
node.count += 1
💡 Each bit leads to a branch; creating a new node means this bit pattern is new in the trie.
insert
Insert Number 1 - Bits 19 to 1
Continue inserting bits 19 down to 1 for number 1. All these bits are 0, so create child nodes for bit 0 along the path, incrementing counts.
💡 Most bits are zero for small numbers, so the trie path extends mostly along 0-child nodes.
Line:for i in range(20, -1, -1):
bit = (num >> i) & 1
if not node.children[bit]:
node.children[bit] = TrieNode()
node = node.children[bit]
node.count += 1
💡 The trie path for 1 is a chain of 0 bits except the last bit.
compare
Query Trie for Number 4 with Limit 7 (high+1)
Query how many previously inserted numbers have XOR with 4 less than 7. Start from bit 20, comparing bits of 4 and limit 7 to decide traversal and counting.
💡 Querying uses bitwise logic to count how many paths in the trie satisfy XOR < limit at each bit.
💡 This step accumulates counts of numbers with XOR bits less than limit at this position.
insert
Insert Number 4 into Trie
Insert number 4 bit by bit from bit 20 down to bit 0, creating new nodes or traversing existing ones, incrementing counts.
💡 Inserting each number updates the trie structure and counts to reflect all numbers inserted so far.
Line:for i in range(20, -1, -1):
bit = (num >> i) & 1
if not node.children[bit]:
node.children[bit] = TrieNode()
node = node.children[bit]
node.count += 1
💡 Trie grows and counts update to represent all inserted numbers.
traverse
Count Pairs Less Than high+1 (7) Computed
After querying and inserting all numbers, the count of pairs with XOR less than 7 is computed.
💡 This count includes all pairs with XOR strictly less than 7.
Line:countHigh = countPairsLessThan(nums, high + 1)
💡 This partial count is the first part of the final answer calculation.
traverse
Count Pairs Less Than low (2) Computed
Similarly, count pairs with XOR less than 2 by querying the trie with limit 2.
💡 This count excludes pairs with XOR equal or greater than 2.
Line:countLow = countPairsLessThan(nums, low)
💡 This partial count will be subtracted from the previous to get pairs in [low, high].
reconstruct
Calculate Final Answer: countHigh - countLow
Subtract countLow from countHigh to get the number of pairs with XOR in the range [2, 6].
💡 This subtraction isolates pairs whose XOR lies within the desired range.
Line:return countPairsLessThan(nums, high + 1) - countPairsLessThan(nums, low)
💡 The final answer is the count of pairs with XOR in [low, high].
class TrieNode:
def __init__(self):
self.children = [None, None]
self.count = 0
class Solution:
def countPairsLessThan(self, nums, limit):
root = TrieNode() # STEP 1
def insert(num):
node = root
for i in range(20, -1, -1): # STEP 2-3
bit = (num >> i) & 1
if not node.children[bit]:
node.children[bit] = TrieNode()
node = node.children[bit]
node.count += 1
def query(num, limit):
node = root
count = 0
for i in range(20, -1, -1): # STEP 4-7
if not node:
break
bit_num = (num >> i) & 1
bit_lim = (limit >> i) & 1
if bit_lim == 1:
if node.children[bit_num]:
count += node.children[bit_num].count
node = node.children[1 - bit_num]
else:
node = node.children[bit_num]
return count
result = 0
for num in nums: # STEP 8
result += query(num, limit)
insert(num)
return result
def countPairs(self, nums, low, high): # STEP 9-11
return self.countPairsLessThan(nums, high + 1) - self.countPairsLessThan(nums, low)
if __name__ == '__main__':
nums = [1,4,2,7]
low, high = 2, 6
sol = Solution()
print(sol.countPairs(nums, low, high)) # Output: 6
📊
Count Pairs With XOR in a Range - Watch the Algorithm Execute, Step by Step
Watching the trie construction and query process step-by-step reveals how bitwise properties enable efficient counting of XOR pairs, which is difficult to grasp from code alone.
Step 1/11
·Active fill★Answer cell
insert
insert
1
0
insert
1
000000000000000000001
query
4
0
query
4
00
query
4
00
query
4
001
insert
4
0000000000000000000011
query
query
countPairs
Search: 6
Key Takeaways
✓ The trie efficiently encodes all numbers bitwise, enabling fast queries to count pairs with XOR below a limit.
This insight is hard to see from code alone because the bitwise trie structure and counting logic are abstract without visualization.
✓ Querying the trie uses bitwise comparisons of the number and limit bits to decide which branches to count and traverse.
Understanding how limit bits guide traversal and counting clarifies why the algorithm counts pairs correctly.
✓ The final answer is computed by subtracting counts of pairs with XOR less than low from those less than high+1.
This subtraction isolates pairs within the desired XOR range, a key step that is subtle without seeing intermediate counts.
Practice
(1/5)
1. Given the following code snippet for MapSum, after inserting keys "apple" with value 3 and "app" with value 2, what is the value of node.sum at the trie node corresponding to 'p' (the second character) after the second insert?
easy
A. 3
B. 5
C. 2
D. 0
Solution
Step 1: Trace insert of "apple" with value 3
Delta = 3 - 0 = 3. Root.sum += 3 -> 3. For each character 'a','p','p','l','e', node.sum += 3. After first insert, node at second 'p' has sum 3.
Step 2: Trace insert of "app" with value 2
Delta = 2 - 0 = 2. Root.sum += 2 -> 5. For 'a','p','p', node.sum += 2. Node at second 'p' sum was 3, now 3 + 2 = 5.
Final Answer:
Option B -> Option B
Quick Check:
Sum at second 'p' after both inserts is 5 [OK]
Hint: Sum accumulates delta on each insert per prefix node [OK]
Common Mistakes:
Forgetting to add delta on second insert
Confusing first and second 'p' nodes
Using old value instead of delta
2. What is the time complexity of the optimal trie-based solution for finding shortest unique prefixes for n words each of maximum length m?
medium
A. O(n^2 * m) because each prefix is compared with all other words
B. O(n * m) because each word is inserted and traversed once in the trie
C. O(n * m^2) due to nested prefix checks during insertion
D. O(n * log n * m) because sorting is involved before trie insertion
Solution
Step 1: Identify operations per word
Each word of length m is inserted into the trie once, updating frequency counts along the path.
Step 2: Analyze total complexity
Insertion and prefix search both take O(m) per word, so total is O(n * m). No sorting or nested loops over words occur.
Final Answer:
Option B -> Option B
Quick Check:
Trie insertion and search scale linearly with total characters [OK]
Hint: Trie operations scale with total characters, not pairwise comparisons [OK]
Common Mistakes:
Confusing trie insertion with pairwise prefix checks
Assuming sorting is required
3. Suppose the problem changes: you want to find the maximum XOR of two numbers in an array, but now numbers can be negative 32-bit integers. Which modification to the bitwise trie approach is necessary to handle negative numbers correctly?
hard
A. Use two's complement 32-bit representation and treat bits as unsigned during trie insertion and traversal
B. Ignore sign bit and only build trie from absolute values of numbers
C. Use 64-bit representation to store numbers in trie to avoid sign issues
D. Preprocess numbers by adding a large constant to make all numbers positive before building trie
Solution
Step 1: Understand representation of negative numbers
Negative integers in 32-bit are stored in two's complement form, which can be treated as unsigned bit patterns for trie operations.
Step 2: Modify trie insertion and traversal
Insert and traverse bits as unsigned 32-bit integers, including the sign bit, to correctly handle negative numbers.
Step 3: Avoid incorrect approaches
Using 64-bit or adding constants changes problem semantics; ignoring sign bit loses information.
Hint: Two's complement bits can be treated as unsigned [OK]
Common Mistakes:
Ignoring sign bit or converting to absolute values
4. Suppose the problem changes: now the stream can contain repeated queries of the same letter, and you want to support queries that allow reusing letters multiple times to form words (e.g., words can be formed by concatenating repeated suffixes). Which modification to the optimal StreamChecker approach correctly supports this reuse without degrading query time significantly?
hard
A. Keep the same reversed trie and stream buffer, but on each query, perform a DFS from the root to find all possible suffix matches allowing repeated traversal of nodes.
B. Modify the trie to store counts of repeated letters and update the stream buffer to track counts, then traverse accordingly.
C. Increase the stream buffer size to the total length of all words to capture all repeated suffixes.
D. Use a suffix automaton or Aho-Corasick automaton instead of a trie to handle repeated patterns efficiently in queries.
Solution
Step 1: Understand the reuse requirement
Allowing repeated letters and concatenations means suffix matching alone is insufficient; need to handle overlapping patterns efficiently.
Step 2: Evaluate data structures
DFS on trie is expensive and can degrade query time. Counting repeated letters is complex and error-prone. Increasing buffer size is inefficient. Suffix automaton or Aho-Corasick automaton efficiently handle repeated patterns and multiple matches.
Step 3: Choose optimal solution
Using suffix or Aho-Corasick automaton supports repeated pattern matching with efficient query time.
5. Suppose the Word Break problem is modified so that dictionary words can be reused unlimited times in the segmentation. Which modification to the algorithm is necessary to correctly handle this variant?
hard
A. Modify the inner loop to continue checking all prefixes even after dp[i] is set to True
B. Change dp array to count number of ways instead of boolean flags
C. Remove the dp array and use pure recursion with memoization only
D. No change needed; the original DP with trie approach already supports reuse
Solution
Step 1: Understand reuse impact on DP
When reuse is allowed, multiple segmentations can overlap. The DP must consider all possible prefixes, not stop at first True.
Step 2: Modify inner loop behavior
Breaking early when dp[i] is True prevents exploring all segmentations. Removing the break allows full exploration, correctly handling reuse.
Final Answer:
Option A -> Option A
Quick Check:
Allowing all prefixes ensures reuse is handled [OK]
Hint: Breaking early blocks multiple reuse paths [OK]
Common Mistakes:
Assuming original DP supports reuse without change
Changing dp to count ways unnecessarily
Removing dp and relying on recursion only
Recommended
Ready to prove it?
Pass the test and this pattern moves unknownstrongwhile earning +1 Grip toward your certificate.