LeetCode Solutions
#14 — Longest Common Prefix

Horizontal, vertical, divide & conquer, binary search — four ways to find the LCP

Color ASCII art: an outdated computer on a desk with a bookshelf in the background, suggesting a classic coding workspace.
Illustration in ASCII art style: an outdated computer on a desk with a bookshelf behind it.

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 "".

Example 1:

Example 2:

Constraints:

  • 1 <= strs.length <= 200
  • 0 <= strs[i].length <= 200
  • strs[i] consists of only lowercase English letters if it is non-empty.

The naïve way — horizontal scanning

The most intuitive approach is to take the first string as a candidate prefix, then iterate over the remaining strings. For each subsequent word, we shorten the candidate until it becomes a prefix of that word. This is called horizontal scanning because we move from left to right across the array, comparing the prefix with each string horizontally.

Python

class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:
        if len(strs) == 1:
            return strs[0]          # Only one string → it's the prefix
        
        prefix = strs[0]
        
        for i in range(1, len(strs)):
            # Keep shortening prefix until it matches the start of strs[i]
            while strs[i].find(prefix) != 0:
                prefix = prefix[:-1]   # Remove last character
                if not prefix:
                    return ""          # No common prefix
        return prefix
            

Let's trace the example strs = ["flower","flow","flight"]:

Step‑by‑Step Simulation: horizontal scan for ["flower","flow","flight"]
Iteration (i) Current string strs[i] Prefix before check Action Prefix after check
start "flower" initial prefix = strs[0] "flower"
i=1 "flow" "flower" "flow".find("flower") != 0 → shorten "flowe"
"flowe" still not match → shorten "flow"
"flow" "flow".find("flow") == 0 → match, move to next string "flow"
i=2 "flight" "flow" "flight".find("flow") != 0 → shorten "flo"
"flo" still not match → shorten "fl"
"fl" "flight".find("fl") == 0 → match, loop ends "fl"
The prefix is gradually reduced from "flower" to "flow" and finally to "fl", which is the longest common prefix.

This method is straightforward but may perform redundant comparisons. In the worst case, we could end up shortening the prefix character by character for each string, leading to O(S) time where S is the sum of all characters in all strings. Space is O(1) auxiliary.

A different angle — vertical scanning

Instead of comparing whole words, we can scan character by character vertically. We pick the first character of the first string and compare it with the first character of every other string. If all match, we move to the second character, and so on. The moment we find a mismatch or reach the end of any string, we stop and return the prefix built so far.

This approach can be more efficient in cases where the common prefix is short or a mismatch occurs early, because we avoid looking at the rest of the strings.

Python

class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:
        if len(strs) == 1:
            return strs[0]
        
        # Scan characters of the first string
        for i in range(len(strs[0])):
            ch = strs[0][i]
            # Compare with every other string at position i
            for j in range(1, len(strs)):
                # Stop if we exceed length or find a mismatch
                if i == len(strs[j]) or strs[j][i] != ch:
                    return strs[0][:i]
        
        # All characters of first string matched in all others
        return strs[0]
            

Let’s trace strs = ["flower","flow","flight"] with vertical scanning:

Step‑by‑Step Simulation: vertical scan for ["flower","flow","flight"]
i (char index) ch (strs[0][i]) j=1 ("flow") j=2 ("flight") Action Prefix so far
0 'f' match match continue "f"
1 'l' match match continue "fl"
2 'o' 'o' (match) 'i' (mismatch) stop, return slice [:2] "fl"
The algorithm stops at index 2 because "flight"[2] is 'i' ≠ 'o'. The prefix returned is the first two characters.

Time complexity is still O(S) in the worst case (when all strings are identical), but often it performs fewer character comparisons. Space remains O(1).

Divide and conquer

This problem also lends itself nicely to a recursive divide‑and‑conquer strategy. The idea: split the array into two halves, find the LCP of each half recursively, and then find the LCP of those two results. This is a classic application of the paradigm and can be useful when we want to parallelize or when the array is large.

