解法一:行扫描法
算法一次遍历字符串{S1, ... Sn},当遍历到第i个字符串的时候,找到最长公共前缀LCP(S1, ... , Si)。当LCP(S1, ... , Si)是一个空串的时候,算法就结束了。否则,在执行了n次遍历之后,算法就会返回最终答案LCP(S1, ... , Sn)。
复杂度分析
-
时间复杂度:O(S),S 是所有字符串中字符数量的总和。
最坏的情况下,n 个字符串都是相同的。 算法会将 S1 与其他字符串 [S2, ... , Sn] 都做一次比较。这样就会进行 S 次字符比较,其中 S 是输入数据中所有字符数量。
-
空间复杂度:O(1), 我们只需要使用常数级别的额外空间。
JAVA
public String longestCommonPrefix(String[] strs) {
if (strs.length == 0) return "";
String prefix = strs[0];
for (int i = 1; i < strs.length; i++)
while (strs[i].indexOf(prefix) != 0) { // 返回指定字符在字符串中第一次出现处的索引,返回为0说明此时prefix与strs[i]有公共前缀
prefix = prefix.substring(0, prefix.length() - 1);
if (prefix.isEmpty()) return "";
}
return prefix;
}
解法二:列扫描法-进阶
复杂度分析
-
时间复杂度:O(S),S 是所有字符串中字符数量的总和。
最坏情况下,输入数据为 n 个长度为 m 的相同字符串,算法会进行 S = m*n 次比较。可以看到最坏情况下,本算法的效率与算法一相同,但是最好的情况下,算法只需要进行 n*minLen 次比较,其中 minLen 是数组中最短字符串的长度。
-
空间复杂度:O(1), 我们只需要使用常数级别的额外空间。
JAVA
public String longestCommonPrefix(String[] strs) {
if (strs == null || strs.length == 0) return "";
for (int i = 0; i < strs[0].length() ; i++){
char c = strs[0].charAt(i); // charAt() 方法用于返回指定索引处的字符。索引范围为从 0 到 length() - 1
for (int j = 1; j < strs.length; j ++) {
if (i == strs[j].length() || strs[j].charAt(i) != c)
return strs[0].substring(0, i);
}
}
return strs[0];
}
解法三:分治法
算法的思路来自于LCP操作的结合律。LCP(S1,...,Sn)=LCP( LCP(S1,...,Sk), LCP(Sk+1,...,Sn) )
public String longestCommonPrefix(String[] strs) {
if (strs == null || strs.length == 0) return "";
return longestCommonPrefix(strs, 0 , strs.length - 1);
}
private String longestCommonPrefix(String[] strs, int l, int r) {
if (l == r) {
return strs[l];
}
else {
int mid = (l + r)/2;
String lcpLeft = longestCommonPrefix(strs, l , mid);
String lcpRight = longestCommonPrefix(strs, mid + 1,r);
return commonPrefix(lcpLeft, lcpRight);
}
}
String commonPrefix(String left,String right) {
int min = Math.min(left.length(), right.length());
for (int i = 0; i < min; i++) {
if ( left.charAt(i) != right.charAt(i) )
return left.substring(0, i);
}
return left.substring(0, min);
}
复杂度分析
最坏情况下,我们有 n 个长度为 m 的相同字符串。
-
时间复杂度:O(S),S 是所有字符串中字符数量的总和,S=m*n。
时间复杂度的递推式为 T(n)=2⋅T(2n)+O(m), 化简后可知其就是 O(S)。最好情况下,算法会进行 minLen⋅n 次比较,其中 minLen 是数组中最短字符串的长度。
-
空间复杂度:O(m⋅log(n))
内存开支主要是递归过程中使用的栈空间所消耗的。 一共会进行 log(n) 次递归,每次需要 m 的空间存储返回结果,所以空间复杂度为 O(m⋅log(n))。
解法四:二分查找法
这个想法是应用二分查找法找到所有字符串的公共前缀的最大长度 L
。 算法的查找区间是 (0…minLen),其中 minLen
是输入数据中最短的字符串的长度,同时也是答案的最长可能长度。 每一次将查找区间一分为二,然后丢弃一定不包含最终答案的那一个。算法进行的过程中一共会出现两种可能情况:
-
S[1...mid]
不是所有串的公共前缀。 这表明对于所有的j > i S[1..j]
也不是公共前缀,于是我们就可以丢弃后半个查找区间。 -
S[1...mid]
是所有串的公共前缀。 这表示对于所有的i < j S[1..i]
都是可行的公共前缀,因为我们要找最长的公共前缀,所以我们可以把前半个查找区间丢弃。
public String longestCommonPrefix(String[] strs) {
if (strs == null || strs.length == 0)
return "";
int minLen = Integer.MAX_VALUE;
for (String str : strs)
minLen = Math.min(minLen, str.length());
int low = 1;
int high = minLen;
while (low <= high) {
int middle = (low + high) / 2;
if (isCommonPrefix(strs, middle))
low = middle + 1;
else
high = middle - 1;
}
return strs[0].substring(0, (low + high) / 2);
}
private boolean isCommonPrefix(String[] strs, int len){
String str1 = strs[0].substring(0,len);
for (int i = 1; i < strs.length; i++)
if (!strs[i].startsWith(str1))
return false;
return true;
}
复杂度分析
最坏情况下,我们有 n 个长度为 m 的相同字符串。
-
时间复杂度:O(S⋅log(n)),其中 S 所有字符串中字符数量的总和。
算法一共会进行 log(n) 次迭代,每次一都会进行 S=m∗n 次比较,所以总时间复杂度为 O(S⋅log(n))。
-
空间复杂度:O(1),我们只需要使用常数级别的额外空间。