Please upgrade to NeetCode Pro to view company tags.
Prerequisites
Before attempting this problem, you should be comfortable with:
String Manipulation - Comparing characters, extracting substrings, and iterating through strings
Sorting - Understanding lexicographic string ordering for the sorting approach
Trie Data Structure - Building and traversing a prefix tree for the optimal solution
1. Horizontal Scanning
Intuition
Start with the first string as the initial prefix candidate. Then compare it with each subsequent string, shrinking the prefix to match only the common portion. After processing all strings, what remains is the longest common prefix. The prefix can only shrink or stay the same as we go through more strings.
Algorithm
Initialize prefix as the first string in the array.
For each subsequent string, compare characters one by one with the current prefix.
Find the index j where characters stop matching (or we run out of characters in either string).
Truncate prefix to include only characters from index 0 to j-1.
After processing all strings, return the remaining prefix.
classSolution{funclongestCommonPrefix(_ strs:[String])->String{varprefix=Array(strs[0])for i in1..<strs.count {let s =Array(strs[i])var j =0while j <min(prefix.count, s.count){ifprefix[j]!= s[j]{break}
j +=1}prefix=Array(prefix[0..<j])}returnString(prefix)}}
Time & Space Complexity
Time complexity: O(n∗m)
Space complexity: O(1)
Where n is the length of the shortest string and m is the number of strings.
2. Vertical Scanning
Intuition
Instead of comparing entire strings horizontally, we can compare characters column by column across all strings. Check if all strings have the same character at position 0, then position 1, and so on. The moment we find a mismatch or reach the end of any string, we've found where the common prefix ends.
Algorithm
Iterate through character positions starting from index 0.
At each position i, check the character in the first string.
Compare this character against position i in every other string.
If any string is too short or has a different character, return the prefix up to index i-1.
If we complete the loop without returning, the entire first string is the common prefix.
classSolution:deflongestCommonPrefix(self, strs: List[str])->str:for i inrange(len(strs[0])):for s in strs:if i ==len(s)or s[i]!= strs[0][i]:return s[:i]return strs[0]
publicclassSolution{publicStringlongestCommonPrefix(String[] strs){for(int i =0; i < strs[0].length(); i++){for(String s : strs){if(i == s.length()|| s.charAt(i)!= strs[0].charAt(i)){return s.substring(0, i);}}}return strs[0];}}
classSolution{public:
string longestCommonPrefix(vector<string>& strs){for(int i =0; i < strs[0].length(); i++){for(const string& s : strs){if(i == s.length()|| s[i]!= strs[0][i]){return s.substr(0, i);}}}return strs[0];}};
classSolution{/**
* @param {string[]} strs
* @return {string}
*/longestCommonPrefix(strs){for(let i =0; i < strs[0].length; i++){for(let s of strs){if(i === s.length || s[i]!== strs[0][i]){return s.slice(0, i);}}}return strs[0];}}
publicclassSolution{publicstringLongestCommonPrefix(string[] strs){for(int i =0; i < strs[0].Length; i++){foreach(string s in strs){if(i == s.Length || s[i]!= strs[0][i]){return s.Substring(0, i);}}}return strs[0];}}
funclongestCommonPrefix(strs []string)string{for i :=0; i <len(strs[0]); i++{for_, s :=range strs {if i ==len(s)|| s[i]!= strs[0][i]{return s[:i]}}}return strs[0]}
class Solution {funlongestCommonPrefix(strs: Array<String>): String {for(i in strs[0].indices){for(s in strs){if(i == s.length || s[i]!= strs[0][i]){return s.substring(0, i)}}}return strs[0]}}
classSolution{funclongestCommonPrefix(_ strs:[String])->String{let first =Array(strs[0])for i in0..<first.count {for str in strs {let s =Array(str)if i == s.count || s[i]!= first[i]{returnString(first[0..<i])}}}return strs[0]}}
Time & Space Complexity
Time complexity: O(n∗m)
Space complexity: O(1) since we did not use extra space.
Where n is the length of the shortest string and m is the number of strings.
3. Sorting
Intuition
When strings are sorted lexicographically, the first and last strings in the sorted order are the most different from each other. If these two extremes share a common prefix, then all strings in between must also share that same prefix. So we only need to compare the first and last strings after sorting.
Algorithm
If there's only one string, return it directly.
Sort the array of strings lexicographically.
Compare only the first and last strings in the sorted array.
Find the longest prefix they share by comparing characters one by one.
Return this prefix, which is guaranteed to be common to all strings.
publicclassSolution{publicstringLongestCommonPrefix(string[] strs){if(strs.Length ==1){return strs[0];}
Array.Sort(strs);string first = strs[0];string last = strs[strs.Length -1];int i =0;while(i < Math.Min(first.Length, last.Length)){if(first[i]!= last[i]){return first.Substring(0, i);}
i++;}return first;}}
import"sort"funclongestCommonPrefix(strs []string)string{iflen(strs)==1{return strs[0]}
sort.Strings(strs)
first := strs[0]
last := strs[len(strs)-1]
n :=len(first)iflen(last)< n {
n =len(last)}for i :=0; i < n; i++{if first[i]!= last[i]{return first[:i]}}return first
}
class Solution {funlongestCommonPrefix(strs: Array<String>): String {if(strs.size ==1){return strs[0]}
strs.sort()val first = strs[0]val last = strs[strs.size -1]var i =0while(i <minOf(first.length, last.length)){if(first[i]!= last[i]){return first.substring(0, i)}
i++}return first
}}
classSolution{funclongestCommonPrefix(_ strs:[String])->String{if strs.count ==1{return strs[0]}let sorted = strs.sorted()let first =Array(sorted[0])let last =Array(sorted[sorted.count -1])var i =0while i <min(first.count, last.count){if first[i]!= last[i]{returnString(first[0..<i])}
i +=1}return sorted[0]}}
Time & Space Complexity
Time complexity: O(n∗mlogm)
Space complexity: O(1) or O(m) depending on the sorting algorithm.
Where n is the length of the longest string and m is the number of strings.
4. Trie
Intuition
A Trie naturally represents all prefixes. We insert the shortest string into the trie, then query each other string against it. For each string, we walk down the trie as far as characters match, tracking how deep we get. The minimum depth reached across all strings is the length of the longest common prefix.
Algorithm
Find the shortest string and insert it into a Trie (this limits the trie size and ensures we don't go beyond what could be common).
Initialize prefixLen to the length of the shortest string.
For each string, traverse the trie while characters match, updating prefixLen to be the minimum of its current value and how far we matched.
After checking all strings, extract the first prefixLen characters from any string as the result.
classTrieNode:def__init__(self):
self.children ={}classTrie:def__init__(self):
self.root = TrieNode()definsert(self, word:str)->None:
node = self.root
for char in word:if char notin node.children:
node.children[char]= TrieNode()
node = node.children[char]deflcp(self, word:str, prefixLen:int)->int:
node = self.root
for i inrange(min(len(word), prefixLen)):if word[i]notin node.children:return i
node = node.children[word[i]]returnmin(len(word), prefixLen)classSolution:deflongestCommonPrefix(self, strs:list[str])->str:iflen(strs)==1:return strs[0]
mini =0for i inrange(1,len(strs)):iflen(strs[mini])>len(strs[i]):
mini = i
trie = Trie()
trie.insert(strs[mini])
prefixLen =len(strs[mini])for i inrange(len(strs)):
prefixLen = trie.lcp(strs[i], prefixLen)return strs[0][:prefixLen]
classTrieNode{Map<Character,TrieNode> children =newHashMap<>();}classTrie{TrieNode root =newTrieNode();voidinsert(String word){TrieNode node = root;for(char c : word.toCharArray()){
node.children.putIfAbsent(c,newTrieNode());
node = node.children.get(c);}}intlcp(String word,int prefixLen){TrieNode node = root;int i =0;while(i <Math.min(word.length(), prefixLen)){if(!node.children.containsKey(word.charAt(i))){return i;}
node = node.children.get(word.charAt(i));
i++;}returnMath.min(word.length(), prefixLen);}}publicclassSolution{publicStringlongestCommonPrefix(String[] strs){if(strs.length ==1){return strs[0];}int mini =0;for(int i =1; i < strs.length; i++){if(strs[mini].length()> strs[i].length()){
mini = i;}}Trie trie =newTrie();
trie.insert(strs[mini]);int prefixLen = strs[mini].length();for(int i =0; i < strs.length; i++){
prefixLen = trie.lcp(strs[i], prefixLen);}return strs[0].substring(0, prefixLen);}}
classTrieNode{public:
unordered_map<char, TrieNode*> children;};classTrie{public:
TrieNode* root;Trie(){
root =newTrieNode();}voidinsert(const string& word){
TrieNode* node = root;for(char c : word){if(node->children.find(c)== node->children.end()){
node->children[c]=newTrieNode();}
node = node->children[c];}}intlcp(const string& word,int prefixLen){
TrieNode* node = root;int i =0;while(i <min((int)word.length(), prefixLen)){if(node->children.find(word[i])== node->children.end()){return i;}
node = node->children[word[i]];
i++;}returnmin((int)word.length(), prefixLen);}};classSolution{public:
string longestCommonPrefix(vector<string>& strs){if(strs.size()==1){return strs[0];}int mini =0;for(int i =1; i < strs.size(); i++){if(strs[mini].size()> strs[i].size()){
mini = i;}}
Trie trie;
trie.insert(strs[mini]);int prefixLen = strs[mini].length();for(int i =0; i < strs.size(); i++){
prefixLen = trie.lcp(strs[i], prefixLen);}return strs[0].substr(0, prefixLen);}};
classTrieNode{constructor(){this.children ={};}}classTrie{constructor(){this.root =newTrieNode();}/**
* @param {string} word
* @return {void}
*/insert(word){let node =this.root;for(let char of word){if(!node.children[char]){
node.children[char]=newTrieNode();}
node = node.children[char];}}/**
* @param {string} word
* @param {number} prefixLen
* @return {number}
*/lcp(word, prefixLen){let node =this.root;let i =0;while(i < Math.min(word.length, prefixLen)){if(!node.children[word[i]]){return i;}
node = node.children[word[i]];
i++;}return Math.min(word.length, prefixLen);}}classSolution{/**
* @param {string[]} strs
* @return {string}
*/longestCommonPrefix(strs){if(strs.length ===1){return strs[0];}let mini =0;for(let i =1; i < strs.length; i++){if(strs[mini].length > strs[i].length){
mini = i;}}const trie =newTrie();
trie.insert(strs[mini]);let prefixLen = strs[mini].length;for(let i =0; i < strs.length; i++){
prefixLen = trie.lcp(strs[i], prefixLen);}return strs[0].substring(0, prefixLen);}}
publicclassTrieNode{publicDictionary<char, TrieNode> Children =newDictionary<char, TrieNode>();}publicclassTrie{publicTrieNode Root;publicTrie(){
Root =newTrieNode();}publicvoidInsert(string word){TrieNode node = Root;foreach(char c in word){if(!node.Children.ContainsKey(c)){
node.Children[c]=newTrieNode();}
node = node.Children[c];}}publicintLcp(string word,int prefixLen){TrieNode node = Root;for(int i =0; i < Math.Min(word.Length, prefixLen); i++){if(!node.Children.ContainsKey(word[i])){return i;}
node = node.Children[word[i]];}return Math.Min(word.Length, prefixLen);}}publicclassSolution{publicstringLongestCommonPrefix(string[] strs){if(strs.Length ==1)return strs[0];int mini =0;for(int i =1; i < strs.Length; i++){if(strs[i].Length < strs[mini].Length){
mini = i;}}Trie trie =newTrie();
trie.Insert(strs[mini]);int prefixLen = strs[mini].Length;for(int i =0; i < strs.Length; i++){
prefixLen = trie.Lcp(strs[i], prefixLen);}return strs[0].Substring(0, prefixLen);}}
type TrieNode struct{
children map[byte]*TrieNode
}type Trie struct{
root *TrieNode
}funcNewTrie()*Trie {return&Trie{root:&TrieNode{children:make(map[byte]*TrieNode)}}}func(t *Trie)Insert(word string){
node := t.root
for i :=0; i <len(word); i++{
c := word[i]if_, exists := node.children[c];!exists {
node.children[c]=&TrieNode{children:make(map[byte]*TrieNode)}}
node = node.children[c]}}func(t *Trie)Lcp(word string, prefixLen int)int{
node := t.root
length :=len(word)if prefixLen < length {
length = prefixLen
}for i :=0; i < length; i++{if_, exists := node.children[word[i]];!exists {return i
}
node = node.children[word[i]]}iflen(word)< prefixLen {returnlen(word)}return prefixLen
}funclongestCommonPrefix(strs []string)string{iflen(strs)==1{return strs[0]}
mini :=0for i :=1; i <len(strs); i++{iflen(strs[i])<len(strs[mini]){
mini = i
}}
trie :=NewTrie()
trie.Insert(strs[mini])
prefixLen :=len(strs[mini])for i :=0; i <len(strs); i++{
prefixLen = trie.Lcp(strs[i], prefixLen)}return strs[0][:prefixLen]}
class TrieNode {val children = HashMap<Char, TrieNode>()}class Trie {val root =TrieNode()funinsert(word: String){var node = root
for(c in word){if(!node.children.containsKey(c)){
node.children[c]=TrieNode()}
node = node.children[c]!!}}funlcp(word: String, prefixLen: Int): Int {var node = root
var i =0while(i <minOf(word.length, prefixLen)){if(!node.children.containsKey(word[i])){return i
}
node = node.children[word[i]]!!
i++}returnminOf(word.length, prefixLen)}}class Solution {funlongestCommonPrefix(strs: Array<String>): String {if(strs.size ==1)return strs[0]var mini =0for(i in1 until strs.size){if(strs[i].length < strs[mini].length){
mini = i
}}val trie =Trie()
trie.insert(strs[mini])var prefixLen = strs[mini].length
for(i in strs.indices){
prefixLen = trie.lcp(strs[i], prefixLen)}return strs[0].substring(0, prefixLen)}}
classTrieNode{var children =[Character:TrieNode]()}classTrie{var root =TrieNode()funcinsert(_ word:String){var node = root
for c in word {if node.children[c]==nil{
node.children[c]=TrieNode()}
node = node.children[c]!}}funclcp(_ word:String,_ prefixLen:Int)->Int{var node = root
let chars =Array(word)var i =0while i <min(chars.count, prefixLen){if node.children[chars[i]]==nil{return i
}
node = node.children[chars[i]]!
i +=1}returnmin(chars.count, prefixLen)}}classSolution{funclongestCommonPrefix(_ strs:[String])->String{if strs.count ==1{return strs[0]}var mini =0for i in1..<strs.count {if strs[i].count < strs[mini].count {
mini = i
}}let trie =Trie()
trie.insert(strs[mini])var prefixLen = strs[mini].count
for i in0..<strs.count {
prefixLen = trie.lcp(strs[i], prefixLen)}returnString(strs[0].prefix(prefixLen))}}
Time & Space Complexity
Time complexity: O(n∗m)
Space complexity: O(n)
Where n is the length of the shortest string and m is the number of strings.
Common Pitfalls
Not Handling Empty Strings in the Array
If any string in the input array is empty, the longest common prefix must be an empty string. Failing to check for this case before accessing characters can lead to index out of bounds errors.
Accessing Characters Beyond String Length
When comparing characters at a given index, you must ensure the index is valid for all strings being compared. A common mistake is to iterate based on one string's length without checking if shorter strings have characters at that position.