Skip to content

Longest Common Prefix

Vertical scanning and divide-and-conquer approaches

Finding the longest common prefix among an array of strings is a classic problem that tests your understanding of string comparison techniques and algorithm design patterns.


Problem Statement

Write a function to find the longest common prefix string amongst an array of strings.

If there is no common prefix, return an empty string "".

This is LeetCode Problem #14 - an Easy difficulty problem.

Examples

InputOutputExplanation
["flower","flow","flight"]"fl"Common prefix is "fl"
["dog","racecar","car"]""No common prefix
["interspecies","interstellar","interstate"]"inters"All start with "inters"
["a"]"a"Single string is its own prefix
[]""Empty array has no prefix

Constraints

  • 1 <= strs.length <= 200
  • 0 <= strs[i].length <= 200
  • strs[i] consists of only lowercase English letters

Approach 1: Vertical Scanning

Compare characters column by column across all strings.

How It Works

  1. Take the first string as reference
  2. For each character position (column):
    • Check if all strings have this character at this position
    • If any string is shorter or has a different character, stop
  3. Return the prefix found

Mermaid Diagram

Visual Walkthrough

For ["flower", "flow", "flight"]:

Position 0:  f  f  f  -> All match 'f' -> prefix = "f"
Position 1:  l  l  l  -> All match 'l' -> prefix = "fl"
Position 2:  o  o  i  -> Mismatch! 'o' != 'i'

Return "fl"

Solution

python
def longestCommonPrefix(strs: list[str]) -> str:
    """
    Find longest common prefix using vertical scanning.

    Args:
        strs: List of strings to compare

    Returns:
        Longest common prefix string

    Time: O(S) where S = sum of all characters in all strings
    Space: O(1) - only using a few variables
    """
    if not strs:
        return ""

    # Use first string as reference
    for i in range(len(strs[0])):
        char = strs[0][i]

        # Check this position in all other strings
        for j in range(1, len(strs)):
            # If string is shorter or character doesn't match
            if i >= len(strs[j]) or strs[j][i] != char:
                return strs[0][:i]

    return strs[0]
java
public String longestCommonPrefix(String[] strs) {
    if (strs == null || strs.length == 0) return "";
    for (int i = 0; i < strs[0].length(); i++) {
        char c = strs[0].charAt(i);
        for (int j = 1; j < strs.length; j++) {
            if (i >= strs[j].length() || strs[j].charAt(i) != c) {
                return strs[0].substring(0, i);
            }
        }
    }
    return strs[0];
}

Complexity: Time O(S) · Space O(1)

  • Time: O(S) where S is the sum of all characters in all strings - in worst case we compare every character when all strings are identical
  • Space: O(1) - only using a few pointer variables, no additional data structures

Approach 2: Horizontal Scanning

Compare strings pairwise, reducing the prefix each time.

How It Works

  1. Start with the first string as the prefix
  2. Compare with each subsequent string
  3. Reduce prefix until it matches the beginning of each string
python
def longestCommonPrefix_horizontal(strs: list[str]) -> str:
    """
    Find LCP using horizontal scanning (pairwise comparison).

    Time: O(S) where S = sum of all characters
    Space: O(1)
    """
    if not strs:
        return ""

    prefix = strs[0]

    for i in range(1, len(strs)):
        # Reduce prefix until it matches start of current string
        while not strs[i].startswith(prefix):
            prefix = prefix[:-1]
            if not prefix:
                return ""

    return prefix

Complexity: Time O(S) · Space O(1)

  • Time: O(S) where S is the sum of all characters - prefix shrinks character by character until it matches each string
  • Space: O(1) - only storing the prefix string which shrinks from the first string

Visual Walkthrough

For ["flower", "flow", "flight"]:

Start: prefix = "flower"

Compare with "flow":
  "flow".startswith("flower")? No
  prefix = "flowe"
  "flow".startswith("flowe")? No
  prefix = "flow"
  "flow".startswith("flow")? Yes!

Compare with "flight":
  "flight".startswith("flow")? No
  prefix = "flo"
  "flight".startswith("flo")? No
  prefix = "fl"
  "flight".startswith("fl")? Yes!

