学习分治的写法和二分查找提升速度的应用
方法一:纵向扫描
遍历第一个字符串,与列表中的其他字符串的相同位置字符逐一比对
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
if len(strs) == 0:
return ''
common_prefix = ''
for i, cur in enumerate(strs[0]):
for j in range(len(strs)):
if i >= len(strs[j]) or cur != strs[j][i]:
return common_prefix
common_prefix += cur
return common_prefix
复杂度分析
- 时间复杂度: O ( m n ) O(mn) O(mn),其中 m m m 是字符串数组中的字符串的平均长度, n n n是字符串的数量。最坏情况下,字符串数组中的每个字符串的每个字符都会被比较一次。
- 空间复杂度: O ( 1 ) O(1) O(1)。使用的额外空间复杂度为常数。
方法二:横向扫描
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
if not strs:
return ""
prefix, count = strs[0], len(strs)
for i in range(1, count):
prefix = self.lcp(prefix, strs[i])
if not prefix:
break
return prefix
def lcp(self, str1, str2):
length, index = min(len(str1), len(str2)), 0
while index < length and str1[index] == str2[index]:
index += 1
return str1[:index]
复杂度分析
- 时间复杂度: O ( m n ) O(mn) O(mn),其中 m m m 是字符串数组中的字符串的平均长度, n n n 是字符串的数量。最坏情况下,字符串数组中的每个字符串的每个字符都会被比较一次。
- 空间复杂度: O ( 1 ) O(1) O(1)。使用的额外空间复杂度为常数。
方法三:分治(递归)
注意到
LCP
\textit{LCP}
LCP 的计算满足结合律,有以下结论:
L
C
P
(
S
1
…
S
n
)
=
L
C
P
(
L
C
P
(
S
1
…
S
k
)
,
L
C
P
(
S
k
+
1
…
S
n
)
)
L C P\left(S_{1} \ldots S_{n}\right)=L C P\left(L C P\left(S_{1} \ldots S_{k}\right), L C P\left(S_{k+1} \ldots S_{n}\right)\right)
LCP(S1…Sn)=LCP(LCP(S1…Sk),LCP(Sk+1…Sn))
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
def lcp(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]
return lcpLeft[:minLength]
return "" if not strs else lcp(0, len(strs) - 1)
复杂度分析
- 时间复杂度: O ( m n ) O(mn) O(mn),其中 m m m 是字符串数组中的字符串的平均长度, n n n 是字符串的数量。时间复杂度的递推式是 T ( n ) = 2 ⋅ T ( n 2 ) + O ( m ) T(n)=2 \cdot T(\frac{n}{2})+O(m) T(n)=2⋅T(2n)+O(m),通过计算可得 T ( n ) = O ( m n ) T(n)=O(mn) T(n)=O(mn)。
- 空间复杂度: O ( m log n ) O(m \log n) O(mlogn),其中 m m m 是字符串数组中的字符串的平均长度, n n n 是字符串的数量。空间复杂度主要取决于递归调用的层数,层数最大为 log n \log n logn,每层需要 m m m 的空间存储返回结果。
方法四:二分查找(可能是一种较快方法)
显然,最长公共前缀的长度不会超过字符串数组中的最短字符串的长度。用
minLength
\textit{minLength}
minLength 表示字符串数组中的最短字符串的长度,则可以在
[
0
,
minLength
]
[0,\textit{minLength}]
[0,minLength] 的范围内通过二分查找得到最长公共前缀的长度。每次取查找范围的中间值
mid
\textit{mid}
mid,判断每个字符串的长度为
mid
\textit{mid}
mid 的前缀是否相同,如果相同则最长公共前缀的长度一定大于或等于
mid
\textit{mid}
mid,如果不相同则最长公共前缀的长度一定小于
mid
\textit{mid}
mid,通过上述方式将查找范围缩小一半,直到得到最长公共前缀的长度。
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
def isCommon(length):
str0, count = strs[0][:length], len(strs)
return all(strs[i][:length] == str0 for i in range(1, count))
if not strs:
return ''
minLength = min([len(x) for x in strs])
left = 0
right = minLength
while(left < right):
mid = (left + right + 1) // 2
if isCommon(mid):
left = mid
else:
right = mid - 1
return strs[0][:left]
复杂度分析
- 时间复杂度: O ( m n log m ) O(mn \log m) O(mnlogm),其中 m m m 是字符串数组中的字符串的最小长度, n n n 是字符串的数量。二分查找的迭代执行次数是 O ( log m ) O(\log m) O(logm),每次迭代最多需要比较 m n mn mn 个字符,因此总时间复杂度是 O ( m n log m ) O(mn \log m) O(mnlogm)。
- 空间复杂度: O ( 1 ) O(1) O(1)。使用的额外空间复杂度为常数。