《LeetCode之每日一题》:147.有效的括号字符串
Posted 是七喜呀!
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《LeetCode之每日一题》:147.有效的括号字符串相关的知识,希望对你有一定的参考价值。
有关题目
给定一个只包含三种字符的字符串:( ,) 和 *,
写一个函数来检验这个字符串是否为有效字符串。
有效字符串具有如下规则:
任何左括号 ( 必须有相应的右括号 )。
任何右括号 ) 必须有相应的左括号 ( 。
左括号 ( 必须在对应的右括号之前 )。
* 可以被视为单个右括号 ) ,或单个左括号 ( ,或一个空字符串。
一个空字符串也被视为有效字符串。
示例 1:
输入: "()"
输出: True
示例 2:
输入: "(*)"
输出: True
示例 3:
输入: "(*))"
输出: True
注意:
字符串大小将在 [1,100] 范围内。
题解
法一:动态规划
参考官方题解
思路:
①dp[i][j]含义:在s中,下标从i 到 j (0 <= i <= j < n),对应的字符串是否有效
②初始化;
①长度为 1 字符串,为 "*", 为真。
②长度为 2 字符串,为"()","(*","*)","**",为真。
③动态转移方程:
长度大于 2 字符串dp[i][j](0 <= i < j < n)的真假,取决于以下两种情况中的一种即可。
①c1 = s[i], c2 = s[j], 满足(c1 == '(' || c1 == '*') && (c2 == ')' || c2 == '*')。
此时dp[i][j] = dp[i + 1][j - 1];
②[i, j] = [i, k] U [k + 1, j],即我们说明[i, k]与 [k + 1, j]为 2 个有效的括号字符串。
④注意点:由于dp[i][j] = dp[i + 1][j - 1],所以我们要注意遍历的为倒序
class Solution {
public:
bool checkValidString(string s) {
int n = s.size();
vector<vector<bool>> dp(n, vector<bool>(n, false));
//长度为 1 字符串,为 "*", 为真
for (int i = 0; i < n; ++i){
char c = s[i];
dp[i][i] = c == '*';
}
//长度为 2 字符串,为"()","(*","*)","**",为真
for (int i = 0; i < n - 1; ++i){
char c1 = s[i], c2 = s[i + 1];
if ((c1 == '(' || c1 == '*') && (c2 == ')' || c2 == '*')){
dp[i][i + 1] = true;
}
}
//长度大于 2 字符串dp[i][j](0 <= i < j < n)的真假
for (int i = n - 3; i >= 0; --i){
char c1 = s[i];
for (int j = + 2; j < n; ++j){
char c2 = s[j];
if ((c1 == '(' || c1 == '*') && (c2 == ')' || c2 == '*')){
dp[i][j] = dp[i + 1][j - 1];
}
for (int k = i; k < j && !dp[i][j]; ++k){
dp[i][j] = dp[i][k] && dp[k + 1][j];
}
}
}
return dp[0][n - 1];
}
};
法二:正反遍历
参考官方题解评论区Amber_Han
思路:
保证左右括号的有效性,'*'作为万能括号可用可不用
①从前往后确定有效的 ')'
②从后往前确定有效的 '('
class Solution {
public:
bool checkValidString(string s) {
int n = s.size();
int l = 0, m = 0;
//正向遍历,判断左括号数量是否足够,且是否在满足有效括号
for (int i = 0; i < n; ++i){
char c = s[i];
if (c == '('){
++l;
} else if (c == '*'){
++m;
} else {
--l;
}
if (l < 0) --m, ++l;
if (m < 0) return false;
}
//反向遍历,判断右括号数量是否足够,且是否满足有效的括号
int r = 0;
m = 0;
for (int i = n - 1; i >= 0; --i){
char c = s[i];
if (c == '('){
--r;
} else if (c == '*'){
++m;
} else {
++r;
}
if (r < 0) --m, ++r;
if (m < 0) return false;
}
return true;
}
};
时间复杂度:O(n),正反遍历时间复杂度O(n),总时间复杂度O(n)
空间复杂度:O(1)
法三:栈
思路:
根据栈先进后出特点,本题可以使用栈。
我们使用两个栈分别存储左括号 与 '*',具体地:
①碰到左括号与 '*' 我们入栈。
②碰到右括号我们进行以下判断:
由于'*' 可看作空白字符串,我们优先存在的除去左括号,
若不存在左括号,我们使用'*'充当左括号,
若两者皆不存在,则返回false
③由于遍历结束,还可能剩余左括号与'*',所以我们进行循环
我们两个栈中存放的为下标,此时'*'看作右括号时,要满足,
对应栈顶下标大于左括号栈顶下标,如不存在,则返回false,反之,我们拿掉栈顶元素,继续循环
循环结束条件为,左括号栈与星号栈有一个先为空
最后判断,左括号栈是否为空,为空则返回true;
class Solution {
public:
bool checkValidString(string s) {
int n = s.size();
stack<char> l, a;
for (int i = 0; i < n; ++i){
char c = s[i];
if (c == '('){
l.push(i);
} else if (c == '*'){
a.push(i);
} else {
if (!l.empty()){
//由于'*'可做空字符串,所以我们优先除去存在的左括号,若没有左括号,若有'*',我
l.pop();
} else if (!a.empty()){
a.pop();
} else {
return false;
}
}
}
//')' 遍历除去之后,可能还有'('与 '*'
while(!l.empty() && !a.empty()){
int leftIndex = l.top();
l.pop();
int aIndex = a.top();
a.pop();
if (leftIndex > aIndex){
return false;
}
}
return l.empty();
}
};
法四:贪心
参考官方题解
class Solution {
public:
bool checkValidString(string s) {
int n = s.size();
int maxCnt = 0, minCnt = 0;
for (int i = 0; i < n; ++i){
char c = s[i];
if (c == '('){
++maxCnt;
++minCnt;
} else if (c == ')'){
--maxCnt;
minCnt = max(minCnt - 1, 0);
if (maxCnt < 0){
return false;
}
} else {
++maxCnt;
minCnt = max(minCnt - 1, 0);
}
}
return minCnt == 0;
}
};
时间复杂度:O(n),n为字符串 s 长度,自左往右遍历一次
空间复杂度:O(1)
以上是关于《LeetCode之每日一题》:147.有效的括号字符串的主要内容,如果未能解决你的问题,请参考以下文章