Approach 2: Trie with Optimizations¶
This solution uses a Trie (prefix tree) to efficiently find the longest common prefix between integers from two arrays. The core idea is to insert each number from the first array (arr1) into a Trie, and then check for the longest common prefix with each number from the second array (arr2).
Explanation:¶
TrieNode Structure¶
TrieNode* children[10];: Each node in the Trie has 10 children, corresponding to digits0-9. This structure helps store digit-based paths as numbers are inserted into the Trie.-
pool[100001];: This is a memory pool used to pre-allocate Trie nodes. Instead of dynamically allocating new nodes during runtime (which is slower), a large block of memory is pre-allocated and used as needed. This avoids performance overhead from memory allocation. -
Why use a memory pool?
- In competitive programming or situations with strict performance requirements, frequent memory allocation (using
new) can be slow. By pre-allocating memory, the program runs faster and uses less system overhead for memory management.
- In competitive programming or situations with strict performance requirements, frequent memory allocation (using
Trie Class¶
newNode() Function¶
- This function returns a pointer to a new TrieNode from the pre-allocated
pool. -
TrieNode* res = &pool[k++];: Each call returns a node from thepool, incrementingkto track the next available node. This eliminates the need for dynamic allocation usingnewand ensures we don’t exceed the memory limit. -
Initialization of
children: - After allocating a new node from the pool, all child pointers (
children[0]tochildren[9]) are initialized tonullptrto indicate there are no children for that node yet.
insert(int x) Function¶
- Reversing the Number (
y = y * 10 + x % 10): - This part of the code reverses the digits of the number
xbefore inserting it into the Trie. x % 10extracts the least significant digit ofx.-
y = y * 10 + x % 10;: Rebuildsyin reversed order by moving the current value ofyone digit to the left (y * 10) and adding the current least significant digit (x % 10). -
Why reverse the digits?
- The problem requires comparing the leftmost digits of the numbers (i.e., the most significant digits). By reversing the digits before insertion, we process these digits first, making it easier to find common prefixes.
-
Why initialize
y = 1and noty = 0?- This initialization ensures that the digits of
xare reversed and processed correctly, particularly whenxhas trailing zeros, which would turn into leading zeros after reversal. If we had initializedy = 0, for instance, whenx = 123000, the result would bey = 321instead of000321, leading to incorrect results.
- This initialization ensures that the digits of
getMaxPrefixLength(int x) Function¶
-
This function finds the longest common prefix between
xand any number in the Trie. -
Reversing the Number (
y = y * 10 + x % 10):- Similar to
insert(), the digits ofxare reversed. The reversedyis used to traverse the Trie, following the path of common digits from the most significant to the least significant.
- Similar to
-
Trie Traversal:
- We start from the root node and traverse the Trie as long as the corresponding child node exists for the current digit (
y % 10). Each step down the Trie represents a match in the prefix betweenxand one or more numbers in the Trie.
- We start from the root node and traverse the Trie as long as the corresponding child node exists for the current digit (
-
Why initialize
res = -1?:- We start with
res = -1because, even if there's no match, we will increment it once in the first iteration, thenres = 0. When the first match is found, the result becomes1for one-digit matches, and so on.
- We start with
Solution Class¶
- Inserting all numbers from
arr1to the Trie: -
Each number in
arr1is inserted into the Trie. -
Querying the longest common prefix with
arr2: -
For each number in
arr2, the function checks the maximum common prefix length between it and all numbers in the Trie usinggetMaxPrefixLength(). -
The result is updated with the maximum prefix length found for any number in
arr2.
Complexity¶
- Time complexity: $O(n \cdot \log_{10}(x) + m \cdot \log_{10}(x))$, where
nis the size ofarr1andmis the size ofarr2. - Inserting each number into the Trie takes $O(log_{10}(x))$, where
xis the number of digits in the number (sincexis at most10^8, it has at most 9 digits). -
Querying the longest prefix also takes $O(log_{10}(x))$.
-
Space complexity: $O(n \cdot \log_{10}(x))$, due to the space needed to store the digits of the numbers in the Trie.
Code¶
struct TrieNode {
TrieNode* children[10];
};
TrieNode pool[100001];
class Trie {
private:
int k = 0;
TrieNode* root = newNode();
TrieNode* newNode() {
TrieNode* res = &pool[k++];
for (int i = 0; i < 10; i++) {
res->children[i] = nullptr;
}
return res;
}
public:
void insert(int x) {
int y = 1;
while (x) {
y = y * 10 + x % 10;
x /= 10;
}
TrieNode* curr = root;
while (y > 1) {
x = y % 10;
y /= 10;
curr->children[x] = curr->children[x] ?: newNode();
curr = curr->children[x];
}
}
int getMaxPrefixLength(int x) {
int y = 1;
while (x) {
y = y * 10 + x % 10;
x /= 10;
}
TrieNode* curr = root;
int res = -1;
while (y > 0 && curr) {
++res;
curr = curr->children[y % 10];
y /= 10;
}
return res;
}
};
class Solution {
public:
int longestCommonPrefix(vector<int>& arr1, vector<int>& arr2) {
Trie tr;
int res = 0;
for (auto& x: arr1) tr.insert(x);
for (auto& x: arr2) res = max(res, tr.getMaxPrefixLength(x));
return res;
}
};