最长公共前缀

1127 字
6 分钟
最长公共前缀

题目链接:14. 最长公共前缀 - 力扣(LeetCode)

  • 难度:简单
  • 标签:字典树、数组、字符串

题目描述#

Note

原题说明: 编写一个函数来查找字符串数组中的最长公共前缀。如果不存在公共前缀,返回空字符串 ""

示例#

  • 输入strs = ["flower","flow","flight"]
  • 输出"fl"

提示#

  • 1strs.length2001 \le strs.length \le 200
  • 0strs[i].length2000 \le strs[i].length \le 200
  • strs[i] 仅由小写英文字母组成。

方案一:纵向扫描(我的初版)#

核心思路: 从第一个字符串的第一个字符开始,依次与后面所有字符串的同一位置进行对比。一旦发现长度不够或者字符不匹配,立即返回。

源码实现#

class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
if(strs.empty()) return "";
for (int i = 0; i < strs[0].size(); ++i) {
char c = strs[0][i];
for (int j = 1; j < strs.size(); ++j) {
// 如果当前字符串长度不够,或者字符不匹配,就返回当前前缀
if (i >= strs[j].size() || strs[j][i] != c) {
return strs[0].substr(0, i);
}
}
}
return strs[0];
}
};

复杂度分析#

  • 时间复杂度O(mn)O(mn)。其中 mm 是字符串数组中所有字符串的平均长度,nn 是字符串的数量。最坏情况下(所有字符串都完全相同),我们需要遍历所有字符。
  • 空间复杂度O(1)O(1)。只使用了常数级的额外空间。

方案二:横向扫描(逐步缩减)#

核心思路: 假设第一个单词就是前缀,然后拿它不断去和后面的单词做匹配,不匹配就砍掉最后一个字符,直到匹配为止。

class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
if (strs.empty()) return "";
string prefix = strs[0];
for (size_t i = 1; i < strs.size(); ++i) {
// rfind(..., 0) == 0 表示 strs[i] 以 prefix 开头
while (strs[i].rfind(prefix, 0) != 0) {
prefix.pop_back(); // 砍掉最后一个字符
if (prefix.empty()) return "";
}
}
return prefix;
}
};

复杂度分析#

  • 时间复杂度O(S)O(S)SS 是所有字符串中字符数量的总和。
  • 空间复杂度O(1)O(1)

方案三:排序比对(巧妙解法)#

核心思路: 将数组按字典序排序。排序后,只需要对比第一个单词最后一个单词的公共前缀即可。因为这两个单词是差异最大的,如果它们有共同点,那么中间的所有单词一定也包含这个前缀。

#include <algorithm>
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
if (strs.empty()) return "";
// 字典序排序
sort(strs.begin(), strs.end());
const string &first = strs.front(), &last = strs.back();
int i = 0;
int n = min(first.size(), last.size());
while (i < n && first[i] == last[i]) ++i;
return first.substr(0, i);
}
};

复杂度分析#

  • 时间复杂度O(nLlogn)O(n \cdot L \cdot \log n)。其中 nn 是单词数,LL 是平均长度。排序需要的时间开销。
  • 空间复杂度O(L)O(L)。用于存储排序相关的开销。

方案四:字典树(Trie Tree)#

核心思路: 将所有单词存入字典树,记录每个节点的 pass(经过该节点的单词数)。从根节点开始向下找,只要当前节点满足“只有一个子节点”且“子节点的 pass == 总单词数”,它就是公共前缀的一部分。

源码实现#

struct TrieNode {
TrieNode* next[26] = {nullptr};
int pass = 0; // 经过该节点的单词数
};
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
if (strs.empty()) return "";
TrieNode* root = new TrieNode();
const int n = strs.size();
// 1. 建树
for (auto &s : strs) {
TrieNode* p = root;
for (char c : s) {
int idx = c - 'a';
if (!p->next[idx]) p->next[idx] = new TrieNode();
p = p->next[idx];
p->pass++;
}
}
// 2. 遍历找最长唯一链
string ans;
TrieNode* p = root;
while (true) {
int cnt = 0, idx = -1;
for (int i = 0; i < 26; ++i) {
if (p->next[i]) { cnt++; idx = i; }
}
// 要求:只有一个分支,且该分支被所有单词经过
if (cnt != 1 || p->next[idx]->pass != n) break;
ans += char('a' + idx);
p = p->next[idx];
}
return ans;
}
};

复杂度分析#

  • 时间复杂度O(S)O(S)SS 是所有字符的总数。
  • 空间复杂度O(S×Σ)O(S \times \Sigma)Σ\Sigma 是字符集大小(26个字母)。

总结#

  • 简单题也有深意:纵向扫描是最容易想到的。
  • 排序大法好:字符串排序比对是一种非常巧妙的思维跃迁。
  • 字典树:虽然在这题中略显笨重(空间开销大),但在处理海量数据的各种前缀问题时它是真正的神器。
Tip

如果字符串数量不多但都很长,排序法非常高效;如果你对字典树这种高级数据结构感兴趣,这道题是一个极好的入门练习。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

最长公共前缀
https://firefly-7a0.pages.dev/posts/leetcode_notes/14longest-common-prefix/
作者
lonelystar
发布于
2025-10-04
许可协议
CC BY-NC-SA 4.0
最后更新于 2025-10-04,距今已过 207 天

部分内容可能已过时

评论区

Profile Image of the Author
LonelyStar
Hello, I'm LonelyStar.
公告
欢迎来到我的博客!
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
119
分类
11
标签
346
总字数
226,548
运行时长
0
最后活动
0 天前

目录