题目

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

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

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

纵向扫描

最直接的思路:
选取第一个字符串假设为前缀。
用这个前缀的每一个字符,去和所有其他字符串比较,如果其他字符串在该位置也是这个字符,那这个字符就属于前缀;反之,前缀就到此结束。

不需要考虑选取数组中的第一个字符串是否长度是最小或最长的。因为最差情况就是这个字符串是长度最短的,并且所有字符刚好都是前缀。

时间复杂度:最坏情况,数组有n个字符串,字符串全都一样且长度为m,O(mn)
空间复杂度:O(1)

def longestCommonPrefix(strs):
    if not strs:
        return ""

    # 第一个字符串长度,字符串数量
    length, count = len(strs[0]), len(strs)
    for i in range(length):
        # 遍历第一个字符串的每个字符
        c = strs[0][i]
        # 从1开始遍历字符串数组,每个字符串的当前字符!=第一个字符串的当前字符,
        # 或者当前字符的索引==当前字符串的长度(i=0时,len(strs[j])=0,说明有空字符串;
        # i>0时,i==len(strs[j])说明遇到了长度更小的字符串,那当前字符肯定不会是公共前缀了)
        if any(i == len(strs[j]) or strs[j][i] != c for j in range(1, count)):
            return strs[0][:i]
        
    # 排除strs=['']或['a'],只有一个元素的情况
    return strs[0]

横向扫描

假设数组中第一个字符串是前缀。
拿假设前缀,和第二个字符串比较,共有部分是新的前缀。
再拿新的前缀,和第三个字符串比较,共有部分是新的前缀。
一直如此,比到数组最后一个字符串。

时间复杂度:最坏情况和之前一样,O(mn)
空间复杂度:O(1)

def longestCommonPrefix(strs):
    prefix = strs[0]
    for str in strs[1:]:
        minLen, index = min(len(prefix), len(str)), 0
        while index < minLen and prefix[index] == str[index]:
            index += 1
        prefix = prefix[:index]

    return prefix

分治法

分治法是一种思想,就是分而治之。

递归是一种编程技巧,分治思想在代码中适合用递归来实现。

在本题中,将原数组分成两半,分别找出这两部分的公共前缀,再找出这两个前缀的公共前缀,最终就是这个数组的公共前缀。这就是分治思想

时间复杂度O(mn)
空间复杂度O(mlogn),m是字符串平均长度,n是字符串数量

def longestCommonPrefix(strs):
    def split(start, end):
        # 分到不可再分,返回字符串
        if start == end:
            return strs[start]
        mid = (start + end) // 2
        # 递归分割数组
        left, right = split(start, mid), split(mid + 1, end)
        # 以两个字符串中较小的长度作为遍历范围
        minLen = min(len(left), len(right))
        print(left, right, minLen)
        for i in range(minLen):
            # 当出现不一致,则该层公共前缀已找到
            if left[i] != right[i]:
                return left[:i]
        # 没有不一致,长度较小的字符串全部遍历完,则长度较小的字符串就是公共前缀
        return left[:minLen]

    return split(0, len(strs) - 1)

二分法

公共前缀的长度一定不会超过最小长度的字符串。
我们再最小长度字符串上使用二分法,将其分成两半,用左半边字符去和每个字符串比较
如果每个字符串都有,那么就移动左指针
如果有字符串没有,那么就移动右指针,缩小公共范围

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

空间复杂度:O(1)O(1)。使用的额外空间复杂度为常数。

def longestCommonPrefix(strs):
    minLen = min(len(str) for str in strs)
    left, right = 0, minLen - 1
    # 循环条件是<=,left和right会移动到同一个字符上,有==的情况
    while left <= right:
        mid = (left + right) // 2
        # all()函数,全部为True返回True
        allTrue = all(strs[0][:mid + 1] == str[:mid + 1] for str in strs)
        if allTrue:
            left = mid + 1
        else:
            right = mid - 1
    return strs[0][:left]

Ethan
140 声望11 粉丝

水平较低,只是记录,谨慎参阅


引用和评论

0 条评论