【leetcode】字符串相关

leetcode 3. 无重复最长字串

题目描述

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。

思路

用mp记录字符上次出现的位置,用last记录当前起始位置,遇到重复的字符计算ans,并更新last。
注意:这里由于有last限定了字符串的起始位置,因此每次判断时如果mp[s[i]]在last之前,就不用更新。

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        vector mp(129, -1);
        int len = s.length(), ans = 0, last = 0, i = 0;
        for(int i=0; i= last) {
                ans = max(ans, i-last);
                last = mp[s[i]] + 1;
            }
            mp[s[i]] = i;
        }
        ans = max(ans, len-last);
        return ans;
    }
};

leetcode 10. 正则表达式匹配

题目描述

给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.' 和 '*' 的正则表达式匹配。

'.' 匹配任意单个字符
'*' 匹配零个或多个前面的那一个元素
所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

递归思路

边界情况:s空p空,返回true;s不空p空,返回false;

p[j+1]为‘*’:又可以分成两种情况

  1. p[j]在s出现0次,这种情况直接跳过p[j]和p[j+1]即可
  2. p[j]在s中出现n次,且n未知。由于n未知,则可以用递归的方式判断p[j]与s[i+1]的关系。(当然,继续进行递归的前提是p[j]==s[i],否则它们一个字符也匹配不上,只能看是不是情况1)

p[j+1]不为'*':这个时候只需要判断p[j]是否等于是s[i],如果是,递归p[j+1]和s[i+1]

class Solution {
public:
    bool isMatch(string s, string p) {
        return myisMatch(s.c_str(), p.c_str());
    }
    bool myisMatch(const char* s,const char* p) {
        if(*p == '\0') return *s == '\0';
        bool flag = *s && (*s == *p || *p == '.');
        if(*(p+1) == '*') {
            return myisMatch(s, p+2) || (flag && myisMatch(++s, p));
        }
        return flag && myisMatch(++s, ++p);
    }
};

动态规划思路

事实上动规思路和递归如出一辙,都是上述三种情况,只不过动规存储了中间结果。
这题比较容易理解的方式是从后往前递归,只需要设置好dplen1=true即可。

class Solution {
public:
    bool isMatch(string s, string p) {
        int len1 = s.length(), len2 = p.length();
        vector > dp(len1+1, vector(len2+1, false));
        dp[len1][len2] = true;
        for(int i=len1; i>=0; --i) {
            for(int j=len2-1; j>=0; --j) {
                bool flag = i < len1 && (s[i] == p[j] || p[j] == '.');
                if(j < len2 -1 && p[j+1] == '*') {
                    dp[i][j] = dp[i][j+2] || (flag && dp[i+1][j]);
                }
                else {  
                    dp[i][j] = (flag && dp[i+1][j+1]);
                }
            }
        }
        return dp[0][0];
    }
};

leetcode.678 有效的括号

题目描述

