3043. Find the Length of the Longest Common Prefix
Problem Description
You have two arrays arr1
and arr2
containing positive integers.
A prefix of a positive integer is formed by taking one or more consecutive digits starting from the leftmost digit. For instance, 123
is a prefix of 12345
, but 234
is not since it doesn't start from the leftmost digit.
A common prefix between two integers a
and b
is an integer c
that serves as a prefix for both numbers. For example, the integers 5655359
and 56554
share common prefixes 5
, 56
, 565
, and 5655
. However, 1223
and 43456
have no common prefix at all.
Your task is to find the longest common prefix across all possible pairs (x, y)
where x
comes from arr1
and y
comes from arr2
. You need to return the length (number of digits) of this longest common prefix. If no pairs share any common prefix, return 0
.
For example:
- If
arr1 = [1, 10, 100]
andarr2 = [1000]
, the pairs would be(1, 1000)
,(10, 1000)
, and(100, 1000)
. The common prefixes are1
,10
, and100
respectively, with the longest being100
having length3
. - If
arr1 = [1, 2, 3]
andarr2 = [4, 4, 4]
, no pairs share a common prefix, so the answer would be0
.
Intuition
The key insight is that we need to check all possible prefixes efficiently. If we were to compare every number in arr1
with every number in arr2
and extract all their prefixes, it would be computationally expensive.
Instead, we can think about this problem differently. For any number, we can generate all its prefixes by repeatedly dividing by 10. For example, the number 12345
has prefixes: 12345
, 1234
, 123
, 12
, and 1
. We get these by doing 12345 // 10 = 1234
, 1234 // 10 = 123
, and so on.
Since we need to find common prefixes between numbers from two arrays, we can pre-compute all possible prefixes from one array and store them for quick lookup. A hash table (set) is perfect for this because it allows O(1)
lookup time.
The strategy becomes:
- Generate all prefixes for every number in
arr1
and store them in a set. This way, we have all potential prefix candidates fromarr1
ready. - For each number in
arr2
, generate its prefixes one by one (starting from the number itself and dividing by 10 repeatedly). - For each prefix of a number from
arr2
, check if it exists in our set. The first match we find will be the longest common prefix for that particular pair (since we're checking from longest to shortest). - Keep track of the maximum length among all common prefixes found.
This approach is efficient because we only need to generate prefixes once for arr1
and store them, then for each number in arr2
, we can quickly check which of its prefixes exist in our set.
Learn more about Trie patterns.
Solution Approach
The implementation uses a hash table (set in Python) to efficiently store and lookup prefixes.
Step 1: Build the prefix set from arr1
s = set()
for x in arr1:
while x:
s.add(x)
x //= 10
For each number x
in arr1
, we extract all its prefixes by repeatedly dividing by 10. For example, if x = 12345
:
- First iteration: add
12345
to set, thenx = 12345 // 10 = 1234
- Second iteration: add
1234
to set, thenx = 1234 // 10 = 123
- Third iteration: add
123
to set, thenx = 123 // 10 = 12
- Fourth iteration: add
12
to set, thenx = 12 // 10 = 1
- Fifth iteration: add
1
to set, thenx = 1 // 10 = 0
- Loop terminates when
x
becomes 0
Step 2: Find the longest common prefix with arr2
ans = 0
for x in arr2:
while x:
if x in s:
ans = max(ans, len(str(x)))
break
x //= 10
For each number x
in arr2
, we check its prefixes from longest to shortest:
- Start with the number itself
- If it exists in the set
s
, we've found a common prefix - Calculate its length using
len(str(x))
and update the maximum if needed - Use
break
to stop checking shorter prefixes (since we want the longest one for this number) - If not found, divide by 10 to get the next shorter prefix and continue
The key optimization here is the break
statement. Once we find a common prefix for a number from arr2
, we don't need to check its shorter prefixes since we're looking for the longest one.
Time Complexity: O(m * log M + n * log N)
where m
and n
are the lengths of arr1
and arr2
, and M
and N
are the maximum values in the arrays. The log
factor comes from the number of digits in each number.
Space Complexity: O(m * log M)
for storing all prefixes of numbers in arr1
.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example with arr1 = [12, 345, 1234]
and arr2 = [3456, 123, 45]
.
Step 1: Build prefix set from arr1
Starting with an empty set s = {}
:
For x = 12
:
- Add 12 to set:
s = {12}
x = 12 // 10 = 1
- Add 1 to set:
s = {12, 1}
x = 1 // 10 = 0
(stop)
For x = 345
:
- Add 345 to set:
s = {12, 1, 345}
x = 345 // 10 = 34
- Add 34 to set:
s = {12, 1, 345, 34}
x = 34 // 10 = 3
- Add 3 to set:
s = {12, 1, 345, 34, 3}
x = 3 // 10 = 0
(stop)
For x = 1234
:
- Add 1234 to set:
s = {12, 1, 345, 34, 3, 1234}
x = 1234 // 10 = 123
- Add 123 to set:
s = {12, 1, 345, 34, 3, 1234, 123}
x = 123 // 10 = 12
- Add 12 to set (already exists):
s = {12, 1, 345, 34, 3, 1234, 123}
x = 12 // 10 = 1
- Add 1 to set (already exists):
s = {12, 1, 345, 34, 3, 1234, 123}
x = 1 // 10 = 0
(stop)
Final prefix set: s = {12, 1, 345, 34, 3, 1234, 123}
Step 2: Find longest common prefix with arr2
Initialize ans = 0
For x = 3456
:
- Check if 3456 in s? No
x = 3456 // 10 = 345
- Check if 345 in s? Yes!
- Update
ans = max(0, len("345")) = max(0, 3) = 3
- Break (don't check shorter prefixes)
For x = 123
:
- Check if 123 in s? Yes!
- Update
ans = max(3, len("123")) = max(3, 3) = 3
- Break
For x = 45
:
- Check if 45 in s? No
x = 45 // 10 = 4
- Check if 4 in s? No
x = 4 // 10 = 0
(stop)- No common prefix found for this number
Result: The longest common prefix has length 3 (from either the pair (345, 3456) with common prefix "345", or the pair (1234, 123) with common prefix "123").
Solution Implementation
1class Solution:
2 def longestCommonPrefix(self, arr1: List[int], arr2: List[int]) -> int:
3 # Create a set to store all prefixes from arr1
4 prefix_set = set()
5
6 # Generate all possible prefixes for each number in arr1
7 for num in arr1:
8 # Keep dividing by 10 to get all prefixes
9 # For example: 1234 -> 1234, 123, 12, 1
10 while num > 0:
11 prefix_set.add(num)
12 num //= 10
13
14 # Track the maximum length of common prefix found
15 max_length = 0
16
17 # Check each number in arr2 for common prefixes
18 for num in arr2:
19 # Generate prefixes for current number by removing digits from right
20 while num > 0:
21 # If current prefix exists in the set from arr1
22 if num in prefix_set:
23 # Update max_length with the length of this common prefix
24 max_length = max(max_length, len(str(num)))
25 # Found longest prefix for this number, no need to check shorter ones
26 break
27 # Remove the rightmost digit to get next shorter prefix
28 num //= 10
29
30 return max_length
31
1class Solution {
2 public int longestCommonPrefix(int[] arr1, int[] arr2) {
3 // Store all possible prefixes from arr1
4 Set<Integer> prefixSet = new HashSet<>();
5
6 // Generate all prefixes for each number in arr1
7 // For example: 123 generates 123, 12, 1
8 for (int number : arr1) {
9 while (number > 0) {
10 prefixSet.add(number);
11 number /= 10; // Remove the last digit
12 }
13 }
14
15 // Track the maximum length of common prefix
16 int maxPrefixLength = 0;
17
18 // Check each number in arr2 for common prefixes
19 for (int number : arr2) {
20 // Generate prefixes for current number and check against prefixSet
21 while (number > 0) {
22 if (prefixSet.contains(number)) {
23 // Found a common prefix, update maximum length
24 int currentPrefixLength = String.valueOf(number).length();
25 maxPrefixLength = Math.max(maxPrefixLength, currentPrefixLength);
26 break; // No need to check shorter prefixes
27 }
28 number /= 10; // Remove the last digit to get shorter prefix
29 }
30 }
31
32 return maxPrefixLength;
33 }
34}
35
1class Solution {
2public:
3 int longestCommonPrefix(vector<int>& arr1, vector<int>& arr2) {
4 // Store all prefixes from first array
5 unordered_set<int> prefixSet;
6
7 // Generate all possible prefixes for each number in arr1
8 // by repeatedly dividing by 10
9 for (int num : arr1) {
10 while (num > 0) {
11 prefixSet.insert(num);
12 num /= 10;
13 }
14 }
15
16 // Track the maximum prefix length found
17 int maxPrefixLength = 0;
18
19 // Check each number in arr2 for matching prefixes
20 for (int num : arr2) {
21 // Generate prefixes for current number by dividing by 10
22 while (num > 0) {
23 // If this prefix exists in our set from arr1
24 if (prefixSet.count(num)) {
25 // Calculate the number of digits in the matching prefix
26 // log10(x) + 1 gives the digit count
27 int currentPrefixLength = static_cast<int>(log10(num)) + 1;
28 maxPrefixLength = max(maxPrefixLength, currentPrefixLength);
29 break; // Found longest prefix for this number, move to next
30 }
31 num /= 10;
32 }
33 }
34
35 return maxPrefixLength;
36 }
37};
38
1/**
2 * Finds the length of the longest common prefix between any number from arr1 and any number from arr2.
3 * A prefix of a number is formed by taking its digits from left to right.
4 * @param arr1 - First array of positive integers
5 * @param arr2 - Second array of positive integers
6 * @returns The length of the longest common prefix
7 */
8function longestCommonPrefix(arr1: number[], arr2: number[]): number {
9 // Set to store all possible prefixes from arr1
10 const prefixSet: Set<number> = new Set<number>();
11
12 // Generate all prefixes for each number in arr1
13 // For example: 1234 generates 1234, 123, 12, 1
14 for (const num of arr1) {
15 let currentPrefix: number = num;
16 while (currentPrefix > 0) {
17 prefixSet.add(currentPrefix);
18 // Remove the last digit to get the next shorter prefix
19 currentPrefix = Math.floor(currentPrefix / 10);
20 }
21 }
22
23 // Track the maximum length of common prefix found
24 let maxPrefixLength: number = 0;
25
26 // Check all prefixes for each number in arr2
27 for (const num of arr2) {
28 let currentPrefix: number = num;
29 while (currentPrefix > 0) {
30 // If this prefix exists in arr1's prefixes
31 if (prefixSet.has(currentPrefix)) {
32 // Calculate the number of digits in the prefix
33 // Math.log10(x) + 1 gives the digit count for positive integers
34 const prefixLength: number = Math.floor(Math.log10(currentPrefix)) + 1;
35 maxPrefixLength = Math.max(maxPrefixLength, prefixLength);
36 }
37 // Remove the last digit to check shorter prefix
38 currentPrefix = Math.floor(currentPrefix / 10);
39 }
40 }
41
42 return maxPrefixLength;
43}
44
Time and Space Complexity
Time Complexity: O(m Γ log M + n Γ log N)
The algorithm consists of two main phases:
-
Building the prefix set from arr1: For each number in
arr1
, we repeatedly divide by 10 until the number becomes 0. Each numberx
inarr1
has approximatelylogββ(x)
digits, so we perform that many divisions. In the worst case, the maximum valueM
inarr1
requiresO(log M)
operations. Since we process allm
elements inarr1
, this phase takesO(m Γ log M)
time. -
Checking prefixes from arr2: For each number in
arr2
, we again repeatedly divide by 10 while checking if the resulting number exists in the set. Each lookup in the set isO(1)
. In the worst case, we check all prefixes of a number, which isO(log N)
operations whereN
is the maximum value inarr2
. Processing alln
elements inarr2
takesO(n Γ log N)
time.
Space Complexity: O(m Γ log M)
The space is dominated by the set s
that stores all possible prefixes from arr1
. For each number in arr1
, we store all its prefixes (obtained by repeatedly dividing by 10). A number with value x
generates approximately logββ(x)
prefixes. In the worst case, with m
elements and maximum value M
, we store up to O(m Γ log M)
unique prefixes in the set.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Not Breaking After Finding First Match
The Problem:
A common mistake is to continue checking all prefixes of a number from arr2
even after finding a match, which leads to incorrect results.
# INCORRECT - continues checking shorter prefixes
for num in arr2:
while num > 0:
if num in prefix_set:
max_length = max(max_length, len(str(num)))
# Missing break here!
num //= 10
Why it's wrong: When we find that 12345
is a common prefix, we don't want to also check 1234
, 123
, 12
, and 1
. The inner loop would update max_length
multiple times for the same number, but with decreasing lengths, which doesn't affect correctness in this case but is inefficient.
The Fix: Add a break
statement immediately after finding a match to ensure we only consider the longest prefix for each number in arr2
.
Pitfall 2: Using String Operations Instead of Integer Division
The Problem: Converting numbers to strings for prefix generation can be less efficient and more error-prone.
# INEFFICIENT approach
prefix_set = set()
for num in arr1:
str_num = str(num)
for i in range(1, len(str_num) + 1):
prefix_set.add(int(str_num[:i]))
Why it's suboptimal:
- String slicing and conversion between int and string adds overhead
- More complex logic that's harder to understand
- Potential for off-by-one errors with string indices
The Fix: Use integer division by 10, which is more efficient and cleaner.
Pitfall 3: Forgetting to Handle Zero After Division
The Problem: Not properly terminating the loop when the number becomes 0.
# INCORRECT - might cause infinite loop without proper condition for num in arr1: while True: # Wrong condition prefix_set.add(num) num //= 10 # Loop never terminates when num becomes 0
The Fix: Use while num > 0
or while num
to ensure the loop terminates when the number becomes 0 after repeated division.
Pitfall 4: Calculating Length Incorrectly
The Problem: Using mathematical operations to calculate digit count instead of string conversion.
# ERROR-PRONE approach
import math
length = int(math.log10(num)) + 1 # Fails for num = 0
Why it's problematic:
math.log10(0)
raises a domain error- Requires special handling for edge cases
- More complex than necessary
The Fix: Use len(str(num))
which handles all cases correctly and is more readable.
Which type of traversal does breadth first search do?
Recommended Readings
Trie Introduction Imagine you have a magic dictionary where as soon as you start spelling a word it immediately tells you how many words that start with those letters How cool would that be This is exactly the kind of magic a data structure called trie performs What's a Trie
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!