Leetcode 14.最长公共前缀(C++)

水平扫描解法

思路: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库函数的调用真的很耗性能

你可能感兴趣的:(Leetcode进阶之路)