字符串相关题目总结

5. 最长回文子串

挺经典的题目,写法也很多种,最简单的做法直接O(n^2)枚举子串,再遍历子串看是否回文,整体复杂度O(n^3)。判断子串回文的过程可以用字符串哈希优化到O(1),这样整体复杂度为O(n^2)。接下来还可以用二分进一步优化,二分回文串长度就行了,二分check里面遍历一遍看是否存在这个长度的回文串,整体复杂度为O(nlogn)。最后可以用马拉车算法优化到O(n),马拉车就是为了解决这个问题专门设计出来的。

//O(n^2),枚举子串+字符串哈希
class Solution {
public:
    string longestPalindrome(string s) {
        s = " " + s;
        vector l(s.size()+1), r(s.size()+1), p(s.size()+1);
        const int P = 233;
        p[0] = 1;
        for(int i = 1; i < s.size(); i++)
            p[i] = p[i-1]*P, l[i] = l[i-1]*P+s[i];
        for(int i = s.size()-1; i >= 1; i--)
            r[i] = r[i+1]*P+s[i];
        int len = 0, ansl, ansr;
        for(int i = 1; i < s.size(); i++)
            for(int j = i; j < s.size(); j++){
                if(l[j]-l[i]*p[j-i] == r[i]-r[j]*p[j-i] && j-i+1 > len){
                    len = j-i+1;
                    ansl = i, ansr = j;
                }
            }
        return s.substr(ansl, ansr-ansl+1);
    }
};
//O(nlogn),二分+字符串哈希
class Solution {
public:
    vector l, r, p;
    const int P = 233;
    int ansl;
    bool check(int x){
        x = 2*x+1;
        for(int i = 0; i+x-1 < l.size(); i++){
            if(l[i+x-1]-l[i]*p[x-1] == r[i]-r[i+x-1]*p[x-1]){
                ansl = i;
                return true;
            }
        }
        return false;
    }
    string longestPalindrome(string s) {
        string cpy = "";
        for(int i = 0; i < s.size(); i++)
            cpy += s[i], cpy += '*';
        s = " *" + cpy;
        vector l(s.size()+1), r(s.size()+1), p(s.size()+1);
        p[0] = 1;
        for(int i = 1; i < s.size(); i++)
            p[i] = p[i-1]*P, l[i] = l[i-1]*P+s[i];
        for(int i = s.size()-1; i >= 1; i--)
            r[i] = r[i+1]*P+s[i];
        this->l = l, this->r = r, this->p = p;
        int ans = -1, ll = 1, rr = s.size()/2-1;
        while(ll <= rr){
            int mid = ll+rr>>1;
            if(check(mid)){
                ans = mid;
                ll = mid+1;
            }
            else rr = mid-1;
        }
        string res = "";
        cpy = s.substr(ansl, 2*ans+1);
        for(int i = 0; i < cpy.size(); i++)
            if(cpy[i] != '*')
                res += cpy[i];
        return res;
    }
};

3. 无重复字符的最长子串

也是挺经典的题,双指针就行了,过程中维护一个map,标记区间内出现过的字符。

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        vector mp(512);
        int ans = 0;
        for(int l = 0, r = 0; l < s.size(); l++){
            while(r < s.size() && mp[s[r]] == 0){
                mp[s[r]] = 1;
                r++;
            }
            ans = max(ans, r-l);
            mp[s[l]] = 0;
        }
        return ans;
    }
};

76. 最小覆盖子串

和上面一道题类似,也是用双指针解决,只不过过程中需要维护的东西多了点。首先需要统计出来t串中出现了几种不同的字符以及各自的出现次数,然后双指针过程中维护s串区间内出现了多少种满足要求(即区间内该字符出现次数大于等于t串内的出现次数)的不同字符以及各自出现次数,其余部分就和上一题一样了。

class Solution {
public:
    string minWindow(string s, string t) {
        int nums = 0, numt = 0;
        vector mps(512), mpt(512);
        for(int i = 0; i < t.size(); i++){
            if(mpt[t[i]] == 0) numt++;
            mpt[t[i]]++;
        }
        int ans = 0x3f3f3f3f, ansl, ansr;
        for(int l = 0, r = 0; l < s.size(); l++){
            while(r < s.size() && nums < numt){
                if(mps[s[r]]+1 == mpt[s[r]]) nums++;
                mps[s[r]]++;
                r++;
            }
            if(nums == numt && ans > r-l){
                ans = r-l;
                ansl = l, ansr = r-1;
            }
            if(mps[s[l]] == mpt[s[l]]) nums--;
            mps[s[l]]--;
        }
        if(ans != 0x3f3f3f3f) return s.substr(ansl, ansr-ansl+1);
        return "";
    }
};

151. 反转字符串中的单词

