二. 字符串_公共前缀_14. 最长公共前缀

这篇博客探讨了如何在给定的字符串数组中找到最长公共前缀。通过两种不同的方法,横向扫描和纵向扫描,实现了找到共同部分的算法。其中,横向扫描的方法包括了基本的字符串比较和分治策略,而纵向扫描则是从最短字符串开始逐步检查每个字符。这些方法都确保了在最坏情况下的效率,并能正确找出字符串数组的最长公共前缀。
摘要由CSDN通过智能技术生成

题目描述
编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 “”。

示例 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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值