Bird
Raised Fist0
Interview PreptrieshardGoogleAmazon

Count Pairs With XOR in a Range

Choose your preparation mode4 modes available
🎯
Count Pairs With XOR in a Range
hardTRIEGoogleAmazon

Imagine you have a large dataset of encrypted IDs and want to quickly find how many pairs have an XOR value within a certain security threshold range.

💡 This problem involves counting pairs based on bitwise XOR values within a range. Beginners often struggle because XOR operations and bitwise tries are less intuitive than standard data structures, and the problem requires efficient counting rather than just computing XOR.
📋
Problem Statement

Given an integer array nums and two integers low and high, return the number of pairs (i, j) where 0 <= i < j < nums.length and the XOR of nums[i] and nums[j] lies in the inclusive range [low, high].

1 ≤ nums.length ≤ 10^50 ≤ nums[i] ≤ 10^60 ≤ low ≤ high ≤ 10^6
💡
Example
Input"nums = [1,4,2,7], low = 2, high = 6"
Output6

Pairs with XOR in [2,6] are (1,4)=5, (1,2)=3, (4,2)=6, (4,7)=3, (2,7)=5, (1,7)=6

  • All elements are zero → pairs XOR is zero, count depends on range
  • low and high are zero → count pairs with XOR exactly zero
  • Array with one element → no pairs, output 0
  • Maximum element values near 10^6 → test bit trie correctness and performance
⚠️
Common Mistakes
Not handling the inclusive range correctly

Incorrect count due to off-by-one errors

✅ Use countPairsLessThan(high + 1) - countPairsLessThan(low) to handle inclusive range

Incorrect bit traversal order in trie

Wrong counts or runtime errors

✅ Traverse bits from most significant to least significant consistently

Not resetting trie between queries

Counts accumulate incorrectly, leading to wrong answers

✅ Create a new trie root before each countPairsLessThan call

Using 32 bits instead of minimal bits needed

Unnecessary overhead and slower performance

✅ Use 21 bits (ceil(log2(10^6))) to optimize traversal

🧠
Brute Force (Nested Loops)
💡 Starting with brute force helps understand the problem's core: counting pairs and XOR operation. Though inefficient, it clarifies what we are counting and why optimization is needed.

Intuition

Check every pair of elements, compute their XOR, and count if it lies within [low, high].

Algorithm

  1. Initialize count to 0.
  2. For each element nums[i], iterate over all elements nums[j] with j > i.
  3. Compute XOR of nums[i] and nums[j].
  4. If XOR is between low and high inclusive, increment count.
  5. Return count.
💡 The nested loops and direct XOR checks are straightforward but lead to O(n^2) complexity, which is impractical for large arrays.
</>
Code
def countPairs(nums, low, high):
    count = 0
    n = len(nums)
    for i in range(n):
        for j in range(i + 1, n):
            xor_val = nums[i] ^ nums[j]
            if low <= xor_val <= high:
                count += 1
    return count

# Driver code
if __name__ == '__main__':
    nums = [1,4,2,7]
    low, high = 2, 6
    print(countPairs(nums, low, high))  # Output: 6
Line Notes
count = 0Initialize counter to accumulate valid pairs
for i in range(n)Outer loop picks the first element of the pair
for j in range(i + 1, n)Inner loop picks the second element ensuring i < j
xor_val = nums[i] ^ nums[j]Compute XOR of the current pair
if low <= xor_val <= highCheck if XOR lies within the given range
count += 1Increment count for valid pairs
return countReturn total count after checking all pairs
public class Solution {
    public int countPairs(int[] nums, int low, int high) {
        int count = 0;
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                int xorVal = nums[i] ^ nums[j];
                if (xorVal >= low && xorVal <= high) {
                    count++;
                }
            }
        }
        return count;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int[] nums = {1,4,2,7};
        int low = 2, high = 6;
        System.out.println(sol.countPairs(nums, low, high)); // Output: 6
    }
}
Line Notes
int count = 0;Initialize counter for valid pairs
for (int i = 0; i < n; i++)Outer loop selects first element
for (int j = i + 1; j < n; j++)Inner loop selects second element ensuring i < j
int xorVal = nums[i] ^ nums[j];Calculate XOR of pair
if (xorVal >= low && xorVal <= high)Check if XOR is within range
count++;Increment count for valid pairs
return count;Return total count after all pairs checked
#include <iostream>
#include <vector>
using namespace std;

