14.最长公共前缀问题-Easy
问题描述
给定一个字符串数组 strs,找出所有字符串的最长公共前缀。如果不存在公共前缀,返回空字符串 ""。
示例
1 2 3 4 5
| 输入:strs = ["flower","flow","flight"] 输出:"fl"
输入:strs = ["dog","racecar","car"] 输出:""
|
方法一:水平扫描法(逐字符比较)
核心思想
- 以第一个字符串为基准。
- 逐个字符与其他字符串的对应位置比较。
- 遇到不匹配时停止,返回当前匹配的前缀。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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(1)。
方法二:垂直扫描法(逐列比较)
核心思想
- 按列比较所有字符串的相同位置字符。
- 遇到不匹配时停止。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| var longestCommonPrefix = function(strs) { if (strs.length === 0) return ""; for (let i = 0; i < strs[0].length; i++) { const char = strs[0][i]; for (let j = 1; j < strs.length; j++) { if (i === strs[j].length || strs[j][i] !== char) { return strs[0].slice(0, i); } } } return strs[0]; };
|
复杂度分析
- 时间复杂度:O(S),最坏情况下需要比较所有字符。
- 空间复杂度:O(1)。
方法三:分治法
核心思想
- 将问题分解为子问题:分别求左半部分和右半部分的最长公共前缀。
- 合并子问题的解。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| var longestCommonPrefix = function(strs) { if (strs.length === 0) return ""; return divideAndConquer(strs, 0, strs.length - 1); };
function divideAndConquer(strs, left, right) { if (left === right) return strs[left]; const mid = Math.floor((left + right) / 2); const leftPrefix = divideAndConquer(strs, left, mid); const rightPrefix = divideAndConquer(strs, mid + 1, right); return commonPrefix(leftPrefix, rightPrefix); }
function commonPrefix(str1, str2) { const minLen = Math.min(str1.length, str2.length); for (let i = 0; i < minLen; i++) { if (str1[i] !== str2[i]) { return str1.slice(0, i); } } return str1.slice(0, minLen); }
|
复杂度分析
- 时间复杂度:O(S),其中 S 是所有字符串的字符总数。
- 空间复杂度:O(m log n),递归调用栈的深度。
方法四:二分查找法
核心思想
- 对最短字符串的长度进行二分查找。
- 检查当前长度是否是所有字符串的公共前缀。
代码实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| var longestCommonPrefix = function(strs) { if (strs.length === 0) return ""; let minLen = Math.min(...strs.map(s => s.length)); let low = 1, high = minLen; while (low <= high) { const mid = Math.floor((low + high) / 2); if (isCommonPrefix(strs, mid)) { low = mid + 1; } else { high = mid - 1; } } return strs[0].slice(0, (low + high) / 2); };
function isCommonPrefix(strs, len) { const prefix = strs[0].slice(0, len); for (let i = 1; i < strs.length; i++) { if (!strs[i].startsWith(prefix)) { return false; } } return true; }
|
复杂度分析
- 时间复杂度:O(S log m),其中 m 是最短字符串的长度。
- 空间复杂度:O(1)。
方法对比
| 方法 |
时间复杂度 |
空间复杂度 |
适用场景 |
| 水平扫描 |
O(S) |
O(1) |
通用 |
| 垂直扫描 |
O(S) |
O(1) |
字符较短时 |
| 分治法 |
O(S) |
O(m log n) |
大规模数据 |
| 二分查找 |
O(S log m) |
O(1) |
字符串较长时 |
边界情况处理
- 空数组:直接返回
""。
- 单字符串:返回该字符串本身。
- 完全匹配:返回最短字符串。
总结
- 推荐方法:垂直扫描法(代码简洁,效率高)。
- 优化选择:如果字符串非常长,考虑二分查找法。
- 分治法的优势:适合分布式计算或超大规模数据。