Return "fl"

Approach 3: Divide and Conquer

Recursively split the array and find LCP of each half.

python
def longestCommonPrefix_divideConquer(strs: list[str]) -> str:
    """
    Find LCP using divide and conquer.

    Time: O(S) where S = sum of all characters
    Space: O(m * log n) where m = length of shortest string
           (for recursion stack and intermediate strings)
    """
    if not strs:
        return ""

    def commonPrefix(left: str, right: str) -> str:
        """Find common prefix of two strings."""
        min_len = min(len(left), len(right))
        for i in range(min_len):
            if left[i] != right[i]:
                return left[:i]
        return left[:min_len]

    def divideAndConquer(left: int, right: int) -> str:
        if left == right:
            return strs[left]

        mid = (left + right) // 2
        lcp_left = divideAndConquer(left, mid)
        lcp_right = divideAndConquer(mid + 1, right)

        return commonPrefix(lcp_left, lcp_right)

    return divideAndConquer(0, len(strs) - 1)

Complexity: Time O(S) · Space O(m log n)

  • Time: O(S) where S is the sum of all characters - each character is compared once during the merge process
  • Space: O(m * log n) where m is the shortest string length - recursion stack depth is log n, each level stores intermediate prefix strings

Use binary search on the length of the prefix.

python
def longestCommonPrefix_binarySearch(strs: list[str]) -> str:
    """
    Find LCP using binary search on prefix length.

    Time: O(S * log m) where m = length of shortest string
    Space: O(1)
    """
    if not strs:
        return ""

    def isCommonPrefix(length: int) -> bool:
        """Check if prefix of given length is common to all strings."""
        prefix = strs[0][:length]
        return all(s.startswith(prefix) for s in strs)

    # Binary search on the length
    min_len = min(len(s) for s in strs)
    low, high = 0, min_len

    while low < high:
        mid = (low + high + 1) // 2  # Upper middle
        if isCommonPrefix(mid):
            low = mid
        else:
            high = mid - 1

    return strs[0][:low]

Complexity: Time O(S log m) · Space O(1)

  • Time: O(S * log m) where m is the shortest string length - binary search on length with O(S) verification at each step
  • Space: O(1) - only storing indices for binary search boundaries

Approach 5: Using Python's Built-in (Pythonic)

python
import os

def longestCommonPrefix_pythonic(strs: list[str]) -> str:
    """
    Pythonic solution using os.path.commonprefix.

    Note: This works but may not be accepted in interviews
    as it uses a library function.
    """
    return os.path.commonprefix(strs)


def longestCommonPrefix_zip(strs: list[str]) -> str:
    """
    Using zip to compare characters column-wise.

    Time: O(S)
    Space: O(m) for the zip objects
    """
    if not strs:
        return ""

    prefix = []
    for chars in zip(*strs):
        if len(set(chars)) == 1:  # All same
            prefix.append(chars[0])
        else:
            break

    return ''.join(prefix)

Complexity: Time O(S) · Space O(m)

  • Time: O(S) where S is the sum of all characters - zip iterates through all strings column by column
  • Space: O(m) where m is the shortest string length - for the zip iterator objects and result prefix list

Approach 6: Sort and Compare

After sorting, compare only the first and last strings.

python
def longestCommonPrefix_sort(strs: list[str]) -> str:
    """
    Sort strings, then compare first and last only.

    Key insight: After lexicographic sorting, the common prefix
    of all strings must be the common prefix of first and last.

    Time: O(n log n * m) for sorting, O(m) for comparison
    Space: O(n) for sorted copy or O(1) if in-place
    """
    if not strs:
        return ""

    strs.sort()
    first, last = strs[0], strs[-1]

    i = 0
    while i < len(first) and i < len(last) and first[i] == last[i]:
        i += 1

    return first[:i]

Complexity: Time O(n log n * m + m) · Space O(n)

  • Time: O(n log n * m) for sorting n strings of average length m, plus O(m) for final comparison
  • Space: O(n) for the sorted array copy, or O(1) if sorting in-place (but modifies input)

Complexity Comparison