int countPairs(vector<int>& nums, int low, int high) {
    int count = 0;
    int n = nums.size();
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j < n; j++) {
            int xorVal = nums[i] ^ nums[j];
            if (xorVal >= low && xorVal <= high) {
                count++;
            }
        }
    }
    return count;
}

int main() {
    vector<int> nums = {1,4,2,7};
    int low = 2, high = 6;
    cout << countPairs(nums, low, high) << endl; // Output: 6
    return 0;
}
Line Notes
int count = 0;Initialize count for valid pairs
for (int i = 0; i < n; i++)Outer loop picks first element
for (int j = i + 1; j < n; j++)Inner loop picks second element ensuring i < j
int xorVal = nums[i] ^ nums[j];Compute XOR of the pair
if (xorVal >= low && xorVal <= high)Check if XOR is in the range
count++;Increment count for valid pairs
return count;Return total count after all pairs processed
function countPairs(nums, low, high) {
    let count = 0;
    const n = nums.length;
    for (let i = 0; i < n; i++) {
        for (let j = i + 1; j < n; j++) {
            const xorVal = nums[i] ^ nums[j];
            if (xorVal >= low && xorVal <= high) {
                count++;
            }
        }
    }
    return count;
}

// Driver code
const nums = [1,4,2,7];
const low = 2, high = 6;
console.log(countPairs(nums, low, high)); // Output: 6
Line Notes
let count = 0;Initialize counter for valid pairs
for (let i = 0; i < n; i++)Outer loop selects first element
for (let j = i + 1; j < n; j++)Inner loop selects second element ensuring i < j
const xorVal = nums[i] ^ nums[j];Calculate XOR of the pair
if (xorVal >= low && xorVal <= high)Check if XOR is within the range
count++;Increment count for valid pairs
return count;Return total count after all pairs checked
⏱
Complexity
TimeO(n^2)
SpaceO(1)

Two nested loops over n elements lead to n*(n-1)/2 pairs, so O(n^2) time. No extra space except counters.

💡 For n=1000, this means about 500,000 operations, which is slow but still feasible; for n=10^5, it becomes 5 billion operations, which is impractical.
Interview Verdict: TLE

This approach times out on large inputs, showing the need for more efficient algorithms using tries or bitwise optimizations.

🧠
Using Bitwise Trie to Count XOR Pairs Less Than K
💡 This approach introduces a bit trie to efficiently count how many numbers have XOR with a given number less than a threshold. It builds intuition for the final solution by focusing on counting pairs with XOR less than a single bound.

Intuition

Insert numbers into a bit trie and for each number, count how many previously inserted numbers have XOR with it less than the limit by traversing the trie bit-by-bit.

Algorithm

  1. Define a bit trie node with children for bits 0 and 1 and a count of numbers passing through.
  2. For each number in nums, before inserting it, query the trie to count how many numbers have XOR with it less than the limit.
  3. Insert the current number into the trie.
  4. Sum all counts from queries to get total pairs with XOR less than the limit.
💡 The key insight is that counting pairs with XOR less than a limit can be done by traversing the trie according to bits of the number and limit simultaneously.
</>
Code
class TrieNode:
    def __init__(self):
        self.children = [None, None]
        self.count = 0

class Solution:
    def countPairsLessThan(self, nums, limit):
        root = TrieNode()
        def insert(num):
            node = root
            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

        def query(num, limit):
            node = root
            count = 0
            for i in range(20, -1, -1):
                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:
            result += query(num, limit)
            insert(num)
        return result

# Driver code
if __name__ == '__main__':
    nums = [1,4,2,7]
    low, high = 2, 6
    sol = Solution()
    print(sol.countPairsLessThan(nums, high + 1))  # Count pairs with XOR < high+1
Line Notes
class TrieNode:Defines trie node structure for bitwise trie
self.children = [None, None]Two children for bits 0 and 1
self.count = 0Count of numbers passing through this node
for i in range(20, -1, -1)Traverse bits from most significant to least (21 bits for max 10^6)
bit = (num >> i) & 1Extract i-th bit of number
if not node.children[bit]Create child node if missing
node.count += 1Increment count for prefix path
if bit_lim == 1:If limit bit is 1, add counts from subtree matching bit_num
count += node.children[bit_num].countAdd counts of numbers with matching bit prefix
node = node.children[1 - bit_num]Move to opposite bit child to continue query
result += query(num, limit)Accumulate count of pairs with XOR less than limit
insert(num)Insert current number into trie for future queries
class TrieNode {
    TrieNode[] children = new TrieNode[2];
    int count = 0;
}