开一个vector存储所有单词,然后reverse一下就行了。进阶要求不使用额外空间,原地完成反转,对于java的话由于字符串不能修改,所以基本上做不到。但是c++的字符串可以修改,所以可以做到原地反转。整体操作分三步,第一步先去掉多余的空格,将"  you are   so  beautiful  "转为"you are so beautiful",具体实现就是设置两个下标,一个读一个写,在原来的串上覆盖写就行。第二步整体反转,得到"lufituaeb os era uoy"。第三步每个单词挨个进行反转得到"beautiful so are you"。

//普通写法
class Solution {
public:
    string reverseWords(string s) {
        vector a;
        string word = "";
        for(int i = 0; i < s.size(); i++){
            if(s[i] != ' ')
                word += s[i];
            else{
                if(word != "") a.push_back(word);
                word = "";
            }
        }
        if(word != "") a.push_back(word);
        reverse(a.begin(), a.end());
        string res = a[0];
        for(int i = 1; i < a.size(); i++)
            res += " ", res += a[i];
        return res;
    }
};
//进阶要求的空间优化写法
class Solution {
public:
    string reverseWords(string s) {
        int len = 0;
        for(int i = 0; i < s.size(); i++){
            if(s[i] != ' ') s[len++] = s[i];
            else if(len > 0 && s[len-1] != ' ') s[len++] = ' ';
        }
        if(s[len-1] == ' ') len--;
        s = s.substr(0, len);
        reverse(s.begin(), s.end());
        int l = -1, r;
        for(int i = 0; i < s.size(); i++){
            if(s[i] != ' '){
                if(l == -1) l = i;
                r = i;
            }
            else{
                reverse(s.begin()+l, s.begin()+r+1);
                l = -1;
            }
        }
        reverse(s.begin()+l, s.begin()+r+1);
        return s;
    }
};

394. 字符串解码

如果中括号不能嵌套那就很简单了,但这题括号可以嵌套,这种涉及到优先级的情况一般要使用栈来辅助。可以开两个栈,第一个栈存储字符串,第二个栈存储重复次数,然后遍历原字符串,遇到数字直接丢进数字栈,遇到字符串直接丢进字符串栈,然后遇到左括号也需要丢进字符串栈,如果遇到右括号就要开始运算了,字符串栈不断弹栈直到弹出左括号,这些字符串可以拼接为一个长字符串,然后这个长字符串重复次数就是数字栈的栈顶,若干次拼接后得到一个更长的字符串,接下来把该串再次入字符串栈。遍历完原串后数字栈一定为空,字符串栈中可能有若干元素,把字符串栈中所有字符串拼接起来就是答案。

class Solution {
public:
    string decodeString(string s) {
        string res = "", t = "";
        stack st1;
        stack st2;
        int num = 0;
        for(int i = 0; i < s.size(); i++){ 
            if(s[i] >= '0' && s[i] <= '9') num = num*10+s[i]-'0';
            else if(s[i] >= 'a' && s[i] <= 'z') t += s[i];
            else if(s[i] == ']'){
                if(t != ""){
                    st1.push(t);
                    t = "";
                }
                string a = st1.top(), sum = "";
                st1.pop();
                while(a != "["){
                    sum = a+sum;
                    a = st1.top();
                    st1.pop();
                }
                string all = "";
                for(int j = 1; j <= st2.top(); j++)
                    all += sum; 
                st2.pop();
                st1.push(all);
            }
            else{
                if(num){
                    st2.push(num);
                    num = 0;
                }
                if(t != ""){
                    st1.push(t);
                    t = "";
                }
                st1.push("[");
            }
        }
        if(t != "") st1.push(t);
        while(st1.size()){
            res = st1.top()+res;
            st1.pop();
        }
        return res;
    }
};

14. 最长公共前缀

两重循环遍历一下就行了,外层遍历第i个字符,内层确定字符串数组中所有字符串第i个字符是否都相同,如果都相同就可以加入结果字符串。这题字符串哈希+二分看似能优化,其实会更慢,因为预处理哈希的过程就和暴力复杂度一样了。

这道题还有个更简单的方法,直接对字符串数组排序,然后看首尾两字符串最长公共前缀。

class Solution {
public:
    string longestCommonPrefix(vector& strs) {
        int mn = 0x3f3f3f3f;
        for(int i = 0; i < strs.size(); i++)
            mn = min(mn, (int)strs[i].size());
        string res = "";
        for(int i = 0; i < mn; i++){
            bool flag = false;
            for(int j = 0; j < strs.size()-1; j++){
                if(strs[j][i] != strs[j+1][i]){
                    flag = true;
                    break;
                }
            }
            if(!flag) res += strs[0][i];
            else break;
        }
        return res;
    }
};

你可能感兴趣的:(算法,哈希算法,数据结构)