The helper function lcp(left, right) finds the common prefix between two strings using a vertical scan, and divide_and_conquer recursively splits the array.

Python

class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:
        def lcp(left: str, right: str) -> str:
            """Return the 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 divide_and_conquer(arr, l, r):
            """Recursively find LCP in arr[l..r]."""
            if l == r:
                return arr[l]
            mid = (l + r) // 2
            left_lcp = divide_and_conquer(arr, l, mid)
            right_lcp = divide_and_conquer(arr, mid + 1, r)
            return lcp(left_lcp, right_lcp)

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

Trace for strs = ["flower","flow","flight"]:

Step‑by‑Step Simulation: divide and conquer
Call depth Array slice (indices) Action Result LCP
0 [0, 1, 2] split into [0,1] and [2]
1 (left) [0, 1] split into [0] and [1]
2 (leaf) [0] return strs[0] = "flower" "flower"
2 (leaf) [1] return strs[1] = "flow" "flow"
1 (merge) lcp("flower", "flow") → "flow" "flow"
1 (right) [2] return strs[2] = "flight" "flight"
0 (merge) lcp("flow", "flight") → "fl" "fl"
The array is recursively divided until base cases, then results are merged pairwise. The final merge of "flow" and "flight" yields "fl".

The recurrence relation for time complexity is T(n) = 2·T(n/2) + O(m), where m is the length of the strings. In the worst case this still results in O(S) total character comparisons. However, the divide‑and‑conquer approach has the advantage of being easily parallelizable and demonstrates a powerful algorithmic pattern.

Optimizing further — binary search

Can we do better than linear scans? If we consider the length of the common prefix, it is bounded by the length of the shortest string. We can perform a binary search on the possible prefix length. For a candidate length mid, we check whether the first mid characters of the first string form a valid common prefix for all strings. If yes, we try a longer length; if not, we try a shorter one.

This approach reduces the number of comparisons in many scenarios, especially when strings are long and the prefix is relatively short or long. The check itself (isLCP(mid)) iterates over the strings, making the overall time O(S·log m) where m is the length of the shortest string. This can be faster than linear scanning in practice.

Python

class Solution:
    def longestCommonPrefix(self, strs: List[str]) -> str:
        def isLCP(length: int) -> bool:
            """Check if the first `length` chars of strs[0] are common."""
            prefix = strs[0][:length]
            for s in strs[1:]:
                if not s.startswith(prefix):
                    return False
            return True

        if len(strs) == 1:
            return strs[0]

        low, high = 0, min(len(s) for s in strs)
        while low <= high:
            mid = (low + high) // 2
            if isLCP(mid):
                low = mid + 1      # Try a longer prefix
            else:
                high = mid - 1     # Try a shorter one

        return strs[0][:high]
            

Trace binary search for strs = ["flower","flow","flight"] where shortest length = 4 ("flow"):

Step‑by‑Step Simulation: binary search on prefix length
Iteration low high mid isLCP(mid)? Action
1 0 4 2 True (prefix "fl" matches all) low = 3
2 3 4 3 False ("flo" not prefix of "flight") high = 2
3 3 2 low > high → loop ends return strs[0][:2] = "fl"
Binary search quickly narrows down the length to 2. Only two calls to isLCP were needed.

The binary search solution elegantly narrows down the prefix length. Space complexity is O(1), and the startswith check uses efficient substring comparison.

Final complexity comparison

All four approaches are correct and run within the problem's constraints. Here's a quick summary:

  • Horizontal scanning: O(S) time, O(1) space. Simple but may do extra work.
  • Vertical scanning: O(S) time, O(1) space. Often stops early.
  • Divide and conquer: O(S) time, O(m·log n) space due to recursion stack (or O(1) with iterative). Demonstrates recursion.
  • Binary search: O(S·log m) time, O(1) space. Can be faster when strings are long and prefix length is extreme.

In an interview setting, the vertical scanning or binary search methods are particularly appreciated for their efficiency and clarity.

Happy coding, and may your complexity always be low.

References & further reading

Can't get enough from lumænaut? Read this...