经典的括号匹配判断问题,由于是比较久远的记忆不太记得怎么做了,稍微想了一下然后搓出来的。
仅有一种括号的情形比较简单,只要记录一下正反括号的数目从左往右扫一遍,保证中间不出现未匹配的右括号即可。但这种方法对于本题的解法在思路上没有贡献,因为如果使用同样的方法,我们会发现两个子串"(({(()"与"(((({)"在匹配到最末位的时候所记录的状态是相同的,而后者很明显是不满足要求的串。
回归本题,一个简单的思路是确认每个括号是否匹配,即对于每个括号求与其相匹配的另一个反括号。根据满足括号匹配的串而言,有着若A是匹配的,则"(A)"也是匹配的这一性质,也就是说若我们从左向右处理的话,每次对于一个左括号'(', 尝试跳过中间的A来确认是否存在右括号')'与之匹配。
最简单的做法,对每一个左括号单独处理,向右扫的过程中无视与自身种类不同的括号,然后使用仅一种括号的做法,直到找到与当前左括号相匹配的右括号为止,然后从原串中将这一对括号去除,循环至整个串清零,则返回true。
时间O(n²),而且会WA,因为无视种类不同的括号,"({)}"这个样例都过不了。
那么使用递归方法求解,每次当前的左括号l找到右括号r时,验证[l+1,r-1]是否合法,解决了上述WA的问题,时间复杂度仍然是O(n²)。
很自然的能够想到改进的思路,既然使用了递归,我们可以做到将左右括号的匹配与中间子串的合法性验证同时进行,方法如下:
构造如下next函数int next(pos)用来求与pos位置的左括号对应的右括号:
- 对于当前位置pos,确认是否为左括号,若不是则原串不合法
- 边界处理,检查pos+1是否与当前位置相匹配,若是则返回pos+2
- 递归调用next(pos+1),即对于"(A)"中去除A的部分,但由于可能存在"(()())"——即A="()()"——以及其它与之类似的情况, 我们判断在next(pos+1)是否与当前左括号匹配时,若不匹配则有i=next(pos+1),并循环使i=next(i),直至求得与pos的左括号对应的i的右括号
- 若上述i越界,即超出原串长度len,则原串不合法
这样一来串的每个位置都只被访问一次,也就是时间复杂度降到了O(n),而且不仅完成了合法性的检查,还完成了每对括号的匹配。
代码如下:
class Solution {
public:
bool isleft(char c){
return c == '(' || c == '[' || c == '{';
}
bool isright(char c){
return c == ')' || c == ']' || c == '}';
}
bool ispair(char lc, char rc){
if (lc == '(' && rc == ')')
return true;
if (lc == '[' && rc == ']')
return true;
if (lc == '{' && rc == '}')
return true;
return false;
}
int next(int pos, int len, const string &s){
if (!isleft(s[pos]) || pos+1 >= len)
return -1;
if (ispair(s[pos], s[pos+1]))
return pos + 2;
int i = next(pos+1, len, s);
while(i < len){
if (i == -1)
return -1;
if (ispair(s[pos], s[i]))
return i + 1;
i = next(i, len, s);
}
return -1;
}
bool isValid(string s) {
int len = s.length();
int i = 0;
while(i < len){
i = next(i, len, s);
if (i == -1)
return false;
}
return true;
}
};
最终结果也相当不错:
本题虽然难度为easy,但抛开最简单的O(n²)做法,O(n)的思路个人认为在leetcode这边加个medium还是没有问题的。