14. 最长公共前缀

思路:
这个题目需要明确的是字符串最长公共前缀,所以如果存在这么的前缀,数组的每个字符串都有这样的前缀,有一个没有这样的前缀就返回空。所以难点是找存在这样的前缀,但要如何找出他最大的长度。
这里我是看到评论用python的特性,他能按照字符串的ASCII码进行排序,所以自己用C++实现了一下。
C++用sort进行排序,排序的标准是 比较两个字符串,找到第一个不同的字母,比较这两字母的ascii大小,并顺序排序。
然后我们取排序后的数组的第一个字符串和最后一个字符串,一直比较他们的字母是否相同,相同就加入res字符串中,遇到第一个不同的字母就break出循环,然后直接返回res字符串就可。
为什么取第一个和最后一个呢?因为我们前面说了,必须所有的字符串都有那么的前缀,才存在这样的前缀,所以如果有一个字符串不满足,那他就会第一个字母就不相同,他要么排第一个要么排在最后,所以我们就能返回空字符串。如果有这样的前缀,我们需要找到它的长度,那么按照这样的排序,不会让拥有公共前缀的字符串放在第一个字符串和最后一个字符串之间,因此我们就能找到最长的公共前缀。
例如我们的方法可以让abb,a,abz,会排成a,abb,abz

代码:

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        int n=strs.size();
        sort(strs.begin(),strs.end(),[](const string& a,const string& b){
            int al=a.length(),bl=b.length();
            int n = min(al,bl);
            int i=0;
            for(;i<n;++i){
                if(a[i]!=b[i]) break;
            }
            return (a[i]-'a')<(b[i]-'a');
        });
        int l0=strs[0].length(),l1=strs[n-1].length();
        int l = min(l0,l1);
        string res = "";
        for(int i=0;i<l;++i){
            if(strs[0][i]==strs[n-1][i]) res += strs[0][i];
            else break;
        }
        return res;
    }
};

上面这个方法我觉得很妙,
下面这个是比较好想到的,理解了题目“只要存在公共前缀,所有字符串都会有这个前缀”这个条件后就能想到。
首先就是拿出两个字符串来比较,找到共同的部分,然后用这个共同的部分再去和其他的字符串进行对比,对比完就能得到公共前缀了
代码:

class Solution {
public:
    string compare(string str1,string str2){
        int l1=str1.length(),l2=str2.length();
        int l = min(l1,l2);
        string cur ="";
        for(int i=0;i<l;++i){
            if(str1[i]!=str2[i]) return cur;
            cur+=str1[i];
        }
        return cur;
    }
    string longestCommonPrefix(vector<string>& strs) {
        int n = strs.size();
        string res = strs[0];
        for(int i=1;i<n;++i){
            res = compare(res,strs[i]);
        }
        return res;
    }
};

另一种相似的,是同时比较所有的字符串的同一个位置的字母是否相同,只要遇到一个不相同字母那么这前面的子串就是公共前缀了
代码:

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        int n = strs.size();
        string res = "";
        int len = strs[0].length();
        for(int i=0;i<len;++i){ //第一重for用来遍历字符串的每个字母位置
            char temp = strs[0][i];
            for(int j=0;j<n;++j){ //第二重用来遍历每个字符串
                if(i==strs[j].size()||temp!=strs[j][i]) //只要有一个字符串的长度不够了,或遇到不相同的字母了就直接返回res
                    return res;
            }
            res+=temp;
        }
        return res;
    }
};

另外就是分治的方法,
通过将strs里的字符串不断细分成两份,直到只剩下最后两份进行对比,获得这两个字符串的公共前缀,然后这公共前缀再和另外两个的公共前缀比较找到公共前缀。
(用来练习分治了)
代码:

class Solution {
public:
    string commonprefix(string left,string right){
        int minlen = min(left.length(),right.length());
        for(int i=0;i<minlen;++i){
            if(left[i]!=right[i])
                return left.substr(0,i);
        }
        return left.substr(0,minlen);
    }
    string divising(const vector<string>& strs,int start,int end){
        if(start==end) return strs[start];
        else{
            int mid = (start+end)/2;
            string left = divising(strs,start,mid);
            string right = divising(strs,mid+1,end);
            return commonprefix(left,right);
        }
    }
    string longestCommonPrefix(vector<string>& strs) {
        int n = strs.size();
        if(n<=0) return "";
        else return divising(strs,0,n-1);
    }

};

最后一个二分法,我们是知道一个最长前缀的长度最长只能等于所有字符串中最短的字符串的长度,所以我们通过获取一个最短的字符串的长度,然后对每个字符串在这个长度内进行比较能否获得公共前缀。然后我们进行二分是对这个这个长度进行二分,然后二分后的长度再用来找公共前缀,直到找到这个公共前缀的最大长度。
我们通过以下判断,如果当前长度能找到公共前缀,但他不一定最长,所以此时还需要让长度增加,即left=mid。如果没找到公共前缀,可能就是长度太长了,此时应该减小长度,即right=mid-1;
但这个方法超时了,但思路挺好的,用来学习。

class Solution {
public:
    bool iscommonprefix(vector<string>&strs,int len){
        string str0=strs[0].substr(0,len);
        int count = strs.size();
        for(int i=1;i<count;++i){
            string str =strs[i];
            for(int j=0;j<len;++j){
                if(str0[j]!=str[j])
                    return false;
            }
        }
        return true;
    }
    string longestCommonPrefix(vector<string>& strs) {
        if(!strs.size()) return "";
        // min_element是一个指针,指向找到的最短长度字符串,然后->size()就是获得这个指针指向字符串的长度
        int minlen = min_element(strs.begin(),strs.end(),[](const string& a,const string& b){return a.size()<b.size();})->size();
        int left = 0,right =minlen;
        while(left<right){
            int mid = (left+right)/2;
            if(iscommonprefix(strs,mid)) //如果找到公共前缀,可能不是最长,所以要增加长度
                left = mid;
            else  //否则就减短长度
                right =mid-1;
        }
        return strs[0].substr(0,left);
    }

};

还有一种思路,就是用字典树,把所有的字符串都加入进字典树中,然后找到第一个出现分支的节点,那么从根节点到该节点就是最长公共前缀,但这个方法空间复杂度较高,因为每个节点都需要挂着26个节点,寻找第一个分支的时候还需要遍历每个节点判断是否有分支,所以有点不适合使用。就提供一个思路吧。

posted @ 2021-05-26 00:54  Mrsdwang  阅读(258)  评论(0编辑  收藏  举报