1、判断有效的括号
1.1、题目
题目链接
给定一个只包括 '(',')','{','}','[',']'
的字符串,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
输入: "()"
输出: true
输入: "()[]{}"
输出: true
输入: "(]"
输出: false
输入: "([)]"
输出: false
1.2、思路
核心思路:栈
,首先我们应该明确如下两条规则:
- 1、一个「合法」括号组合的
左括号数量一定等于右括号数量
。 - 2、对于一个「合法」的括号字符串组合 p,必然对于任何
0 <= i < len ( p)
都有:子串 p[0..i]
中左括号的数量
都大于或等于右括号的数量
。- 因为从左往右算的话,肯定是左括号多嘛,到最后左右括号数量相等
我们先处理一种括号:
- 每个
右括号 )
的左边必须有一个左括号 (
和它匹配
bool isValid(string str) {
// 待匹配的左括号数量
int left = 0;
for (char c : str) {
if (c == '(')
left++;
else // 遇到右括号
left--;
if (left < 0)
return false;
}
return left == 0;
}
对于三种括号的情况,用一个名为 left 的栈
代替之前思路中的 left 变量,遇到左括号就入栈
,遇到右括号就去栈中寻找最近的左括号
,看是否匹配。
1.3、题解
class Solution {
public:
bool isValid(string s) {
int len = s.size();
if(len % 2 != 0)
return false;
stack<char>stack;
map<char,char>m = {{')','('},{']','['},{'}','{'}};//注意这里面括号的顺序,根据右侧括号,找左侧
for(int i = 0;i < len ;i++)
{
if(s[i] == '(' || s[i] == '[' || s[i] == '{')
stack.push(s[i]);//遇到左括号就push
else
{
if(stack.empty())
return false;
if(m[s[i]] == stack.top())
stack.pop();//遇到右括号就pop
else
return false;
}
}
return stack.empty();
}
};
2、生成有效的括号
2.1、题目
题目链接
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合
。
输入:n = 3
输出:[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
2.2、思路
对于括号合法性的判断
,主要是借助「栈」
这种数据结构,而对于括号的生成
,一般都要利用回溯递归
的思想。
重复两条规则:
- 1、一个「合法」括号组合的
左括号数量一定等于右括号数量
。 - 2、对于一个「合法」的括号字符串组合 p,必然对于任何
0 <= i < len ( p)
都有:子串 p[0..i]
中左括号的数量
都大于或等于右括号的数量
。- 因为从左往右算的话,肯定是左括号多嘛,到最后左右括号数量相等
对于 2n 个位置
,必然有 n 个左括号
,n 个右括号
,用 left
记录还可以使用多少个左括号
,用 right
记录还可以使用多少个右括号
2.3、题解
vector<string> generateParenthesis(int n) {
if (n == 0) return {};
// 记录所有合法的括号组合
vector<string> res;
// 回溯过程中的路径
string track;
// 可用的左括号和右括号数量初始化为 n
backtrack(n, n, track, res);
return res;
}
// 可用的左括号数量为 left 个,可用的右括号数量为 rgiht 个
void backtrack(int left, int right,
string& track, vector<string>& res) {
// 若左括号剩下的多,说明不合法。剩的多因为用的少嘛,左侧先填充的,所以就这样了。
if (right < left) return;
// 数量小于 0 肯定是不合法的
if (left < 0 || right < 0) return;
// 当所有括号都恰好用完时,得到一个合法的括号组合
if (left == 0 && right == 0) {
res.push_back(track);
return;
}
// 尝试放一个左括号
track.push_back('('); // 选择
backtrack(left - 1, right, track, res);
track.pop_back(); // 撤消选择
// 尝试放一个右括号
track.push_back(')'); // 选择
backtrack(left, right - 1, track, res);
track.pop_back(); ;// 撤消选择
}
3、最长有效括号
3.1、题目
给定一个只包含 '(' 和 ')' 的字符串,找出最长的包含有效括号的子串的长度。
示例 1:
输入: "(()"
输出: 2
解释: 最长有效括号子串为 "()"
示例 2:
输入: ")()())"
输出: 4
解释: 最长有效括号子串为 "()()"
3.2、思路
状态
- dp[i] 表示以下标 i 为字符结尾的最长有效字符串的长度
状态转移方程
- 以 ( 结尾的子字符串不考虑,因为不可能构成合法括号
if s[i] == ‘)’
- s[i - 1] == ‘(’,也就是字符串形如 “……()”,我们可以推出:dp[i] = dp[i − 2] + 2。
- 因为结束部分的 “()” 是一个有效子字符串,并且将之前有效子字符串的长度增加了 2.
s[i - 1] == ‘)’,也就是字符串形如 “…))”,我们可以推出:
if s[i - dp[i - 1] - 1] == '(',
dp[i] = dp[i − 1] + dp[i − dp[i − 1] − 2] + 2。
因为如果倒数第二个 )是一个有效子字符串的一部分(记为subs),我们此时需要判断 subs 前面一个符号是不是 (
,如果恰好是(
,我们就用 subs 的长度(dp[i - 1)加上 2 去更新 dp[i]
。
除此以外,我们也会把子字符串 subs 前面的有效字符串的长度加上,也就是 dp[i − dp[i − 1] − 2].
3.3、题解
暴力法
bool isVaild(string s) {
stack<char> a;
for (auto v : s) {
if (v == '(') {
a.push(v);
} else if (!a.empty() && a.top() == '(') {
a.pop();
} else return false;
}
return a.empty();
}
int longestValidParentheses(string s) {
int maxLen = 0;
for (int i = 0; i < s.length(); i++) {
for (int j = 2; j + i <= s.length(); j += 2) {
if (isVaild(s.substr(i, j))) {
if (j > maxLen) {
maxLen = j;
}
}
}
}
return maxLen;
}
class Solution {
public:
int longestValidParentheses(string s) {
int size = s.length();
vector<int> dp(size, 0);
int maxVal = 0;
for(int i = 1; i < size; i++) {
if (s[i] == ')') {
if (s[i - 1] == '(') {
dp[i] = 2;
if (i - 2 >= 0) {
dp[i] = dp[i] + dp[i - 2];
}
} else if (dp[i - 1] > 0) {
if ((i - dp[i - 1] - 1) >= 0 && s[i - dp[i - 1] - 1] == '(') {
dp[i] = dp[i - 1] + 2;
if ((i - dp[i - 1] - 2) >= 0) {
dp[i] = dp[i] + dp[i - dp[i - 1] - 2];
}
}
}
}
maxVal = max(maxVal, dp[i]);
}
return maxVal;
}
};
复杂度分析:
时间复杂度:O(n)。遍历整个字符串一次,就可以将 dp 数组求出来.
空间复杂度: O(n)。需要一个大小为 n 的 dp 数组.
参考
https://labuladong.gitbook.io/algo/suan-fa-si-wei-xi-lie/he-fa-kuo-hao-sheng-cheng