思路:LCP(S1, S2, …, Sn)=LCP(LCP(S1, S2)…, Sn),即S1~Sn的最长公共前缀可以转化为S1和S2的最长公共前缀,再求与S3的公共前缀等
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
if(strs.size()==0 || strs[0].length()==0)
return "";
else{
int minn=strs[0].length();
for(int i=1; i<strs.size(); i++){
if(strs[i].length()==0){
minn=0;
break;
}
for(int j=0; j<minn; j++){
if(strs[i][j]!=strs[i-1][j]){
minn=j;
break;
}
}
}
return strs[0].substr(0, minn);
}
}
};
时间复杂度:O(S),S是所有字符串的字符数量总和。最坏情况有n个相同的字符串,就需要从头到尾遍历到所有字符
空间复杂度:O(1),需要常数级别的额外空间
思路:将字符串对齐,比较每个字符串的第一个字符,如果相同则继续向后比较
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
string str;
int flag=0;
if(strs.size()==0 || strs[0].length()==0)
str="";
else{
str=strs[0];
for(int i=0; i<strs[0].length() && !flag; i++){
char ch=strs[0][i];
for(int j=1; j<strs.size() && !flag; j++){
if(i==strs[j].length() || strs[j][i]!=ch){
str=strs[0].substr(0, i);
flag=1;
}
}
}
}
return str;
}
};
时间复杂度:O(S),S是所有字符串的字符数量总和。假设有n个字符串,最小的长度是minlen,则最好情况是nminlen;当所有的字符串长度相同均为m时,有最坏情况,会进行nm次比较
空间复杂度:O(1),需要常数级别的额外空间
思路:用递归模拟一个二叉树的结构,每个字符串为最底层的叶子结点,每个父结点代表的是孩子节点的最长公共前缀,则最后递归回溯求得的根结点的值即为所有字符串的最长公共前缀
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
string str;
if(strs.size()==0){
str="";
}
else{
str=longestCommonPrefix(strs, 0, strs.size()-1);
}
return str;
}
string longestCommonPrefix(vector<string>& strs, int left, int right){
if(left==right)
return strs[left];
else{
int mid=(left+right)/2;
string str1 = longestCommonPrefix(strs, left, mid);
string str2 = longestCommonPrefix(strs, mid+1, right);
return CommonPrefix(str1, str2);
}
}
string CommonPrefix(string str1, string str2){
int minn=min(str1.length(), str2.length());
int i;
for(i=0; i<minn; i++){
if(str1[i]!=str2[i])
break;
}
return str1.substr(0, i);
}
};
时间复杂度:O(S),S是所有字符串字符总和。最好最坏情况下的比较次数同上
空间复杂度:O(mlogn),主要是递归过程中使用到的栈空间。假设有n个长度为m的相同字符串,每次需要m的空间返回字符串,比较次数为logn,那么就需要mlogn的额外空间
思路:先找到最短字符串的长度,用两个指针一个指向字符串开头,一个指向最短字符串长度的位置,mid指针指向两指针中间。每次比较开头到mid位置的字符串是不是最长公共前缀,如果是的话,则保留前半部分,将low指向mid+1;如果不是的话,说明后半部分一定不满足最长公共前缀,就将high指针指向mid-1
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
int minn=INT_MAX, low, high;
string str;
if(strs.size()==0)
str="";
else{
for(int i=0; i<strs.size(); i++)
minn=minn>strs[i].length()?strs[i].length():minn;
low=0;
high=minn;
while(low<=high){ //二分根据不同的情景有很多细节的不同,因此写二分一定要想好再写呀
int mid=(low+high)/2;
if(isChecked(strs, mid))
low=mid+1;
else
high=mid-1;
}
str=strs[0].substr(0, (low+high)/2);
}
return str;
}
bool isChecked(vector<string>& strs, int mid){
string str=strs[0].substr(0, mid);
for(int i=1; i<strs.size(); i++){
if(strs[i].substr(0, mid)!=str)
return false;
}
return true;
}
};
时间复杂度:O(Slog(n)),S为所有字符串字符总和。(1/2)^n=1,则二分的时间复杂度是O(logn),假设有n个长度为m的相同字符串,最多的比较次数S=nm,因此总的时间复杂度是O(S*log(n))
空间复杂度:O(1),需要常数级别的额外空间
等我学了字典树来填坑~
运行时间:水平扫描 = 分治 < 垂直扫描 = 二分
内存消耗:水平扫描 < 垂直扫描 < 二分 < 分治 (其实前三个差不多)
反思:前两种解法是普遍的解法,后三种其实写起来是容易出错的,为了开阔眼界才实现的。综合来看,水平扫描既是最好想出来的,也是表现相对较好的。
分治用到了递归,是一种空间换时间的解法;二分用了迭代,是用时间换空间的解法
还有还有,string库函数的调用真的很耗性能