题目
https://leetcode.cn/leetbook/read/array-and-string/ceda1/
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""
。
实现
一、暴力搜索 + 计数统计
思路:从后向前遍历所有可能的前缀,统计每个前缀在字符串数组中出现的次数,最终找到满足条件的最长前缀。
- 边界处理:
- 如果输入数组
strs
为空,直接返回空字符串""
。 - 如果数组只有一个字符串,直接返回该字符串本身(因为无需比较)。
- 预处理:
- 将字符串数组按长度升序排序,目的是以最短字符串为基准生成所有可能的前缀(减少无效比较)。
- 初始化计数数组:
- 创建一个长度比最短字符串大 1 的数组
count
,并初始化为 0。- 多出的一个长度是为了兼容最短字符串本身作为前缀的情况(例如
strs[0].slice(0, strs[0].length)
即完整字符串)。
- 多出的一个长度是为了兼容最短字符串本身作为前缀的情况(例如
- 从后向前遍历所有可能的前缀:
- 对最短字符串
strs[0]
,从完整长度开始逐步缩短(从后向前截取),生成子串sub
。 - 统计
sub
在数组中作为前缀出现的次数:- 遍历所有字符串,检查是否以
sub
开头,若是则计数count[i]++
。
- 遍历所有字符串,检查是否以
- 确定最长公共前缀:
- 从计数数组
count
的末尾向前查找第一个满足count[i] === strs.length
的索引i
,表示所有字符串均以strs[0].slice(0, i)
为前缀。 - 返回该子串作为结果。
- 默认返回空字符串:
- 若没有满足条件的前缀,返回
""
。
// 暴力:从后向前遍历
var longestCommonPrefix1 = function (strs) {
if (strs.length === 0) return '';
if (strs.length === 1) return strs[0]; // 处理单个字符串的情况
strs.sort((a, b) => a.length - b.length);
let count = new Array(strs[0].length + 1).fill(0); // 增加一个长度以处理完整字符串
for (let i = strs[0].length; i >= 0; i--) {
let sub = strs[0].slice(0, i);
for (let j = 0; j < strs.length; j++) {
if (strs[j].startsWith(sub)) {
count[i]++;
}
}
}
for (let i = count.length - 1; i >= 0; i--) {
if (count[i] === strs.length) {
return strs[0].slice(0, i);
}
}
return '';
};
时间复杂度:排序 O(n log n)
+ 双层循环 O(m * n)
:O(m + log n)
空间复杂度:计数数组 O(m)
+ 排序空间 O(log n)
:O(m + log n)
二、优化:提前终止 + 去除排序
- 提前终止:在统计
count[i]
时,若发现某个字符串不匹配当前sub
,可提前跳出内层循环。 - 避免排序:直接以第一个字符串为基准,无需排序(但需处理基准字符串非最短的情况)。 修改后的代码:
var longestCommonPrefix = function(strs) {
if (strs.length === 0) return '';
const base = strs[0];
for (let i = base.length; i >= 0; i--) {
const sub = base.slice(0, i);
if (strs.every(s => s.startsWith(sub))) {
return sub;
}
}
return '';
};
时间复杂度:
- 最坏情况:
O(m * n)
(m
是base
的长度,n
是字符串数量)。 - 最佳情况:
O(minLen * n)
(快速找到短前缀)。
空间复杂度:O(1)
(仅用常量空间)。
三、水平扫描
思路:逐个比较字符串,逐步缩小公共前缀的范围。
- 假设第一个字符串
strs[0]
是最长公共前缀LCP
。 - 遍历剩余字符串,不断调整
LCP
,使其与当前字符串匹配。 - 如果
LCP
变为空字符串,提前终止。
// 优化解法:水平扫描
var longestCommonPrefix = function (strs) {
if (strs.length === 0) return '';
let prefix = strs[0];
for (let i = 1; i < strs.length; i++) {
while (strs[i].indexOf(prefix) !== 0) {
prefix = prefix.slice(0, prefix.length - 1);
if (prefix === '') return '';
}
}
return prefix;
};
时间复杂度:
- 最坏情况:
O(S)
(S
是所有字符串的字符总数)。 - 最佳情况:
O(minLen * n)
。 空间复杂度:O(1)
。