按照誓言,已经修改过了,可以放心食用
前引:我打算偶尔也要画个图来讲解我的方法,这样应该可以更好地讲解,但是明天就是端午了,我打算今天这个讲解就偷懒一下,比较敷衍,我感觉我没讲清楚,所以我发誓,端午过后必改,贯彻我的口碑,这次就算了,别跟自己过不去,委屈你了,看我的帖子,晚安
解题思路:
1.获取信息:
(1)查找多个字符串的最长公共前缀,即:这些字符串前几个字符相同,返回它们即可
2.分析问题:(注意,这里的分析思路,我是选取一个字符串作为初始公共前缀,用它来与其他字符串进行比较,求出公共前缀,后续方法,有些不是这个思路,我会说明的,这是我初次看这道题的思路,后面想出来了其他思中)
看到这个问题,我立马想到的是,字符串之间肯定要进行比较,才能知道它是否有公共前缀,那么就会有两种情况
注:size1是一个字符串的长度,size2是另一个字符串的长度
(1)size1==size2
直接依次比较两个字符串相对的那一位即可,相等,就继续,不相等,就返回前面相等的几个字符组成的字符串
(2)size1<size2 || size1>size2
不用慌张,因为是求最长公共前缀,那肯定是迁就较短的字符串,那么就会有两种情况
1.你初始选取的那个字符串比较短,那你可以照常比较,没啥大差别,一番风顺就进行到最后,不一帆风顺,在比较到末尾之前就返回了
2.你初始选取的那个字符串比较长,那你也可以在那个较短的字符串取到末尾下一位时,两者比较相对的位置,肯定是不相同的,所以它自己会返回
3.示例查验:
示例2:提醒了我们当不存在公共前缀时,返回 ""
4.尝试编写代码
1.横向扫描:
(改)思路:我们取字符串数组的第一个字符串作为前缀,再用它与剩下的字符串进行比较,来对前缀进行微调,顺便可以再比较中验证前缀的正确性,比较完最后的一个字符串后,前缀就是最长公共前缀,我给出鲜明的例子,这次配上图
我要生动地形容这个方法的话就是,一颗没有修剪过枝叶的树,经过多次修剪,就会成为美丽的树
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
int size=strs.size();//求出有多少个字符串
if(size==1)return strs[0];//如果只有一个字符串,直接返回
string prefix=strs[0];//选取字符串数组中的第一个字符串为初始前缀
for(int i=0;i<size;i++){//遍历字符串数组中的每一个字符串
if(strs[i]=="")return "";//如果某个字符串为空,直接返回空串
int size1=strs[i].size();//要比较的那个字符串的长度
int size2=prefix.size();//前缀的长度(是指的我形容的那个还没有被修剪完枝叶的树)
int m=0;//比较的位置的下标
for(int j=0;j<size1;j++){//逐位比较
if(prefix[m]!=strs[i][j]){//某个位置不等于的话
if(m==0&&j==0)return "";//如果一开始就不等于,直接返回空串
prefix=strs[i].substr(0,j);//更新前缀,取前面相同的字符组成字符串
break;//跳出循环
}
if(size2>size1&&j==size1-1){//如果前缀(是指的我形容的那个还没有被修剪完枝叶的树)遇到比它小的字符串
prefix=strs[i];//直接更新前缀为更小的那个字符串
}
m++;//更新比较位置的下标
}
}
return prefix;
}
};
(2)纵向扫描
(改)思路:之前我们是修剪树苗,是逐步减少,那么换一种思路,可不可以逐步增多呢?
当然可以,我们每次比较字符串数组中所有字符串的同一位置,会有两种情况:
1.都相同,那么就加上这个字符,作为前缀
2.不是都相同,那么直接返回之前积累的相同的字符组成的前缀
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
string prefix;//初始的前缀为空(这下你该懂得,为什么上一种解法,我要叫修剪树木了吧,这次是积少成多哦)
int i=0;//比较位置的下标
while(1){
if(strs[0]=="")return "";//如果字符串数组中某个字符串为空串,直接返回空串
if(strs[0].size()<=i)return prefix;//如果某个字符串根本没有i下标的位置(就是到头了,不够长),那直接返回前缀
char s=strs[0][i];//取得字符串需要比较的那个位置上的字符
for(auto& str:strs){//依次比较
if(str[i]!=s){//如果不全等于
return prefix;//返回下标
}
}
prefix+=s;//如果全等于,就加上
i++;//继续比较下一个位置
}
return prefix;
}
};
(3)分治法
分治法:把一个大问题拆分为多个小问题
(改)思路:字符串数组,把它划分为二,获得的两个字符串数组,再次划分为二,继续进行上述的划分,直到划分出来的字符串数组里面只有两个字符串,再比较两个字符串,求出它们的前缀,再合各个组的前缀进行比较,最后所得前缀就是答案
代码如下:
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
return GetRes(strs,0,strs.size()-1);//我用的是递归哦
}
private:
string GetRes(vector<string>& strs,int begin,int end){
if(end-begin<=1)return Find(strs[begin],strs[end]);//如果划分的某个字符串数组里面只有两个字符串或者一个,直接比较,然后返回即可
int mid=(begin+end)/2;//求出中点
string left=GetRes(strs,begin,mid);//求出中点左边的前缀
string right=GetRes(strs,mid+1,end);//求出中点右边的前缀
if(left==right)return left;//如果两者相等,直接返回
return Find(left,right);//如果不相等,比较之后,再返回
}
string Find(string& m,string& n){//输入的是两个字符串
string res;//前缀
int size1=m.size(),i=0;
int size2=n.size(),j=0;
while(i<size1&&j<size2){//依次比较它们相对位置上的字符
if(m[i]!=n[j])break;//不同直接返回
res+=m[i];//相同就加上该字符,继续比较下一位置上的字符
i++;
j++;
}
return res;//返回前缀
}
};
(改)(4)二分法(本质上也是,纵向扫描法的优化版本把)(我写到这里太疲惫了,我就说一下思路,等端午节过后,我再补上,也不失为一件美事)
(改)思路:我们知道,纵向扫描法,是比较字符串数组中所有字符串相同位置上的字符,如果相同,就加上作为前缀,如不同,就直接返回前面相同的字符构成的前缀
那么,我们可以在比较的位置上开始下手优化,让它不用比较那么多位置,不久可以优化了吗?
初始比较位置是 i = 0,最后比较的位置自然是某个字符串s它的末尾 j = s.size()了
每次求出mid = i + (j - i) / 2 = (i + j) / 2
比较下标为mid的那个位置,那么就会有两种情况
1.相同,那么最长公共前缀的长度一定大于等于 mid,那么就取,i = mid + 1到 j 这个区间,再次求它们的中点mid来比较这个位置上的字符即可
2.不相同,那么最长公共前缀的长度一定小于等于 mid,那么就取,i 到 j = mid 这个区间,再次求它们的中点mid来比较这个位置上的字符即可
结束条件:在进行多次比较之后,最后的初始比较位置 i 合最后比较位置 j 肯定是相互趋近,这时,结果也大差不差了
以下是代码
class Solution {
public:
string longestCommonPrefix(vector<string>& strs) {
string prefix=strs[0];//取字符串数组中的第一个字符串为初始前缀
for(string& str:strs){//取字符串数组中最短的字符串为初始前缀
if(str==""||prefix[0]!=str[0])return "";
if(prefix.size()>str.size())prefix=str;
}
int begin=0,end=prefix.size()-1;//初始比较位置和最终比较位置
label://标签
while(begin<end){//当begin==end时就推出循环
int mid=(begin+end)/2;//取比较的中点
for(string& str:strs){//取字符串数组中的字符串
if(prefix[mid]!=str[mid]){//如果比较的那个位置,前缀和某个字符串不一样
end=mid;//取左区间继续进行比较
goto label;//前往标签处
}
}
begin=mid+1;//一帆风顺,就取右区间继续进行比较
}
if(begin==end&&end==prefix.size()-1){//这一步大有看头,我会在代码栏外面进行讲解
for(string& str:strs){
if(str[end]!=prefix[end])return prefix.substr(0,end);
}
return prefix;
}
return prefix.substr(0,end);
}
};
我最后一步验证大有看头,是为了专门处理一帆风顺的情况,就是字符串数组中的每个字符串都相同这种特殊的情况,即,可以一帆风顺走到末尾
其他的情况,或多或少都会前往标签处几次,最后
return prefix.substr(0,end);
就会返回合适的长度,但我说的那个特殊情况会少一位,所以我专门处理了一下这个特殊情况
如果你可以处理我这个不足之处,恳求你私信给我,谢谢老师
大概思路就是这个,我这次不写,不是因为我比较懒,想等端午后再补出来,而是我想考考你,磨练一下你,你可以在端午过后来这个帖子对答案嘛
虽然要端午了,但还是要提一嘴,纸上得来终觉浅,绝知此事要躬行