编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀,返回空字符串 ""。
示例 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)。使用的额外空间复杂度为常数。