1. 问题背景与理解
最长公共前缀(Longest Common Prefix)是字符串处理中的经典问题,也是算法面试中的高频考点。这个问题要求在一组字符串中找出它们共同拥有的最长的前缀部分。比如["flower","flow","flight"]这组字符串的最长公共前缀就是"fl"。
在实际开发中,这类字符串匹配问题广泛存在于搜索引擎、文件系统路径匹配、DNS解析等领域。理解并掌握这个问题的解法,不仅能帮助我们应对算法面试,更能提升日常开发中处理字符串相关业务的能力。
2. 解题思路分析
2.1 暴力解法(纵向扫描)
最直观的解法是逐个字符比较所有字符串:
- 以第一个字符串为基准
- 依次比较每个字符串的第i个字符
- 当发现不匹配或某个字符串已结束时停止
- 返回当前匹配的前缀
这种方法时间复杂度为O(S),其中S是所有字符串字符数的总和。空间复杂度为O(1),只需要常数空间存储结果。
2.2 优化解法(横向扫描)
另一种思路是两两比较字符串:
- 初始公共前缀设为第一个字符串
- 依次与后续字符串比较,更新公共前缀
- 当公共前缀为空时提前终止
- 返回最终公共前缀
这种方法在最坏情况下时间复杂度也是O(S),但平均情况下可能更快。
2.3 分治法
将问题分解为子问题:
- 将字符串数组分成两部分
- 分别求出两部分的公共前缀
- 再求这两个前缀的公共前缀
这种方法时间复杂度为O(S),空间复杂度为O(mlogn),其中n是字符串数量,m是字符串平均长度。
3. 代码实现与解析
3.1 暴力解法实现
python复制def longestCommonPrefix(strs):
if not strs:
return ""
prefix = []
for i in range(len(strs[0])):
char = strs[0][i]
for s in strs[1:]:
if i >= len(s) or s[i] != char:
return "".join(prefix)
prefix.append(char)
return "".join(prefix)
关键点:
- 处理空输入的特殊情况
- 使用第一个字符串作为基准
- 逐个字符比较所有字符串
- 遇到不匹配立即返回
3.2 横向扫描实现
python复制def longestCommonPrefix(strs):
if not strs:
return ""
prefix = strs[0]
for s in strs[1:]:
while not s.startswith(prefix):
prefix = prefix[:-1]
if not prefix:
return ""
return prefix
关键点:
- 初始前缀设为第一个字符串
- 使用startswith方法判断前缀
- 逐步缩短前缀直到匹配
- 前缀为空时提前返回
4. 边界条件与异常处理
4.1 空输入处理
当输入数组为空时,应该返回空字符串:
python复制if not strs:
return ""
4.2 单字符串情况
当只有一个字符串时,该字符串本身就是最长公共前缀:
python复制if len(strs) == 1:
return strs[0]
4.3 包含空字符串的情况
如果数组中包含空字符串,公共前缀必定为空:
python复制if "" in strs:
return ""
5. 性能优化技巧
5.1 提前终止
在比较过程中,一旦发现公共前缀为空,可以立即终止循环:
python复制if not prefix:
break
5.2 最小长度优化
可以先找出最短字符串的长度,避免越界检查:
python复制min_len = min(len(s) for s in strs)
for i in range(min_len):
# 比较逻辑
5.3 字符集优化
如果知道字符串只包含特定字符集(如小写字母),可以利用这个特性进行优化。
6. 实际应用场景
6.1 文件路径匹配
在文件系统中查找共同父目录时,可以看作是最长公共前缀问题。
6.2 自动补全功能
搜索引擎的自动补全功能需要快速找出用户输入与词库的共同前缀。
6.3 路由匹配
Web框架中的路由匹配也涉及到前缀匹配问题。
7. 常见错误与调试
7.1 数组越界
忘记检查字符串长度导致越界:
python复制# 错误写法
for i in range(len(strs[0])):
for s in strs:
if s[i] != strs[0][i]: # 可能越界
7.2 初始条件遗漏
忘记处理空输入或单字符串的特殊情况。
7.3 更新逻辑错误
在横向扫描中,前缀更新逻辑错误:
python复制# 错误写法
prefix = s[:i] # 应该逐步缩短而不是重新切片
8. 测试用例设计
8.1 常规测试用例
python复制["flower","flow","flight"] # 应返回 "fl"
["dog","racecar","car"] # 应返回 ""
8.2 边界测试用例
python复制[] # 应返回 ""
["a"] # 应返回 "a"
["",""] # 应返回 ""
["a","b"] # 应返回 ""
8.3 性能测试用例
python复制["a"*1000]*1000 # 1000个长度为1000的相同字符串
["a"*1000,"b"*1000] # 完全不匹配的长字符串
9. 算法复杂度分析
9.1 时间复杂度
所有解法在最坏情况下都是O(S),其中S是所有字符串字符数的总和。这是因为在最坏情况下需要比较所有字符串的所有字符。
9.2 空间复杂度
暴力解法和横向扫描的空间复杂度都是O(1),只需要常数空间存储结果。分治法的空间复杂度是O(mlogn),因为递归调用栈的深度是logn。
10. 其他语言实现
10.1 Java实现
java复制public String longestCommonPrefix(String[] strs) {
if (strs == null || strs.length == 0) return "";
String prefix = strs[0];
for (int i = 1; i < strs.length; i++) {
while (strs[i].indexOf(prefix) != 0) {
prefix = prefix.substring(0, prefix.length() - 1);
if (prefix.isEmpty()) return "";
}
}
return prefix;
}
10.2 C++实现
cpp复制string longestCommonPrefix(vector<string>& strs) {
if (strs.empty()) return "";
string prefix = strs[0];
for (int i = 1; i < strs.size(); ++i) {
while (strs[i].find(prefix) != 0) {
prefix = prefix.substr(0, prefix.length() - 1);
if (prefix.empty()) return "";
}
}
return prefix;
}
11. 进阶思考
11.1 字典树(Trie)解法
可以使用字典树来存储所有字符串,然后从根节点向下遍历,直到遇到分叉点,之前的部分就是最长公共前缀。
11.2 并行处理
对于大规模字符串集合,可以考虑将字符串分组并行处理,最后合并结果。
11.3 二分查找优化
对最短字符串的长度进行二分查找,验证某个长度是否可能是公共前缀。