public class Solution {
    TrieNode root = new TrieNode();

    void insert(int num) {
        TrieNode node = root;
        for (int i = 20; i >= 0; i--) {
            int bit = (num >> i) & 1;
            if (node.children[bit] == null) {
                node.children[bit] = new TrieNode();
            }
            node = node.children[bit];
            node.count++;
        }
    }

    int query(int num, int limit) {
        TrieNode node = root;
        int count = 0;
        for (int i = 20; i >= 0; i--) {
            if (node == null) break;
            int bitNum = (num >> i) & 1;
            int bitLim = (limit >> i) & 1;
            if (bitLim == 1) {
                if (node.children[bitNum] != null) {
                    count += node.children[bitNum].count;
                }
                node = node.children[1 - bitNum];
            } else {
                node = node.children[bitNum];
            }
        }
        return count;
    }

    public int countPairsLessThan(int[] nums, int limit) {
        int result = 0;
        for (int num : nums) {
            result += query(num, limit);
            insert(num);
        }
        return result;
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int[] nums = {1,4,2,7};
        int low = 2, high = 6;
        System.out.println(sol.countPairsLessThan(nums, high + 1)); // Output pairs with XOR < high+1
    }
}
Line Notes
TrieNode[] children = new TrieNode[2];Children for bits 0 and 1
int count = 0;Count of numbers passing through node
for (int i = 20; i >= 0; i--)Traverse bits from MSB to LSB
int bit = (num >> i) & 1;Extract bit i of number
if (node.children[bit] == null)Create child if missing
node.count++;Increment count for prefix path
if (bitLim == 1)If limit bit is 1, add counts from matching subtree
count += node.children[bitNum].count;Add counts of numbers with matching bit prefix
node = node.children[1 - bitNum];Move to opposite bit child for query
result += query(num, limit);Accumulate count of pairs with XOR less than limit
insert(num);Insert current number into trie
#include <iostream>
#include <vector>
using namespace std;

struct TrieNode {
    TrieNode* children[2] = {nullptr, nullptr};
    int count = 0;
};

class Solution {
    TrieNode* root = new TrieNode();
public:
    void insert(int num) {
        TrieNode* node = root;
        for (int i = 20; i >= 0; i--) {
            int bit = (num >> i) & 1;
            if (!node->children[bit]) {
                node->children[bit] = new TrieNode();
            }
            node = node->children[bit];
            node->count++;
        }
    }

    int query(int num, int limit) {
        TrieNode* node = root;
        int count = 0;
        for (int i = 20; i >= 0; i--) {
            if (!node) break;
            int bitNum = (num >> i) & 1;
            int bitLim = (limit >> i) & 1;
            if (bitLim == 1) {
                if (node->children[bitNum]) {
                    count += node->children[bitNum]->count;
                }
                node = node->children[1 - bitNum];
            } else {
                node = node->children[bitNum];
            }
        }
        return count;
    }

    int countPairsLessThan(vector<int>& nums, int limit) {
        int result = 0;
        for (int num : nums) {
            result += query(num, limit);
            insert(num);
        }
        return result;
    }
};

int main() {
    vector<int> nums = {1,4,2,7};
    int low = 2, high = 6;
    Solution sol;
    cout << sol.countPairsLessThan(nums, high + 1) << endl; // Output pairs with XOR < high+1
    return 0;
}
Line Notes
TrieNode* children[2] = {nullptr, nullptr};Children pointers for bits 0 and 1
int count = 0;Count of numbers passing through node
for (int i = 20; i >= 0; i--)Traverse bits from most to least significant
int bit = (num >> i) & 1;Extract bit i of number
if (!node->children[bit])Create child node if missing
node->count++;Increment count for prefix path
if (bitLim == 1)If limit bit is 1, add counts from matching subtree
count += node->children[bitNum]->count;Add counts of numbers with matching bit prefix
node = node->children[1 - bitNum];Move to opposite bit child for query
result += query(num, limit);Accumulate count of pairs with XOR less than limit
insert(num);Insert current number into trie
class TrieNode {
    constructor() {
        this.children = [null, null];
        this.count = 0;
    }
}

