阴间作业和阴间小学期害人不浅: (
题目描述
(我太懒了就直接复制原题了)
Given a string containing only three types of characters: ‘(’, ‘)’ and ‘*’, write a function to check whether this string is valid. We define the validity of a string by these rules:
Any left parenthesis ‘(’ must have a corresponding right parenthesis ‘)’.
Any right parenthesis ‘)’ must have a corresponding left parenthesis ‘(’.
Left parenthesis ‘(’ must go before the corresponding right parenthesis ‘)’.
‘*’ could be treated as a single right parenthesis ‘)’ or a single left parenthesis ‘(’ or an empty string.
An empty string is also valid.
题目中主要是要理解*可以有三种匹配方式
(1)作为左括号匹配
(2)作为右括号匹配
(3)忽略不计(实际左括号和右括号可以完美匹配的时候就不需要*了)
解题思路
法一: 一看到括号匹配,第一反应是用栈来完成。而这题当中多了一个*的处理,由于无法在遇到*的时候是无法确定它到底是哪种匹配方式,于是我就想用一个额外的数据结构将*存下来,在遇到右括号去进行匹配的时候,和最后还剩左括号需要右括号来匹配的时候,就refer to这个*看能不能匹配上。
在第一次写的时候,我用了一个int变量来存放*的数量,当发现实际左右括号匹配不上的时候就看是否有*来做一个通配。但很快发现这个方法是有问题的。
以"**("为例,遍历下来会得到*的数量为2,最后发现左括号的时候refer to*的数量发现有*可以匹配,那么结果就为true,但很显然这个字符串是不符合要求的。
在上面的方法中,我忽略了一个关键问题,就是*和(的相对位置,很显然,在(之前出现的*是无法作为)来匹配的。因此,我在保存下无法确定的*时,不能只记录下数量,而是要将相对位置也记录下来。于是改进思路,用两个栈来存放,一个作为主栈,存放(和*并保持相对位置,另一个作为辅助栈,在遇到)要先去主栈中找(时临时存放*。大致步骤如下(栈1为主栈,栈2为辅助栈):
遍历字符串的每个字符,
(1)遇到(或*,入栈1
(2)遇到),从栈顶开始在主栈中找(,如果遇到的是*,就将其入栈2,直至找到(;如果最后栈1空,就用*来匹配
(3)匹配完)后将栈2中的*还原至栈1中
最后判断是否匹配成功:
在栈1中找(,并将一路上遇到的*入栈2,如果遇到的(都能有*匹配,且最后栈1空,则匹配成功,否则失败
这个方法时间复杂度O(N²),空间复杂度O(N)
代码如下:
class Solution {
public boolean checkValidString(String s) {
if (s == null || s.length() == 0) {
return true;
}
Stack<Character> mainStack = new Stack<>();
Stack<Character> starStack = new Stack<>();
for (int i = 0; i < s.length(); ++i) {
char tmp = s.charAt(i);
if (tmp != ')') {
mainStack.push(tmp);
}
else {
char top = '\0';
while (!mainStack.empty() && (top = mainStack.peek()) == '*') {
mainStack.pop();
starStack.push(top);
}
if (!mainStack.empty()) {
mainStack.pop();
}
else if (!starStack.empty()) {
starStack.pop();
}
else {
return false;
}
while (!starStack.empty()) {
mainStack.push(starStack.peek());
starStack.pop();
}
}
}
while (!mainStack.empty()) {
if (mainStack.peek() == '*') {
mainStack.pop();
starStack.push('*');
}
else {
// '('
if (!starStack.empty()) {
starStack.pop();
mainStack.pop();
}
else {
return false;
}
}
}
return true;
}
}
法二: 题解中提供了一种贪心算法(虽然我是没看出来贪心在哪里),比较巧妙。不考虑*的情况的话,其实我们只需要遍历时记录下(的数量,然后在遇到)时根据数量去判断是否可以匹配,在这个思路中,(的数量在一个时刻上都是确定值。但现在有了*,遍历遇到*时,(的数量就无法确定了,它可以作为一个(用来匹配,那么数量就要+1,可以作为一个)来匹配,那么数量应该-1,也可以直接忽略,那么数量不变。事实上,把*考虑进来之后,(的值就不再是一个确定值,而是一个可能区间。
那么,我们就可以真的用一个区间来表示(的值的可能取值,只要最后它的最小值为0,那么就说明可以成功匹配。具体步骤如下:
初始时low和high均为0,遍历字符串的每一个字符,
(1)遇到(,low++ && high++
(2)遇到*,low-- && high++,(这一步我觉得是理解区间在这题里的意义的重点)
(3)遇到),low-- && high--,(因为)必定会消耗一个(或者*)
代码如下:
class Solution {
public boolean checkValidString(String s) {
if (s == null || s.length() == 0) {
return true;
}
int low = 0, high = low;
for (int i = 0; i < s.length(); ++i) {
char tmp = s.charAt(i);
low += (tmp == '(')? 1 : -1;
high += (tmp != ')')? 1 : -1;
if (high < 0)
break;
low = Math.max(0, low);
}
return low == 0;
}
}
这篇博客讨论了LeetCode的第16天挑战,涉及有效括号字符串的验证。解析了题目要求,指出了解题关键是理解'*'字符的三种匹配方式。博主分享了两种解题思路:一是使用栈来处理,但需要考虑'*'与括号的相对位置;二是通过贪心算法,利用'*'形成可能的值区间,确保最后区间的最小值为0,表示匹配成功。
4万+

被折叠的 条评论
为什么被折叠?



