14. Longest Common Prefix
Problem Description
Given an array of strings, find the longest common prefix that appears at the beginning of all strings in the array.
A common prefix is a sequence of characters that appears at the start of every string. For example, if you have the strings ["flower", "flow", "flight"]
, the longest common prefix would be "fl"
since all three strings start with these two characters.
The function should:
- Take an array of strings as input
- Return the longest string that is a prefix of all strings in the array
- Return an empty string
""
if no common prefix exists
Examples to clarify the problem:
- Input:
["flower", "flow", "flight"]
β Output:"fl"
- Input:
["dog", "racecar", "car"]
β Output:""
(no common prefix) - Input:
["interspecies", "interstellar", "interstate"]
β Output:"inters"
The solution uses the first string as a reference point and checks character by character if all other strings have the same character at each position. The process stops when either a mismatch is found or when we reach the end of any string. The characters checked up to that point form the longest common prefix.
Intuition
When looking for a common prefix among multiple strings, we need to find characters that appear in the same position across all strings. The key insight is that the longest common prefix cannot be longer than the shortest string in the array.
Think of it like stacking the strings vertically and reading column by column:
flower flow flight
Reading vertically, the first column has all 'f'
, the second column has all 'l'
, but the third column has different characters ('o'
, 'o'
, 'i'
). This is where the common prefix ends.
We can pick any string as a reference - let's use the first one. Why? Because if there's a common prefix, it must exist in the first string too. We then iterate through each character position i
of this first string and check if all other strings have:
- At least
i+1
characters (otherwise they're too short) - The same character at position
i
The moment we find a mismatch or reach the end of any string, we stop. The characters we've successfully matched so far form our longest common prefix.
This vertical scanning approach is intuitive because we're essentially asking: "How far can we go before the strings start to differ?" We process character by character from left to right, building up the common prefix incrementally until we hit a difference or run out of characters to compare.
Learn more about Trie patterns.
Solution Approach
The implementation follows a character comparison strategy using the first string as a benchmark. Here's how the algorithm works:
-
Use the first string as reference: We iterate through each character index
i
ofstrs[0]
, from position 0 to its length. -
Compare with remaining strings: For each character position
i
, we check all strings fromstrs[1:]
(all strings except the first one). -
Two stopping conditions: For each string
s
in the remaining strings, we check:len(s) <= i
: This means the current strings
is shorter than or equal to the current index. We've reached the end of this string, so the common prefix can't be longer.s[i] != strs[0][i]
: The character at positioni
in strings
doesn't match the character at positioni
in our reference string. The common prefix ends here.
-
Return the prefix: When either condition is met, we return
s[:i]
, which gives us the substring from the beginning up to (but not including) indexi
. Note that we could also returnstrs[0][:i]
- both would give the same result since all strings share this prefix. -
Complete match: If we finish iterating through all characters of
strs[0]
without hitting any stopping condition, it meansstrs[0]
itself is the common prefix for all strings, so we returnstrs[0]
.
The algorithm processes characters position by position (vertical scanning), checking all strings at each position before moving to the next. This ensures we find the exact point where the strings begin to differ, giving us the longest common prefix.
Time complexity: O(S)
where S
is the sum of all characters in all strings. In the worst case, all strings are identical.
Space complexity: O(1)
as we only use a constant amount of extra space.
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 the algorithm with the input ["flower", "flow", "flight"]
.
Initial Setup:
- Use
strs[0] = "flower"
as our reference string - We'll check each character position
i
from 0 to 5 (length of "flower" is 6)
Iteration 1 (i = 0):
- Check character at position 0 in "flower":
'f'
- Compare with "flow"[0]:
'f'
β (matches) - Compare with "flight"[0]:
'f'
β (matches) - All match, continue to next position
Iteration 2 (i = 1):
- Check character at position 1 in "flower":
'l'
- Compare with "flow"[1]:
'l'
β (matches) - Compare with "flight"[1]:
'l'
β (matches) - All match, continue to next position
Iteration 3 (i = 2):
- Check character at position 2 in "flower":
'o'
- Compare with "flow"[2]:
'o'
β (matches) - Compare with "flight"[2]:
'i'
β (mismatch!) - Found a mismatch at position 2
- Return
strs[0][:2]
which is"fl"
The algorithm correctly identifies that the common prefix ends at position 2, returning "fl"
as the longest common prefix.
Alternative scenario: If we had ["flow", "flower", "flowing"]
instead:
- At i = 4, we'd check position 4 in "flow" (but "flow" only has 4 characters, indices 0-3)
- Since we've reached the end of our reference string without any mismatches, we return the entire reference string:
"flow"
Solution Implementation
1class Solution:
2 def longestCommonPrefix(self, strs: List[str]) -> str:
3 # Iterate through each character position of the first string
4 for i in range(len(strs[0])):
5 # Check this position against all other strings in the array
6 for string in strs[1:]:
7 # If current string is shorter than position i, or
8 # character at position i doesn't match the first string's character
9 if len(string) <= i or string[i] != strs[0][i]:
10 # Return the common prefix up to (but not including) position i
11 return strs[0][:i]
12
13 # If we've checked all positions without mismatch,
14 # the entire first string is the common prefix
15 return strs[0]
16
1class Solution {
2 /**
3 * Finds the longest common prefix string amongst an array of strings.
4 *
5 * @param strs Array of strings to find common prefix from
6 * @return The longest common prefix, or empty string if no common prefix exists
7 */
8 public String longestCommonPrefix(String[] strs) {
9 // Get the total number of strings in the array
10 int numberOfStrings = strs.length;
11
12 // Iterate through each character position of the first string
13 for (int charIndex = 0; charIndex < strs[0].length(); charIndex++) {
14 // Compare this character position across all other strings
15 for (int stringIndex = 1; stringIndex < numberOfStrings; stringIndex++) {
16 // Check if current string is shorter than current position
17 // or if character at current position doesn't match first string
18 if (strs[stringIndex].length() <= charIndex ||
19 strs[stringIndex].charAt(charIndex) != strs[0].charAt(charIndex)) {
20 // Return the common prefix found so far
21 return strs[0].substring(0, charIndex);
22 }
23 }
24 }
25
26 // If we've checked all characters of first string without mismatch,
27 // the entire first string is the common prefix
28 return strs[0];
29 }
30}
31
1class Solution {
2public:
3 string longestCommonPrefix(vector<string>& strs) {
4 // Get the number of strings in the array
5 int numStrings = strs.size();
6
7 // Iterate through each character position of the first string
8 for (int charIndex = 0; charIndex < strs[0].size(); ++charIndex) {
9 // Compare this character position across all other strings
10 for (int stringIndex = 1; stringIndex < numStrings; ++stringIndex) {
11 // Check if current string is shorter than current position
12 // or if the character at this position doesn't match the first string
13 if (strs[stringIndex].size() <= charIndex ||
14 strs[stringIndex][charIndex] != strs[0][charIndex]) {
15 // Return the common prefix found so far
16 return strs[0].substr(0, charIndex);
17 }
18 }
19 }
20
21 // If we've checked all characters of the first string without mismatch,
22 // the entire first string is the common prefix
23 return strs[0];
24 }
25};
26
1/**
2 * Finds the longest common prefix string amongst an array of strings.
3 * @param strs - Array of strings to find common prefix from
4 * @returns The longest common prefix string, or empty string if no common prefix exists
5 */
6function longestCommonPrefix(strs: string[]): string {
7 // Find the minimum length among all strings to limit our search range
8 const minLength: number = strs.reduce(
9 (currentMin: number, str: string) => Math.min(currentMin, str.length),
10 Infinity
11 );
12
13 // Try prefixes from longest to shortest possible length
14 for (let prefixLength: number = minLength; prefixLength > 0; prefixLength--) {
15 // Extract the candidate prefix from the first string
16 const candidatePrefix: string = strs[0].slice(0, prefixLength);
17
18 // Check if all strings start with this candidate prefix
19 const isCommonPrefix: boolean = strs.every(
20 (str: string) => str.slice(0, prefixLength) === candidatePrefix
21 );
22
23 if (isCommonPrefix) {
24 return candidatePrefix;
25 }
26 }
27
28 // No common prefix found
29 return '';
30}
31
Time and Space Complexity
The time complexity is O(n Γ m)
, where n
is the number of strings in the array and m
is the length of the shortest string (or the length of the common prefix, whichever is smaller). In the worst case, we need to compare each character of the first string with the corresponding character in all other strings. The outer loop runs at most m
times (length of the first string or until a mismatch is found), and the inner loop runs n-1
times for each iteration of the outer loop.
The space complexity is O(1)
. The algorithm only uses a constant amount of extra space for the loop variables i
and s
. The returned string is a substring of an existing string (using slicing), which in Python creates a new string, but this is considered part of the output and not counted as auxiliary space used by the algorithm itself.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Empty Array Input
The Problem: The code assumes strs[0]
exists and will throw an IndexError
if the input array is empty.
Example:
- Input:
[]
β RaisesIndexError: list index out of range
Solution: Add a guard clause to handle empty arrays:
def longestCommonPrefix(self, strs: List[str]) -> str:
if not strs: # Handle empty array
return ""
for i in range(len(strs[0])):
for string in strs[1:]:
if len(string) <= i or string[i] != strs[0][i]:
return strs[0][:i]
return strs[0]
2. Single String in Array
The Problem: While the current code handles this correctly, it's inefficient as it still iterates through an empty list strs[1:]
. This is a minor performance consideration but worth noting.
Example:
- Input:
["hello"]
β The loopfor string in strs[1:]
iterates over an empty list
Solution: Add an early return for single-element arrays:
def longestCommonPrefix(self, strs: List[str]) -> str:
if not strs:
return ""
if len(strs) == 1: # Single string is its own prefix
return strs[0]
for i in range(len(strs[0])):
for string in strs[1:]:
if len(string) <= i or string[i] != strs[0][i]:
return strs[0][:i]
return strs[0]
3. Confusion About Return Value in Mismatch
The Problem: When returning s[:i]
instead of strs[0][:i]
, developers might worry about edge cases where s
is shorter than i
. However, this concern is unfounded because the condition len(s) <= i
ensures we return before accessing s[i]
.
Clarification: Both return s[:i]
and return strs[0][:i]
are correct because:
- If
len(s) <= i
, thens[:i]
returns the entire strings
, which is indeed the common prefix - If
s[i] != strs[0][i]
, then both strings share the same prefix up to indexi-1
Recommended approach for clarity:
# Always return from the reference string for consistency return strs[0][:i]
4. Alternative Edge Case - Array Contains Empty String
The Problem: If any string in the array is empty, the longest common prefix must be empty. The current solution handles this correctly but it's worth understanding why.
Example:
- Input:
["flower", "", "flight"]
β Output:""
The code handles this naturally because when checking the empty string:
len("") <= 0
isTrue
for the first character check- Returns
strs[0][:0]
which is""
No code change needed, but understanding this behavior prevents confusion during debugging.
Which data structure is used to implement recursion?
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!