有效的括号(单类型括号)
给定一个只包括 ‘(’,’)’ 的字符串,判断字符串是否有效。有效字符串需满足:左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。注意空字符串可被认为是有效字符串。
示例 1: 输入: “(())” , 输出: true
示例 2: 输入: “()())”, 输出: false
分析:判定有效的括号()组合的条件为从字符串的自左向右顺序起,从下标0到任何下标截止的子串其右括号要小于等于左括号的数目,一个有效的括号的组合在任何一个坐标的位置向左计算,左括号的数目必须大于等于右括号的数目 “()(())”,到括号字符串结束的时候,左括号的数目等于右括号的数目。无效括号的条件为:右括号的数目一旦大于左括号的数目。
同理 :判定有效的括号()组合的条件为从字符串的自右向左顺序起, 也可为任何下标起始值到size()-1下标截止的子串其左括号要小于等于右括号的数目,一个有效的括号的组合在任何一个坐标的起始位置向右到字符串结束位置计算,左括号的数目必须小于等于右括号的数目 “()(())”,坐标到下标0位置起始时,左括号的数目等于右括号的数目。无效括号的条件为:左括号的数目一旦大于右括号的数目。
class Solution {
public:
bool isValid(string s) {
if (s.size() == 0)
return true;
int right = 0;
int left = 0;
for (int i = 0; i < s.size(); i++){
if (s[i] == '(')
left++;
else
right++;
if (right>left)
return false;
}
if(right==left)
return true;
return false;
}
};
有效的括号(多类型括号)
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串,判断字符串是否有效。
有效字符串需满足:左括号必须用相同类型的右括号闭合。左括号必须以正确的顺序闭合。注意空字符串可被认为是有效字符串。
示例:"()[]{}" 与 "{[()]}"为有效的括号。
分析: 从左往右,连续的左括号(包括不连续)的最后一个左括号,应与最接近的其右边的右括号匹配。
- 初始化栈 S。
- 一次处理表达式的每个括号。如果遇到开括号,我们只需将其推到栈上即可。这意味着我们将稍后处理它,让我们简单地转到前面的 子表达式。
- 如果我们遇到一个闭括号,那么我们检查栈顶的元素。如果栈顶的元素是一个 相同类型的 左括号,那么我们将它从栈中弹出并继续处理。否则,这意味着表达式无效。
- 如果到最后我们剩下的栈中仍然有元素,那么这意味着表达式无效。
class Solution {
public:
bool isValid(string s) {
if (s.empty()) //注意:空字符串可被认为是有效字符串
return true;
if (s.size() % 2 == 1) //奇数个
return false;
stack<char> stack;
for (int i = 0; i < s.size(); ++i)
{
if (s.at(i) == '(' || s.at(i) == '[' || s.at(i) == '{')
{
stack.push(s.at(i));
}
else
{
if (stack.empty())
return false;
if ((s.at(i) == ')' && '(' != stack.top()) || (s.at(i) == ']' && '[' != stack.top()) || ((s.at(i) == '}' && '{' != stack.top())))
return false;
stack.pop();
}
}
return stack.empty();
}
};
最长有效括号
给定一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长的包含有效括号的子串的长度。
示例 1: 输入: “(()” , 输出: 2。解释: 最长有效括号子串为 “()”
示例 2:输入: “)()())”,输出: 4。解释: 最长有效括号子串为 “()()”
分析: 这个问题可以通过动态规划解决。我们定义一个 dp 数组,其中第 i 个元素表示以下标为 ii的字符结尾的最长有效子字符串的长度。我们将 dp 数组全部初始化为 0 。现在,很明显有效的子字符串一定以‘)’ 结尾。这进一步可以得出结论:以 ‘(’ 结尾的子字符串对应的 dp 数组位置上的值必定为 0 。所以说我们只需要更新 ‘)’ 在 dp 数组中对应位置的值。
为了求出 dp 数组,我们每两个字符检查一次,如果满足如下条件
- s[i]=‘)’ 且 s[i−1]=‘(’ ,也就是字符串形如"……()",我们可以推出:
dp[i]=dp[i−2]+2
。我们可以进行这样的转移,是因为结束部分的 “()” 是一个有效子字符串,并且将之前有效子字符串的长度增加了 2 。 - s[i]=‘)’ 且 s[i−1]=‘)’,也就是字符串形如"…))" ,我们可以推出:如果
s[i−dp[i−1]−1]=‘(’
,那么dp[i]=dp[i−1]+dp[i−dp[i−1]−2]+2
。
class Solution {
public:
int longestValidParentheses(string s) {
vector<int> dp(s.size(), 0);
int max = 0;
for (int i = 1; i < s.size(); ++i) {
if (s[i] == ')') {
if (s[i - 1] == '(') {
dp[i] = 2 + (i >= 2 ? dp[i-2] : 0);
} else if (i - 1 - dp[i-1] >= 0 && s[i - 1 - dp[i-1]] == '('){
if (i - 2 - dp[i-1] >= 0) {
dp[i] = 2 + dp[i-1] + dp[i - 2 - dp[i-1]];
}
else {
dp[i] = 2 + dp[i-1];
}
}
}
if (dp[i] > max) {
max = dp[i];
}
}
return max;
}
};
括号生成
给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。
例如,给出 n = 3,生成结果为:
[
“((()))”,
“(()())”,
“(())()”,
“()(())”,
“()()()”
]
分析: 回溯法(DFS深度优先遍历+剪枝)。判定有效的括号组合的条件为任何下标截止的子串其右括号要小于等于左括号的数目,一个有效的括号的组合在任何一个坐标的位置向左计算,左括号的数目必须大于等于右括号的数目 “()(())”。剪枝的条件为:左括号的数目一旦小于右括号的数目,以及左括号的数目和右括号数目有的大于n。
只有在我们知道序列仍然保持有效时才添加 ‘(’ or ‘)’,而不是每次添加。我们可以通过跟踪到目前为止放置的左括号和右括号的数目来做到这一点,如果我们还剩一个位置,我们可以开始放一个左括号。 如果左括号大于等于右括号的数量,我们可以放一个右括号。
//回溯:DFS+少量的剪枝,
class Solution {
public:
vector<string> generateParenthesis(int n) {
vector<string> res;
func(res, "", 0, 0, n);
return res;
}
//l和r代表左右括号的数目,n代表需要生成n对括号,res用引用的形式添加。
void func(vector<string> &res, string str, int l, int r, int n){
if(l > n || r > n || r > l) return ;
if(l == n && r == n) {res.push_back(str); return;}
func(res, str + '(', l+1, r, n);
func(res, str + ')', l, r+1, n);
return;
}
};
删除无效的括号
删除最小数量的无效括号,使得输入的字符串有效,返回所有可能的结果。说明: 输入可能包含了除 ( 和 ) 以外的字符。
示例 1: 输入: “()())()”,输出: ["()()()", “(())()”]
示例 2:输入: “(a)())()”,输出: ["(a)()()", “(a())()”]
示例 3:输入: “)(”, 输出: [""]
分析: 思路是假设右括号比左括号多,算法设计成只删除右括号的情况。如果左括号比右括号多怎么办?该算法的巧妙之外就是把这种情况也转换成了删除“右括号”的情况(翻转字符串)。而左右括号的定义是在参数 par 中定义的。如果 par = {’(’, ‘)’},说明 ‘(’ 是左括号,如果 par = {’)’, ‘(’},则 ‘)’ 变成了左括号。仔细体会下面的代码。
- 自左向右移除多余的右括号,用下标n指的是上次移除右括号的下标,下次移除时从n之后的位置移除。但是当出现连续的右括号时,如
())
,移除第一个右括号的第二个右括号的结果是相同的,为避免重复,我们移除连续右括号的第一个。if (s[j] == ‘)’&& (j == n || s[j-1] != ‘)’)), 之所以判断s[j-1] != ‘)’,因为在出现右括号比左括号多1的时候,当出现两个连续的右括号时,第一个已经删除,第二个就不用再处理删除逻辑。 - 自右向左移除多余的左括号,可先翻转字符串,借用1的方法。
class Solution {
public:
vector<string> removeInvalidParentheses(string s) {
vector<string> res;
remove(move(s), {'(', ')'}, 0, 0, res);
return res;
}
void remove(std::string s, const vector<char>& par, int m, int n, vector<string>& res) {
int stack = 0, i = m;
for (int i = m; i < s.length(); ++i) {
if (s[i] == par[0]) stack++;
if (s[i] == par[1]) stack--;
if (stack >= 0) continue;
//自左向右的顺序检测右括号的数目小于等于左括号的数目
// "右"括号多出来了,删除一个右括号
for (int j = n; j <= i; ++j) {
if (s[j] == par[1] && (j == n || s[j-1] != par[1])) {
auto ss = s.substr(0, j) + s.substr(j + 1);
remove(move(ss), par, i, j, res);
}
}
return;
}
reverse(s.begin(), s.end());
//自右向左的顺序检测左括号的数目小于等于右括号的数目
// "左"括号多出来了,删除一个左括号
if (par[0] == '(') {
remove(move(s), {par[1], par[0]}, 0, 0, res);
} else {
res.push_back(move(s));
}
}
};