class Solution {
    constructor() {
        this.root = new TrieNode();
    }

    insert(num) {
        let node = this.root;
        for (let i = 20; i >= 0; i--) {
            let bit = (num >> i) & 1;
            if (!node.children[bit]) {
                node.children[bit] = new TrieNode();
            }
            node = node.children[bit];
            node.count++;
        }
    }

    query(num, limit) {
        let node = this.root;
        let count = 0;
        for (let i = 20; i >= 0; i--) {
            if (!node) break;
            let bitNum = (num >> i) & 1;
            let bitLim = (limit >> i) & 1;
            if (bitLim === 1) {
                if (node.children[bitNum]) {
                    count += node.children[bitNum].count;
                }
                node = node.children[1 - bitNum];
            } else {
                node = node.children[bitNum];
            }
        }
        return count;
    }

    countPairsLessThan(nums, limit) {
        let result = 0;
        for (let num of nums) {
            result += this.query(num, limit);
            this.insert(num);
        }
        return result;
    }
}

// Driver code
const nums = [1,4,2,7];
const low = 2, high = 6;
const sol = new Solution();
console.log(sol.countPairsLessThan(nums, high + 1)); // Output pairs with XOR < high+1
Line Notes
this.children = [null, null];Children array for bits 0 and 1
this.count = 0;Count of numbers passing through node
for (let i = 20; i >= 0; i--)Traverse bits from most significant to least
let bit = (num >> i) & 1;Extract bit i of number
if (!node.children[bit])Create child node if missing
node.count++;Increment count for prefix path
if (bitLim === 1)If limit bit is 1, add counts from matching subtree
count += node.children[bitNum].count;Add counts of numbers with matching bit prefix
node = node.children[1 - bitNum];Move to opposite bit child for query
result += this.query(num, limit);Accumulate count of pairs with XOR less than limit
this.insert(num);Insert current number into trie
⏱
Complexity
TimeO(n * log M)
SpaceO(n * log M)

Each number is inserted and queried in a trie of height ~log M (M=10^6), so O(n log M) time and space.

💡 For n=10^5 and M=10^6, log M ~20, so about 2 million operations, which is efficient enough.
Interview Verdict: Accepted

This approach efficiently counts pairs with XOR less than a limit, a crucial step towards solving the full range problem.

🧠
Optimal: Count Pairs With XOR in [low, high] Using Bit Trie
💡 This approach uses the previous method twice to count pairs with XOR less than or equal to high and less than low, then subtracts to get pairs in [low, high]. It combines bit trie queries with prefix counting for optimal performance.

Intuition

Number of pairs with XOR in [low, high] = count pairs with XOR < high+1 - count pairs with XOR < low.

Algorithm

  1. Implement a function to count pairs with XOR less than a given limit using a bit trie (from Approach 2).
  2. Compute countHigh = countPairsLessThan(nums, high + 1).
  3. Compute countLow = countPairsLessThan(nums, low).
  4. Return countHigh - countLow as the number of pairs with XOR in [low, high].
💡 This subtraction trick avoids complex range queries and leverages the efficient counting method for simpler bounds.
</>
Code
class TrieNode:
    def __init__(self):
        self.children = [None, None]
        self.count = 0

class Solution:
    def countPairsLessThan(self, nums, limit):
        root = TrieNode()
        def insert(num):
            node = root
            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

        def query(num, limit):
            node = root
            count = 0
            for i in range(20, -1, -1):
                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:
            result += query(num, limit)
            insert(num)
        return result

    def countPairs(self, nums, low, high):
        return self.countPairsLessThan(nums, high + 1) - self.countPairsLessThan(nums, low)

# Driver code
if __name__ == '__main__':
    nums = [1,4,2,7]
    low, high = 2, 6
    sol = Solution()
    print(sol.countPairs(nums, low, high))  # Output: 6