ApproachTimeSpaceBest For
Vertical ScanningO(S)O(1)General use
Horizontal ScanningO(S)O(1)Simple implementation
Divide & ConquerO(S)O(m log n)Parallel processing
Binary SearchO(S log m)O(1)Very long strings
Sort + CompareO(n log n * m)O(n)Already sorted input
Zip (Pythonic)O(S)O(m)Quick coding

Where:

  • S = sum of all character counts
  • n = number of strings
  • m = length of shortest string

Edge Cases

CaseInputOutputHandling
Empty array[]""Return empty
Single string["abc"]"abc"Return the string
Empty string in array["", "abc"]""LCP is empty
No common prefix["dog", "cat"]""First chars differ
All identical["abc", "abc"]"abc"Return full string

Interview Tips

  1. Start with vertical scanning: Most intuitive and efficient
  2. Ask about input size: Binary search for very long strings
  3. Handle edge cases first: Empty array, single string
  4. Mention optimization: Early termination when prefix becomes empty

Common Follow-up Questions

  • "What if strings are very long?" - Binary search on length
  • "What if we have many strings?" - Divide and conquer for parallelization
  • "What if strings change frequently?" - Use a Trie data structure

Trie-Based Solution (Advanced)

For multiple queries or dynamic updates, a Trie is optimal:

python
class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end = False

class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, word: str) -> None:
        node = self.root
        for char in word:
            if char not in node.children:
                node.children[char] = TrieNode()
            node = node.children[char]
        node.is_end = True

    def longestCommonPrefix(self) -> str:
        """
        Find LCP by traversing until a branch point.
        """
        prefix = []
        node = self.root

        while node and len(node.children) == 1 and not node.is_end:
            char = next(iter(node.children))
            prefix.append(char)
            node = node.children[char]

        return ''.join(prefix)


def longestCommonPrefix_trie(strs: list[str]) -> str:
    """
    Find LCP using a Trie.

    Time: O(S) to build trie, O(m) to find LCP
    Space: O(S) for trie

    Best for: Multiple queries after initial build
    """
    if not strs:
        return ""

    trie = Trie()
    for s in strs:
        if not s:  # Empty string means no common prefix
            return ""
        trie.insert(s)

    return trie.longestCommonPrefix()

Complexity: Time O(S) · Space O(S)

  • Time: O(S) to build the trie where S is the sum of all characters, plus O(m) to find the LCP
  • Space: O(S) to store all characters in the trie nodes - each character creates a new node

Implement Trie - Prefix Tree (LeetCode 208)

Problem: Implement a trie with insert, search, and startsWith methods.

Key Insight: Trie nodes contain children map and end-of-word flag. Each path from root represents a prefix.

Approach: Insert by creating nodes for each character. Search follows path and checks end flag. startsWith only checks path existence.

Complexity: O(m) for all operations where m is key length, O(alphabet_size * n * m) space

Search Suggestions System (LeetCode 1268)

Problem: Given products array and searchWord, return list of suggested products after each character of searchWord is typed. Suggest at most 3 products with common prefix, sorted lexicographically.

Key Insight: Can use Trie for prefix matching or binary search on sorted array.

Approach: Sort products. For each prefix, binary search for leftmost match, take up to 3 products starting there.

Complexity: O(n log n + m * n) time for sorting + queries, or O(sum of lengths) for Trie

Longest Common Subsequence (LeetCode 1143)

Problem: Given two strings, return the length of their longest common subsequence (not necessarily contiguous).

Key Insight: Classic 2D DP. Different from prefix - subsequence can skip characters.

Approach: dp[i][j] = LCS length for first i chars of text1 and first j chars of text2. Match adds 1, mismatch takes max of excluding either.

Complexity: O(m * n) time, O(m * n) space (can optimize to O(n))


Summary

Key PointDetails
Best ApproachVertical scanning for simplicity
Time ComplexityO(S) for most approaches
Space ComplexityO(1) for vertical/horizontal scanning
Key InsightOnly need to check until first mismatch

Takeaway: The vertical scanning approach is the most intuitive and efficient for typical inputs. For specialized cases (very long strings, multiple queries), consider binary search or Trie-based solutions.


References