题目描述:
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 “”。
示例 1:
输入: ["flower","flow","flight"]
输出: "fl"
示例 2:
输入: ["dog","racecar","car"]
输出: ""
解释: 输入不存在公共前缀。
说明:
所有输入只包含小写字母 a-z 。
思路:
本题虽然只是“容易”等级的题目,但是可以有至少五种解法,而且这是一个在搜索领域中很常见的问题,所以还是值得一看。
- 暴力解法:
先找到两个字符串的公共前缀,再将前缀与第三个字符串比较…,这种方法最坏情况下的时间复杂度为 O ( S ) O(S) O(S),其中 S 代表所有字符串的总长度。
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
if len(strs) == 0:
return ''
s = strs[0]
for t in strs[1:]:
long_common_prefix = ''
for p, q in zip(s, t):
if p == q:
long_common_prefix += p
else:
break
s = long_common_prefix
# 出现空串时提前结束
if s == '':
return ''
return s
- 暴力算法的实现上可以稍微转变思路,传统的寻找共同子串是一个“加法”操作,每找到一个相同字符就就添加进变量 l o n g _ c o m m o n _ p r e f i x long\_common\_prefix long_common_prefix 。实际上也可以采用“减法”操作,在 t 串中寻找 s 串,假如没找到或者不是在开头,就缩短 s 串。这样实际上并没有减少时间复杂度,只是写法上更简单。
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
if len(strs) == 0:
return ''
long_common_prefix = strs[0]
for s in strs[1:]:
while s.find(long_common_prefix) != 0:
long_common_prefix = long_common_prefix[:-1]
if long_common_prefix == '':
return ''
return long_common_prefix
- 纵向扫描:可以想象把所有字符串堆叠起来,依次比较所有字符串的第一个字符、第二个字符、第三个字符…,直到遇到某个位置的字符不全相等。同样需要比较所有字符,所以时间复杂度不变
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
if len(strs) == 0:
return ''
for i in range(len(strs[0])):
for s in strs[1:]:
if i == len(s) or s[i] != strs[0][i]:
return s[:i]
return strs[0]
-
分治法:对于方法2,可以采用分治思想,将数组从中间一分为二,分别求两边子数组的最长公共前缀。而每一个子数组也可以一分为二…
分治法实际上也没有减少比较的次数,因此时间复杂度不变。同时因为采用了递归,所以空间复杂度为 O ( m ⋅ l o g ( n ) ) O(m\cdot log(n)) O(m⋅log(n)), n为字符串个数,m为字符串长度
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
if len(strs) == 0:
return ''
return self.split_list(strs, 0, len(strs)-1)
def split_list(self, strs, start, end):
if start == end:
return strs[start]
else:
middle = (end + start) // 2
left_str = self.split_list(strs, start, middle)
right_str = self.split_list(strs, middle + 1, end)
return self.get_common_prefix(left_str, right_str)
def get_common_prefix(self, s, t):
min_len = min(len(s), len(t))
for i in range(min_len):
if s[i] != t[i]:
return s[:i]
return s[:min_len]
- 二分查找法:
这里借用官方题解的图进行说明。首先找到所有字符串最短的长度,因为这是最终答案的最大可能长度。根据这个最短字符串S进行二分查找,那么查找区间为 ( 0 , m i n L e n ) (0, minLen) (0,minLen)。每次区间一分为二,丢弃不可能包含正确答案的那一半。具体来说二分查找中会出现两种情况:
① S[0, mid]是其余所有字符串的前缀,那么对于S[0, i] (i < mid)来说,它都是所有串的公共前缀。由于想要找的是最长的公共前缀,所以可以把前半个区间丢弃
② S[0, mid]不是其余所有字符串的前缀,那么对于S[0, i] (i > mid)来说,它更不可能是所有串的公共前缀,所以可以丢弃后半个区间
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
if len(strs) == 0:
return ''
def is_common_prefix(prefix, str_list):
for s in str_list:
if s[:len(prefix)] != prefix:
return False
return True
min_str = min(strs, key=len)
start, end = 0, len(min_str)
while start < end:
mid = (end + start + 1) // 2
if is_common_prefix(min_str[:mid], strs):
start = mid
else:
end = mid - 1
return min_str[:(end + start) // 2]
- 一些使用 Python 的奇淫技巧(来自于官方讨论区中 @xshura 用户的回答):
① Python 中字符串是可以进行比较的,原生的 min()和 max()函数甚至 sort()函数会根据单个字符的 ascii码排序,例abb, aba,abac,最大为abb,最小为aba。所以只需要比较最大最小的公共前缀就是整个数组的公共前缀
def longestCommonPrefix(self, strs):
if not strs: return ""
s1 = min(strs)
s2 = max(strs)
for i,x in enumerate(s1):
if x != s2[i]:
return s2[:i]
return s1
②. 方法三如果使用python,可以有更加简单的写法
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
s = ""
"""
>>> a = [[1,2,3], [4,5,6]]
>>> zip(*a)
>>> [[1,4], [2,5], [3,6]]
"""
for i in zip(*strs):
if len(set(i)) == 1:
s += i[0]
else:
break
return s