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]为‘*’:又可以分成两种情况
- p[j]在s出现0次,这种情况直接跳过p[j]和p[j+1]即可
- 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 有效的括号
题目描述
给定一个只包含三种字符的字符串:( ,) 和 *,写一个函数来检验这个字符串是否为有效字符串。有效字符串具有如下规则:
- 任何左括号 ( 必须有相应的右括号 )。
- 任何右括号 ) 必须有相应的左括号 ( 。
- 左括号 ( 必须在对应的右括号之前 )。
- * 可以被视为单个右括号 ) ,或单个左括号 ( ,或一个空字符串。
- 一个空字符串也被视为有效字符串。
该题的简化版(leetcode.20)是没有*的,因此只需要简单地用一个栈判断即可。如果只有(和),那可以连栈都不用。
但这题有*,因此肯定无法用一个栈解决问题。
双栈法
此题的解法仅适用于只有'('和')'的情况。因为只有一种括号类型,因此对于号匹配没有那么严苛的要求——即不要求在判断中一定要匹配相邻的)或(。
比如:s = (()。我们在判断时并不一定要要求和第二个(匹配、)和第一个(匹配,因为当前串中,和)实际是等价的,所以我们在判断时可以先不决定是否要匹配,而可以先把能匹配)的(全部匹配完。对于剩余的(和,只需要满足(的数量不比多,且对于每一个(,都有比它位置靠后的*相对应。
因此,有了双栈解法,思路如下:
- 遇见'('将index入左栈
- 遇见'*'将index入右栈
- 遇见')'出左栈——这一步即为了先将所有的)匹配掉;如果左栈为空,出右栈——已经没有'(',自然用来替,且这些肯定都在当前')'的左边;如果右栈也空,则无法匹配,返回false
- 遍历结束后,先判断左栈是否比右栈多,如果是,则表明没有足够的*来匹配剩余的(了,不满足条件,返回false
- 如果左栈不多于右栈,那剩下的步骤只需依次判断,对于左栈的每一个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;
}
};