当前:LC14 · 最长公共前缀 · 首次出现于 Day 15 · 路径:顶栏「56天打卡」→ 点击 LC 题号 → 逐题动画

LC14 · Longest Common Prefix · 字符串

最长公共前缀:逐列比对,遇异即止

公共前缀是所有串都认的开头。拿第一串当标尺一列一列比,谁先露馅就截在那一列之前。

公共前缀一定是 strs[0] 的某个前缀。纵向:扫第 j 列,任一串在此列越界或字符对不上,答案就是 strs[0][:j]。横向:先把答案设成第一串,再对每个后续串把答案削到双方的公共前缀,削空即可停。两种都从「必须是每一串的开头」推出来。时间 O(S)(S 为所有字符数),空间 O(1)。

时间 O(S)空间 O(1)结论先行 · 全文约 6 节
导读

这是 LeetCode 14. Longest Common Prefix。给你一个字符串数组 strs,写出所有串共同拥有的最长前缀。前缀必须从下标 0 开始连着取,不能从中间挖一段。没有任何公共开头就返回空串。数组为空也返回空串。

主例 strs = ["flower", "flow", "flight"]。三串开头两个字符都是 f、l,第三列分别是 o、o、i,在这里分开,答案是 "fl"。对照 ["dog", "racecar", "car"],第一列 d/r/c 就对不上,答案是 ""。再对照 ["ab", "a"]:第二列时短串已经没字符了,答案只能是 "a"。

暴力可以对每个可能的前缀长度去问「是不是每一串都有这段」,重复比较很多列。缺的不是「公共开头」这个定义,而是一次扫描里同时处理两种失败:列上字符不同,以及某一串已经走完。本文用手走主例的每一列,并说明横向缩短为什么得到同一段 "fl"。

以第一串为标尺,一列一列往下比

第一直觉是枚举长度。主例最短串是 flow,长度 4,于是试 "f"、"fl"、"flo"、"flow" 是不是三串都认。"flo" 已经被 flight 否决,前面两段却被反复确认。结果能对,每一列被问了太多次。

缺的是一根不会漏解的标尺。若公共前缀非空,它一定也是第一串的前缀——否则第一串自己就不认这段开头。所以拿 strs[0] 当尺子:只问第 0 列、第 1 列……其余每一串在这一列是不是同一个字符。尺子本身不会比真正的公共前缀更短;一旦某列失败,尺子右侧全部作废。

这就是纵向比较。外层走列 j,内层走串 i。第 j 列全员相同,才有资格看下一列。任一串在这一列越界,或 strs[i][j] != strs[0][j],立刻返回 strs[0][:j]。循环能把第一串走完,说明第一串整段都被其余串认下,答案就是 strs[0]。

用手走主例。第 0 列:f / f / f,通过。第 1 列:l / l / l,通过。第 2 列:o / o / i,flight 露馅。演示停在这一列,公共前缀截在 [0, 2),就是 "fl"。后面的 w、e、r 不必再看——它们连第二串都未必对齐,更不可能是三串共享的开头。

同一事实也可以横着削。先令 ans = "flower",拿 "flow" 去削:第四个字符起对不上,ans 变成 "flow";再拿 "flight" 去削:第三个字符 o/i 对不上,ans 变成 "fl"。再来一串就继续削,削空就可以提前停。纵向按列停,横向按串停,比的都是「当前候选还是不是每一串的前缀」,答案同为 "fl"。

为什么第一串能当标尺公共前缀是交集。交集再短,也还是第一串的前缀。用 strs[0] 当尺子不会漏掉更长的公共段,因为更长的段第一串自己都没有。
第 2 列:f/l 不同 → 停止
flower
flow
flight
第 0 列全是 f ✓

短串先走完,也要立刻截断

只检查「字符不同」会漏掉另一种失败:某一串比标尺短,第 j 列根本没有字符。公共前缀不能比最短串更长,越界和字符冲突是并列的停止条件。

对照 ["ab", "a"]。第 0 列 a/a,通过。第 1 列标尺要看 b,第二串长度为 1,已经没字符了。演示停在这一列:返回 strs[0][:1],也就是 "a"。写成 if j >= len(strs[i]) || strs[i][j] != strs[0][j],越界必须写在读字符前面,否则会踩空。

空串在数组里时,第 0 列立刻越界,答案是 ""。只有一串时,没有人能否定它,答案就是它自己。这些边界都落在同一句判断上,不必另开一条路。

短串越界即停
ab
a
"a" 在第 1 列越界 → 前缀 "a"

Go:逐列扫描

solution.goGo
func longestCommonPrefix(strs []string) string {
if len(strs) == 0 { return "" }
for j := 0; j < len(strs[0]); j++ {
for i := 1; i < len(strs); i++ {
if j >= len(strs[i]) || strs[i][j] != strs[0][j] {
return strs[0][:j]
}
}
}
return strs[0]
}

1空数组没有标尺,直接返回空串。只有一串时内层循环不跑,最后返回 strs[0]。

2外层 j 走标尺的每一列。能走到这里,说明前 j 列已经被所有串认下。

3内层从第二串比起。j >= len(strs[i]) 必须写在读 strs[i][j] 前面。

4越界或对不上,公共部分刚好是标尺的前 j 个字符,主例 j=2 时切出 "fl"。

5标尺每一列都过了,说明第一串整段都是公共前缀。

总结

公共前缀必是第一串的前缀。逐列比到分歧或越界,主例在 o/o/i 切开,得到 fl。

  • 跳着取公共字符、找最长公共子串,都不是这题。必须从下标 0 连着取。
  • 纵向按列停,横向按串把候选削短,比的是同一件事。主例两种走法都得到 "fl"。
  • 停止条件有两个:字符不同,以及短串越界。["ab","a"] 死在第二列越界,不是死在字符冲突。
同族题目
LC28找出字符串中第一个匹配项的下标LC151反转字符串中的单词LC125验证回文串