题目描述
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 “”。
示例 1:
输入:strs = [“flower”,“flow”,“flight”]
输出:“fl”
示例 2:
输入:strs = [“dog”,“racecar”,“car”]
输出:""
解释:输入不存在公共前缀。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-common-prefix
纵向扫描
这个最长公共子串的问题比较简单,因为输入比较有规则,所以很好判断
1.首先找到所有字符串中长度最短的,因为最长公共子串的长度一定不可能超过他
2.在从每一个位置检查,所有字符串的符号是否是相同的,若是相同的,那么加入ret中,若不是相同则直接退出,返回结果
string longestCommonPrefix(vector<string>& strs) {
string ret = "";
int m = strs.size();
int min_len = INT_MAX;
//记录字符串中最短的那个的长度
for(int i = 0; i < m; i++) {
if(strs[i].size() < min_len) {
min_len = strs[i].size();
}
}
//这样遍历的时候便没有了越界问题
for(int i = 0; i < min_len; i++) {
bool flag = true;
for(int j = 1; j < m; j++) {
if(strs[j - 1][i] != strs[j][i]) {
flag = false;
break;
}
}
if(flag) {
ret.push_back(strs[0][i]);
} else {
break;
}
}
return ret;
}
横向扫描(S1,S2,S3,S4的最长公共子串)
1.首先,找到S1和S2的最长公共子串str。
2.在一个一个的拿str与后面的S3和S4,确定,str和后面的最长公共子串,之后,不断确定str
3.当str在过程中为空时,那么str最后一定为空
string longestCommonPrefix(vector<string>& strs) {
int m = strs.size();
string ret = "";
//如果只有一个字符串的话,那么直接返回第一个字符串即可
if(m == 1) {
return strs[0];
}
if(m == 0) {
return ret;
}
int len = min(strs[0].size(), strs[1].size());
int retlen;
//在ret后面继续添加相等的字符
for(int i = 0; i < len; i++) {
if(strs[0][i] == strs[1][i]) {
ret.push_back(strs[0][i]);
} else {
break;
}
}
//这里第一次确定了ret的最长长度,以后的遍历中长度会更短
//没比较一次,retlen就更新一次
retlen = ret.size();
for(int i = 2; i < m; i++) {
len = min(retlen, (int)strs[i].size());
retlen = len;//这个若是下面的for循环不进行更新,那么retlen的长度就是这两个中最短的
for(int j = 0; j < len; j++) {
if(ret[j] != strs[i][j]) {
retlen = j;
break;
}
}
}
//从哪个位置开始截取,截取多少位
return ret.substr(0, retlen);
}
string longestCommonPrefix(vector<string>& strs) {
int m = strs.size();
if(m == 0) {
return "";
}
string ret = strs[0];
int retlen = ret.size();
for(int i = 1; i < m; i++) {
int len = min((int)strs[i].size(), retlen);
retlen = len;
for(int j = 0; j < len; j++) {
if(strs[i][j] != ret[j]) {
retlen = j;
break;
}
}
if(retlen == 0) {
break;
}
}
return ret.substr(0, retlen);
}
横向扫描官方题解给出的更好
1.首先将第一个字符串取出来,第一个字符串如果正好是最长子串或第一个字符串不是最长子串,那么都是可以的。
2.if (i==strs[j].size() || c != strs[j][i]) //如果当前子串已经遍历完成了,或者字符不相等,那么立即返回字符串
分治(这里是分而治之,将原问题拆成多个小问题,之后小问题的解在汇总成大问题的解)
分治法需要满足总问题的解,可以拆成类似的子问题,而子问题的解就是总问题的解
//这个函数的作用是求strs[begin]位置到strs[end]位置的最长子串,包含begin和end
string longestPrefixStr(string str1, string str2) {
int len = min(str1.size(), str2.size());
for(int i = 0; i < len; i++) {
if(str1[i] != str2[i]) {
return str1.substr(0, i);
}
}
//这里返回的字符串,因为str1和str2的公共前缀,那么自然取谁的都可以
return str1.substr(0, len);
}
//递归的空间复杂度和层数有关,这里递归的层数为logn
//每层的复杂度为O(m)
string longestCommonPrefix(vector<string>& strs, int begin, int end) {
//这个是递归结束的条件
if(begin == end) { //此时begin==end,也就是说只有一个字符串的时候
return strs[begin];
} else if(begin + 1 == end) { //当有两个字符串的时候,那么求取公共,这条if判断可以去掉,这样的话,都让最后一个处理
return longestPrefixStr(strs[begin], strs[end]);
}
//如果第二个if去掉的话,也是可以的,那么长度为1的字母就由下面处理了,这样长度为1的话,也需要执行一次longestCommonPrefix
//但是这样的话,只有一个出口,代码上会好一些,我感觉都可以
//否则会继续递归
int mid = (begin + end) / 2;
//[begin, mid]和[min+1, end]再次找到最长子串
return longestPrefixStr(longestCommonPrefix(strs, begin, mid), longestCommonPrefix(strs, mid + 1, end));
}
string longestCommonPrefix(vector<string>& strs) {
int m = strs.size();
if(m == 0) {
return "";
} else {
//该函数长度只要大于等于1,都可以处理
return longestCommonPrefix(strs, 0, m - 1);
}
}
二分查找法:
最长的公共前缀子串的话,需要那么一定超不过所有字符串中最短的那个字符串
找到最短的字符串的长度len,那么我们可以取(0~len)的中间值mid,这里mid前面的话,进行比较,如果相等的话,那么一定大于mid
如果不相等的话,一定小于mid,所以根据这个来进行下一次判断的位置,每次都需要比较mid到第一个位置
bool longestPrefixEquals(vector<string>& strs, int len) {
int m = strs.size();
for(int i = 0; i <= len; i++) {
for(int j = 1; j < m; j++) {
if(strs[j][i] != strs[0][i]) {
return false;
}
}
}
return true;
}
string longestCommonPrefix(vector<string>& strs) {
int m = strs.size();
int minlen = INT_MAX;
//找到最短的字符串,并且记录长度
for(int i = 0; i < m; i++) {
if(strs[i].size() < minlen) {
minlen = strs[i].size();
}
}
//这里标记二分查找的范围也就是最短字符串的长度
//这里包含low和high两个位置
int low = 0, high = minlen - 1;
//这个high是最后一个位置,还是一个不存在的位置和low<=high还有low<high有关系
//还需要配置low = mid + 1
while(low <= high) {
int mid = (low + high) / 2; //这里要比较包含mid之前的所有位置,从0,mid
//[0, mid]相等的话,那么再检查mid+1之后的位置就可以
//如果相等的话,那么直接返回0到low位置的子串
//这个关键是下面的语句,若是相等的话,那么low = mid + 1,这里因为是计算mid这个位置,所以此处即是
//保存了一个相等的字符的数量,又是下一次遍历的起点。
//所以这里是一句代码两个作用。
//这里是有相等的话,可会改变low的值,当一次也不相等的时候,那么直接返回0的数量
//如果在之前有一次相等的话,那么之后都不成功的话,那么直接返回最后一次相等的值。
//因为这个相等的话,他是越来越精细的,所以,最后返回的是准确的长度
//一次一次的找到更准确的位置,最后每一个位置都可以遍历到,这样就能找到解了。
//还能记录正确的解
//而且每次都加1和减1,所以不会死循环
if(longestPrefixEquals(strs, mid)) {
low = mid + 1;
} else {//否则,那么至少0到mid位置,没有相等的了,所以在此检查mid-1位置之前即可
high = mid - 1;
}
}
return strs[0].substr(0, low);
}
int main() {
vector<string> str = { "cir", "car", "cdr" };
cout << longestCommonPrefix(str) << endl;
return 0;
}