Line Notes
class TrieNode:Defines trie node structure
def countPairsLessThan(self, nums, limit):Counts pairs with XOR less than limit
for i in range(20, -1, -1):Traverse bits from MSB to LSB
if bit_lim == 1:Add counts from matching subtree when limit bit is 1
result += query(num, limit)Accumulate count for each number
def countPairs(self, nums, low, high):Final function to count pairs in [low, high]
return self.countPairsLessThan(nums, high + 1) - self.countPairsLessThan(nums, low)Subtract counts to get pairs in range
class TrieNode {
    TrieNode[] children = new TrieNode[2];
    int count = 0;
}

public class Solution {
    TrieNode root = new TrieNode();

    void insert(int num) {
        TrieNode node = root;
        for (int i = 20; i >= 0; i--) {
            int bit = (num >> i) & 1;
            if (node.children[bit] == null) {
                node.children[bit] = new TrieNode();
            }
            node = node.children[bit];
            node.count++;
        }
    }

    int query(int num, int limit) {
        TrieNode node = root;
        int count = 0;
        for (int i = 20; i >= 0; i--) {
            if (node == null) break;
            int bitNum = (num >> i) & 1;
            int bitLim = (limit >> i) & 1;
            if (bitLim == 1) {
                if (node.children[bitNum] != null) {
                    count += node.children[bitNum].count;
                }
                node = node.children[1 - bitNum];
            } else {
                node = node.children[bitNum];
            }
        }
        return count;
    }

    int countPairsLessThan(int[] nums, int limit) {
        root = new TrieNode();
        int result = 0;
        for (int num : nums) {
            result += query(num, limit);
            insert(num);
        }
        return result;
    }

    public int countPairs(int[] nums, int low, int high) {
        return countPairsLessThan(nums, high + 1) - countPairsLessThan(nums, low);
    }

    public static void main(String[] args) {
        Solution sol = new Solution();
        int[] nums = {1,4,2,7};
        int low = 2, high = 6;
        System.out.println(sol.countPairs(nums, low, high)); // Output: 6
    }
}
Line Notes
TrieNode[] children = new TrieNode[2];Children for bits 0 and 1
int count = 0;Count of numbers passing through node
for (int i = 20; i >= 0; i--)Traverse bits from MSB to LSB
if (bitLim == 1)Add counts from matching subtree when limit bit is 1
result += query(num, limit);Accumulate count for each number
int countPairs(int[] nums, int low, int high)Final function to count pairs in range
return countPairsLessThan(nums, high + 1) - countPairsLessThan(nums, low);Subtract counts to get pairs in [low, high]
#include <iostream>
#include <vector>
using namespace std;

struct TrieNode {
    TrieNode* children[2] = {nullptr, nullptr};
    int count = 0;
};

class Solution {
    TrieNode* root;
public:
    Solution() {
        root = new TrieNode();
    }

    void insert(int num) {
        TrieNode* node = root;
        for (int i = 20; i >= 0; i--) {
            int bit = (num >> i) & 1;
            if (!node->children[bit]) {
                node->children[bit] = new TrieNode();
            }
            node = node->children[bit];
            node->count++;
        }
    }

    int query(int num, int limit) {
        TrieNode* node = root;
        int count = 0;
        for (int i = 20; i >= 0; i--) {
            if (!node) break;
            int bitNum = (num >> i) & 1;
            int bitLim = (limit >> i) & 1;
            if (bitLim == 1) {
                if (node->children[bitNum]) {
                    count += node->children[bitNum]->count;
                }
                node = node->children[1 - bitNum];
            } else {
                node = node->children[bitNum];
            }
        }
        return count;
    }

    int countPairsLessThan(vector<int>& nums, int limit) {
        root = new TrieNode();
        int result = 0;
        for (int num : nums) {
            result += query(num, limit);
            insert(num);
        }
        return result;
    }

    int countPairs(vector<int>& nums, int low, int high) {
        return countPairsLessThan(nums, high + 1) - countPairsLessThan(nums, low);
    }
};

int main() {
    vector<int> nums = {1,4,2,7};
    int low = 2, high = 6;
    Solution sol;
    cout << sol.countPairs(nums, low, high) << endl; // Output: 6
    return 0;
}
Line Notes
TrieNode* children[2] = {nullptr, nullptr};Children pointers for bits 0 and 1
int count = 0;Count of numbers passing through node
for (int i = 20; i >= 0; i--)Traverse bits from MSB to LSB
if (bitLim == 1)Add counts from matching subtree when limit bit is 1
result += query(num, limit);Accumulate count for each number
int countPairs(vector<int>& nums, int low, int high)Final function to count pairs in range
return countPairsLessThan(nums, high + 1) - countPairsLessThan(nums, low);Subtract counts to get pairs in [low, high]
class TrieNode {
    constructor() {
        this.children = [null, null];
        this.count = 0;
    }
}