给定一个只包含三种字符的字符串:( ,) 和 *,写一个函数来检验这个字符串是否为有效字符串。有效字符串具有如下规则:

  1. 任何左括号 ( 必须有相应的右括号 )。
  2. 任何右括号 ) 必须有相应的左括号 ( 。
  3. 左括号 ( 必须在对应的右括号之前 )。
  4. * 可以被视为单个右括号 ) ,或单个左括号 ( ,或一个空字符串。
  5. 一个空字符串也被视为有效字符串。

该题的简化版(leetcode.20)是没有*的,因此只需要简单地用一个栈判断即可。如果只有(和),那可以连栈都不用。
但这题有*,因此肯定无法用一个栈解决问题。

双栈法

此题的解法仅适用于只有'('和')'的情况。因为只有一种括号类型,因此对于号匹配没有那么严苛的要求——即不要求在判断中一定要匹配相邻的)或(。
比如:s = (()。我们在判断时并不一定要要求和第二个(匹配、)和第一个(匹配,因为当前串中,和)实际是等价的,所以我们在判断时可以先不决定是否要匹配,而可以先把能匹配)的(全部匹配完。对于剩余的(和,只需要满足(的数量不比多,且对于每一个(,都有比它位置靠后的*相对应。
因此,有了双栈解法,思路如下:

  1. 遇见'('将index入左栈
  2. 遇见'*'将index入右栈
  3. 遇见')'出左栈——这一步即为了先将所有的)匹配掉;如果左栈为空,出右栈——已经没有'(',自然用来替,且这些肯定都在当前')'的左边;如果右栈也空,则无法匹配,返回false
  4. 遍历结束后,先判断左栈是否比右栈多,如果是,则表明没有足够的*来匹配剩余的(了,不满足条件,返回false
  5. 如果左栈不多于右栈,那剩下的步骤只需依次判断,对于左栈的每一个index,是否有右栈的更大的index来匹配它(这时候'*'代表')',自然下标必须比'('大)
class Solution {
public:
    bool checkValidString(string s) {
        stack st1, st2;
        int len = s.length();
        for(int i = 0; i < len; ++i) {
            if(s[i] == '(') {
                st1.push(i);
            }
            else if(s[i] == '*') {
                st2.push(i);
            }
            else {
                if(!st1.empty()) {
                    st1.pop();
                }
                else if(!st2.empty()) {
                    st2.pop();
                }
                else return false;
            }
        }
        if(st1.size() > st2.size()) return false;
        while(!st1.empty() && !st2.empty()) {
            if(st1.top() < st2.top()) {
                st1.pop();
                st2.pop();
            }
            else return false;
        }
        return st1.empty();
    }
};

贪心法

贪心法只允许'('剩余,且只关心了'('的最多和最少剩余个数。最多剩余意味着将所有*都看成是'(',最少剩余意味着将所有'*'看成是')'。
在这个方法中,用low标示最少剩余,用high标识最多剩余;而为了解决问题,我们对low和high的意义做进一步改动——对于最少剩余的情况,由于可能出现(太少,因此如果此时还将*当成),必然是不合法的,即此时low会小于0,对于这些low小于0的情况,我们并不关心——因为我们对low的做法是将所有*都看成(,但我们也可以将*看成空字符啊,既然low<0已经不合法了,那我们不必再考虑,直接将*忽略一次就可以了。
这里可能会有些不理解,*不是也可以当成)来处理吗?的确,但这不是low关心的,而是high关心的,low只关心最差情况,用刚才的方式(忽略low<0)剪枝后,low变相地只是在观测是否存在*过多的情况而已。也就是,在字符串遍历结束之后,如果low还大于0,那说明最少剩余的情况下,仍无法匹配所有的'(',那匹配失败。

而对于high来说,它每次都将'*'当作'(',因此在遍历结束后,它是很有可能会大于0的。但这并不影响结果,因为当high大于0时,意味这很可能有过多的*被当成'(',只要low==0,即当我们将所有*都当空串时,没有'('结余,那串就是合法的。因此,对于high,我们在结束时并不关心它的大小。但在遍历过程中,需要用high来防止出现)过多的情况(这是动态的,即到达串的某一个位置,届时的状态可能会出现')'过多)。因为当high<0时,说明即便我们将'*'都看作'(',当前也没有多余的'('来匹配')'了。

代码逻辑:
遇'(':low++; high++;
遇')':若low>0则low--; high--;
遇'*':若low>0则low--; high++;
中途若high<0返回false;结束后若low>0返回false,low==0返回true

class Solution {
public:
    bool checkValidString(string s) {
        int low = 0, high = 0;
        int len = s.length();
        for(int i=0; i 0) --low;
                --high;
            }
            else if(s[i] == '*') {
                if(low > 0) --low;
                ++high;
            }
            if(high < 0) return false;
        }
        return low == 0;
    }
};

你可能感兴趣的:(c++算法)