最长公共前缀

数组 字符串

题目描述

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 ""

思路分析

查找多个字符串的最长公共前缀,比较直观且高效的方法是纵向扫描法

我们可以将字符串数组看作是一个二维字符矩阵。既然是查找“所有”字符串的公共前缀,那么前缀的最大长度一定不会超过数组中第一个字符串的长度。因此,可以将第一个字符串设定为“基准”,一列一列地向下对比所有字符串在同一位置上的字符。

算法步骤

  1. 边界处理:如果传入的数组为空或长度为 0,说明没有公共前缀,直接返回空字符串 ""
  2. 设定基准:以第一个字符串 strs[0] 为准进行外层循环,遍历它的每一列(每个字符)。
  3. 纵向对比:在内层循环中,逐个检查数组里的其他字符串,看它们在相同位置(同一列)的字符是否与基准字符相等。
  4. 提前终止:一旦发现某个字符串已经到达末尾,或者在对应位置的字符与基准字符不匹配,就说明在这个位置“断开”了,前面的内容即为最长公共前缀,将其截取并返回。
  5. 完整遍历:如果外层循环能完整结束,说明第一个字符串本身就是整个数组的最长公共前缀,直接返回它。

示例代码

/**
 * 查找字符串数组中的最长公共前缀。
 * 采用“纵向扫描法”:以第一个字符串为基准,一列一列地向下对比所有字符串的同一个位置上的字符。
 *
 * @param strs 需要检查的字符串数组
 * @returns 所有字符串的最长公共前缀,如果没有则返回空字符串 ""
 */
export function longestCommonPrefix(strs: string[]): string {
    // 处理边界情况:如果传入的数组为空或不存在,说明没有公共前缀,直接返回空字符串
    if (!strs || strs.length === 0) {
        return "";
    }

    // 外层循环:遍历第一个字符串的每一列(即每一个字符的索引 i)
    for (let i = 0; i < strs[0].length; i++) {
        // 拿到第一个字符串在第 i 列的“基准字符”
        const char = strs[0][i];

        // 内层循环:逐个遍历数组中的其他字符串(从索引 1,也就是第二个字符串开始)
        for (let j = 1; j < strs.length; j++) {
            // 这里判断是否遇到“不匹配”的情况,有两种可能:
            // 1. i === strs[j].length:当前参与比较的字符串比较短,已经走到头了
            // 2. strs[j][i] !== char:当前参与比较的字符串在第 i 个位置的字符,与基准字符不同
            if (i === strs[j].length || strs[j][i] !== char) {
                // 一旦遇到不一样的地方,说明之前的字符就是最长的公共前缀了。
                // 这时从第一个字符串截取 0 到 i 的部分(但不包含 i)直接返回
                return strs[0].substring(0, i);
            }
        }
    }

    // 如果全部列都扫描完了,循环没有被中断过,说明第一个字符串本身就是所有的字符串的公共前缀
    return strs[0];
}

复杂度分析

  • 时间复杂度O(m * n),其中 m 是字符串的平均长度,n 是字符串数组的长度。最坏情况下(所有字符串完全相同),所有字符都会被比较一次。
  • 空间复杂度O(1),除了保存循环变量用到常数级内存,只截取字符串作为返回值,没有额外使用其他具有动态伸缩空间的集合结构。

示例演示

strs = ["flower", "flow", "flight"] 为例:

第一个循环(第 0 列):以 strs[0][0] = 'f' 作为基准。

  • strs[1][0] = 'f',匹配。
  • strs[2][0] = 'f',匹配。

第二个循环(第 1 列):以 strs[0][1] = 'l' 作为基准。

  • strs[1][1] = 'l',匹配。
  • strs[2][1] = 'l',匹配。

第三个循环(第 2 列):以 strs[0][2] = 'o' 作为基准。

  • strs[1][2] = 'o',匹配。
  • strs[2][2] = 'i',与 'o' 不匹配

此时退出循环并截取第一个单词的前两列内容。 即 strs[0].substring(0, 2),返回对应子串 fl