14. 最长公共前缀

423 阅读6分钟

前面13个题的地址

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

示例 1:
输入:strs = ["flower","flow","flight"]
输出:"fl"

示例 2:
输入:strs = ["dog","racecar","car"]
输出:""
解释:输入不存在公共前缀。

解法一

自己的暴力解法。主要思路:把数组的第一个字符串拿出来和其他字符串比较,遍历第一个字符串的字符,遇到不相同的字符,就直接返回之前遍历过的字符,就是最长的公共前缀,如果有个字符串提前遍历完了,那么这个字符串就是最长的公共前缀。

纵向扫描

def longestCommonPrefix1(strs):
    if len(strs) == 0: return ""
    firt = strs[0]
    i = 0
    while i < len(firt):
        for str in strs:
            if len(str) == 0:
                return ""
            elif i < len(str) and str[i] != firt[i]:
                return firt[0:i]
            elif i >= len(str):
                return str
        i += 1
    return firt[0:i]

官方做法:

def longestCommonPrefix1(strs):
    """纵向扫描"""
    if not strs:
        return ""
    # 获取第一个字符串的长度和数组长度
    length, count = len(strs[0]), len(strs)
    # 遍历第一个字符串,每次比较相同位置的第一个字符串字符和其他字符串字符
    for i in range(length):
        c = strs[0][i]  # 取到第一个字符串的字符
        # 当i == len(strs[j]) or strs[j][i] != c为True时,就返回
        # 也就是当遍历到一个字符串结束,或者有个字符不等时就返回
        # 等价于下面三句注释的代码
        if any(i == len(strs[j]) or strs[j][i] != c for j in range(1, count)):
            return strs[0][:I]
        # for j in range(1, count):
        #     if i == len(strs[j]) or strs[j][i] != c:
        #         return strs[0][:I]
    # 如果能遍历完第一个字符串,那么第一个字符串就是最长公告前缀
    return strs[0]

解法二

参考力扣官方答案,可知我上面的方法是纵向扫描法。还可以采用横向扫描法。

主要思想:依次遍历字符串数组中的每个字符串,对于每个遍历到的字符串,更新最长公共前缀,当遍历完所有的字符串以后,即可得到字符串数组中的最长公共前缀。

横向扫描

def longestCommonPrefix2(strs):
    """横向扫描"""
    # 当数组为空时,就直接返回空字符串
    if not strs:
        return ""
    # 取第一个字符串为被比较的前一个字符串
    prefix, count = strs[0], len(strs)
    # 从第二位开始遍历,因为第一个字符串已经被取出来了
    for i in range(1, count):
        # 获取第一个字符串和第二个字符串的公共前缀
        # 再将前面的得到的公共前缀和后一个字符串比较,获取新的公共前缀
        prefix = lcp(prefix, strs[I])
        # 如果在尚未遍历完所有的字符串时,最长公共前缀已经是空串
        # 则最长公共前缀一定是空串,因此不需要继续遍历剩下的字符串,直接返回空串即可
        if not prefix:
            break
    return prefix

复杂度分析

上面的解法一和解法二的复杂度是一样的。

  • 时间复杂度:O(mn),其中 m 是字符串数组中的字符串的平均长度,n 是字符串的数量。最坏情况下,字符串数组中的每个字符串的每个字符都会被比较一次。
  • 空间复杂度:O(1)。使用的额外空间复杂度为常数。

解法三

参考力扣官方答案,采用分治法。

主要思想:

  • 将数组分成前后两堆,分别求出两堆的最长公共前缀,然后再对这两个公共前缀求出共同的前缀,那么就得到了整个数组的公共前缀。
  • 分成前后两堆的的子数组,也可以分别进行上面的操作,再分别分成更小的数组,分别求出公共前缀,也即是递归下去。

分治法 & 递归

def longestCommonPrefix3(strs):
    """分治法"""
    def lcp(start, end):
        # 当递归二分下去,start和end相等时,即为同一个字符串,不能再分治下去了
        if start == end:
            return strs[start]
        # 获取中间角标,继续左右分治
        mid = (start + end) // 2
        # 递归分别求出左边和右边的最长公共前缀
        lcpLeft, lcpRight = lcp(start, mid), lcp(mid + 1, end)
        minLength = min(len(lcpLeft), len(lcpRight))
        # 遍历获取最后的公共前缀
        for i in range(minLength):
            if lcpLeft[i] != lcpRight[I]:
                return lcpLeft[:I]
        # 如果遍历完了,都没有返回,那么minLength就是公共前缀的长度
        return lcpLeft[:minLength]

    # 调用上面函数
    return "" if not strs else lcp(0, len(strs) - 1)

复杂度分析

  • 时间复杂度:O(mn),其中 m 是字符串数组中的字符串的平均长度,n 是字符串的数量。时间复杂度的递推式是T(n)=2⋅T( n/2 )+O(m),通过计算可得 T(n)=O(mn)。
  • 空间复杂度:O(mlogn),其中 m 是字符串数组中的字符串的平均长度,n 是字符串的数量。空间复杂度主要取决于递归调用的层数,层数最大为logn,每层需要 m 的空间存储返回结果

解法四

参考力扣官方答案,还可以采用二分查找进行求解。

主要思想:最长公共前缀的长度不会超过字符串数组中的最短字符串的长度。用minLength 表示字符串数组中的最短字符串的长度,则可以在 [0,minLength] 的范围内通过二分查找得到最长公共前缀的长度。每次取查找范围的中间值mid,判断每个字符串的长度为mid 的前缀是否相同,如果相同则最长公共前缀的长度一定大于或等于mid,如果不相同则最长公共前缀的长度一定小于mid,通过上述方式将查找范围缩小一半,直到得到最长公共前缀的长度。

二分查找

def longestCommonPrefix4(strs):
    """二分查找"""
    # 该长度的子串是否是所有字符的公共前缀
    def isCommonPrefix(length):
        # 取第一个字符串的length长度为比较对象
        str0, count = strs[0][:length], len(strs)
        # 从第二位开始遍历,比较是否length长度的子串是公共前缀
        return all(strs[i][:length] == str0 for i in range(1, count))

    if not strs:
        return ""

    minLength = min(len(s) for s in strs)  # 获取最短的字符串长度
    # 最高位不能写作minLength-1,不然当["a"],只有一位时,不能进入while循环
    low, high = 0, minLength
    while low < high:
        # 获取中间位置
        mid = low + (high - low + 1) // 2
        if isCommonPrefix(mid):
            # 如果前面部分都是公共前缀,那么后面可能还有公告前缀,就将low移到mid位置
            low = mid
        else:
            # 反正如果前面部分不是公共前缀,就将high移到mid-1位置
            high = mid - 1
    # 最后的low就是最长公共前缀的长度
    return strs[0][:low]

复杂度分析

  • 时间复杂度:O(mnlogm),其中 m 是字符串数组中的字符串的最小长度,n 是字符串的数量。二分查找的迭代执行次数是 O(logm),每次迭代最多需要比较mn 个字符,因此总时间复杂度是 O(mnlogm)。
  • 空间复杂度:O(1)。使用的额外空间复杂度为常数。

力扣官方答案