class Solution {
    constructor() {
        this.root = new TrieNode();
    }

    insert(num) {
        let node = this.root;
        for (let i = 20; i >= 0; i--) {
            let bit = (num >> i) & 1;
            if (!node.children[bit]) {
                node.children[bit] = new TrieNode();
            }
            node = node.children[bit];
            node.count++;
        }
    }

    query(num, limit) {
        let node = this.root;
        let count = 0;
        for (let i = 20; i >= 0; i--) {
            if (!node) break;
            let bitNum = (num >> i) & 1;
            let bitLim = (limit >> i) & 1;
            if (bitLim === 1) {
                if (node.children[bitNum]) {
                    count += node.children[bitNum].count;
                }
                node = node.children[1 - bitNum];
            } else {
                node = node.children[bitNum];
            }
        }
        return count;
    }

    countPairsLessThan(nums, limit) {
        this.root = new TrieNode();
        let result = 0;
        for (let num of nums) {
            result += this.query(num, limit);
            this.insert(num);
        }
        return result;
    }

    countPairs(nums, low, high) {
        return this.countPairsLessThan(nums, high + 1) - this.countPairsLessThan(nums, low);
    }
}

// Driver code
const nums = [1,4,2,7];
const low = 2, high = 6;
const sol = new Solution();
console.log(sol.countPairs(nums, low, high)); // Output: 6
Line Notes
this.children = [null, null];Children array for bits 0 and 1
this.count = 0;Count of numbers passing through node
for (let i = 20; i >= 0; i--)Traverse bits from MSB to LSB
if (bitLim === 1)Add counts from matching subtree when limit bit is 1
result += this.query(num, limit);Accumulate count for each number
countPairs(nums, low, high)Final function to count pairs in range
return this.countPairsLessThan(nums, high + 1) - this.countPairsLessThan(nums, low);Subtract counts to get pairs in [low, high]
⏱
Complexity
TimeO(n * log M)
SpaceO(n * log M)

Two calls to countPairsLessThan each take O(n log M), total O(n log M). Space for trie is also O(n log M).

💡 This is efficient for large inputs, making it suitable for interview coding.
Interview Verdict: Accepted

This is the optimal approach combining bit trie and prefix counting to solve the problem efficiently.

📊
All Approaches - One-Glance Tradeoffs
💡 In interviews, always start by explaining brute force, then jump to the optimal trie-based approach for coding.
ApproachTimeSpaceStack RiskReconstructUse In Interview
1. Brute ForceO(n^2)O(1)NoN/AMention only - never code due to inefficiency
2. Bit Trie Counting XOR < limitO(n log M)O(n log M)NoN/AExplain as building block for final solution
3. Optimal Range Count Using TrieO(n log M)O(n log M)NoN/ACode this approach for final solution
💼
Interview Strategy
💡 Use this guide to understand the problem deeply, starting from brute force to optimal trie-based solutions. Practice explaining each step clearly and test edge cases.

How to Present

Clarify the problem and constraints with the interviewer.Explain the brute force approach and its inefficiency.Introduce the bit trie data structure and how it helps count pairs with XOR less than a limit.Show how to combine two counts to get pairs in the XOR range.Write clean, modular code and test with examples.

Time Allocation

Clarify: 3min → Approach: 5min → Code: 15min → Test: 7min. Total ~30min

What the Interviewer Tests

Understanding of bitwise operations, trie data structure, ability to optimize brute force, and clear communication of complex ideas.

Common Follow-ups

  • What if the input array is very large? → Use tries for O(n log M) solution.
  • Can you find pairs with XOR exactly equal to a value? → Modify trie queries accordingly.
💡 These follow-ups test your ability to adapt the trie approach and understand XOR properties deeply.
🔍
Pattern Recognition

When to Use

1) Problem involves pairs and XOR operation; 2) Need to count pairs with XOR constraints; 3) Large input size requiring optimization; 4) Bitwise trie or prefix counting hints.

Signature Phrases

'count pairs with XOR in range''XOR less than or equal to K''bitwise trie''prefix counting'

NOT This Pattern When

Problems involving XOR but without counting pairs or range queries, e.g., single maximum XOR value problems without counting.

Similar Problems

Count Pairs With XOR Less Than K - simpler counting problemMaximum XOR of Two Numbers in an Array - finding max XOR using trieXOR Queries of a Subarray - prefix XOR and trie usage

Practice

(1/5)
1. Consider the following buggy Trie insert method. Which line contains the subtle bug that causes search to fail for inserted words?
medium
A. Line where node = self.root is assigned
B. Line where node = node.children[ch] is assigned inside the loop
C. Line where isEnd flag should be set but is missing
D. Line where the if condition checks if ch not in node.children

Solution

  1. Step 1: Identify missing isEnd flag set

    After inserting all characters, isEnd must be set true to mark word end. This line is missing.
  2. Step 2: Understand impact on search

    Without isEnd=true, search returns false even if path exists, because node.isEnd remains false.
  3. Final Answer:

    Option C -> Option C
  4. Quick Check:

    Missing isEnd assignment causes search failure for inserted words [OK]
Hint: isEnd must be set after inserting entire word [OK]
Common Mistakes:
  • Forgetting to mark isEnd after insertion
2. Consider the following buggy code snippet for the longest word in dictionary problem. Which line contains the subtle bug that can cause incorrect results?
medium
A. Line: queue.append(child) without checking if child.word is not None
B. Line: for ch in sorted(node.children.keys())
C. Line: node.word = word in insert method
D. Line: trie.insert(word) in the for loop

Solution

  1. Step 1: Identify enqueue condition

    The BFS queue should only include nodes representing complete words (child.word != None) to ensure all prefixes are valid.
  2. Step 2: Locate the bug

    The code enqueues all children regardless of whether they represent complete words, allowing invalid prefixes to be considered, breaking the prefix condition.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Enqueueing invalid prefix nodes causes incorrect longest word [OK]
Hint: Only enqueue nodes with complete words in BFS [OK]
Common Mistakes:
  • Enqueueing all children regardless of word completion
  • Misplacing word assignment in insert
  • Sorting children incorrectly
3. Suppose the problem changes: words can be repeated multiple times and you want the shortest unique prefix that distinguishes each occurrence (including duplicates). Which modification to the trie-based solution correctly handles this?
hard
A. Store a list of word indices at each node and find prefix unique to each index.
B. Modify frequency increments to count each occurrence separately and return prefix only if frequency equals the occurrence count.
C. No change needed; the existing frequency counts handle duplicates naturally.
D. Sort words first and compare adjacent duplicates to assign unique prefixes.

Solution

  1. Step 1: Understand duplicate occurrence challenge

    Duplicates share the same prefix path and frequency counts, so frequency alone cannot distinguish occurrences.
  2. Step 2: Identify solution

    Storing word indices at each node allows tracking which occurrences pass through, enabling unique prefix per occurrence.
  3. Step 3: Why other options fail

    No change needed; the existing frequency counts handle duplicates naturally. ignores duplicates; B misuses frequency counts; D breaks original order and misses non-adjacent duplicates.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    Tracking indices enables unique prefixes per duplicate [OK]
Hint: Frequency counts alone can't distinguish identical duplicates [OK]
Common Mistakes:
  • Assuming frequency counts suffice for duplicates
  • Relying on sorting only
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

  1. Step 1: Understand the reuse requirement

    Allowing repeated letters and concatenations means suffix matching alone is insufficient; need to handle overlapping patterns efficiently.
  2. 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.
  3. Step 3: Choose optimal solution

    Using suffix or Aho-Corasick automaton supports repeated pattern matching with efficient query time.
  4. Final Answer:

    Option D -> Option D
  5. Quick Check:

    Suffix automaton handles repeated substrings efficiently [OK]
Hint: Suffix automaton handles repeated patterns efficiently [OK]
Common Mistakes:
  • Trying DFS on trie causing exponential time
  • Increasing buffer size unboundedly
  • Tracking counts in trie nodes incorrectly
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

  1. 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.
  2. 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.
  3. Final Answer:

    Option A -> Option A
  4. 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 unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard