20. 有效的括号
给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
示例 1:
输入:s = “()”
输出:true
示例 2:
输入:s = “()[]{}”
输出:true
示例 3:
输入:s = “(]”
输出:false
示例 4:
输入:s = “([)]”
输出:false
示例 5:
输入:s = “{[]}”
输出:true
提示:
1 <= s.length <= 104
s 仅由括号 ‘()[]{}’ 组成
栈匹配
算法原理
- 栈先入后出特点恰好与本题括号排序特点一致,即若遇到左括号入栈,遇到右括号时将对应栈顶左括号出栈,则遍历完所有括号后 stack 仍然为空;
- 建立哈希表 dic 构建左右括号对应关系:key括号,value右括号;这样查询 2 个括号是否对应只需 O(1) 时间复杂度;建立栈 stack,遍历字符串 s 并按照算法流程一一判断。
算法流程
- 如果 c 是左括号,则入栈 push;否则通过哈希表判断括号对应关系,若 stack 栈顶出栈括号 stack.pop() 与当前遍历括号 c 不对应,则提前返回 false。
- 提前返回优点: 在迭代过程中,提前发现不符合的括号并且返回,提升算法效率。
- 解决边界问题:
- 栈 stack 为空: 此时 stack.pop() 操作会报错;因此,我们采用一个取巧方法,给 stack 赋初值 ?? ,并在哈希表 dic 中建立 key: ‘?’,value:’? 的对应关系予以配合。此时当 stack 为空且 c 为右括号时,可以正常提前返回 false;
- 字符串 s 以左括号结尾: 此情况下可以正常遍历完整个 s,但 stack 中遗留未出栈的左括号;因此,最后需返回 len(stack) == 1,以判断是否是有效的括号组合。
复杂度分析
- 时间复杂度 O(N):正确的括号组合需要遍历 1遍 s;
- 空间复杂度 O(N):哈希表和栈使用线性的空间大小。
class Solution:
def isValid(self, s: str) -> bool:
if len(s) % 2 == 1:
return False
d = {'{':'}','(':')','[':']','?': '?'}
stack =['?']
for c in s:
if c in d:
stack.append(c)
elif d[stack.pop()] != c:
return False
return len(stack)==1
678. 有效的括号字符串
给定一个只包含三种字符的字符串:( ,) 和 *,写一个函数来检验这个字符串是否为有效字符串。有效字符串具有如下规则:
- 任何左括号 ( 必须有相应的右括号 )。
- 任何右括号 ) 必须有相应的左括号 ( 。
- 左括号 ( 必须在对应的右括号之前 )。
-
∗
*
∗ 可以被视为单个右括号 ) ,或单个左括号 ( ,或一个空字符串。
一个空字符串也被视为有效字符串。
示例 1:
输入: “()”
输出: True
示例 2:
输入: “(*)”
输出: True
示例 3:
输入: “(*))”
输出: True
注意:
- 字符串大小将在 [1,100] 范围内。
解题思路:贪心算法
-
用一个变量delta维护当前左括号的数量,左括号+1、右括号-1:
-
如果任意时刻有delta < 0说明右括号比左括号多,直接失败
-
如果最终有delta == 0说明平衡,才成功。
-
这里由于 ∗ * ∗可以变成3种字符,如果用贪心法也是可以的:维护当前左括号的数量范围[L, R],遇到(则同时加1、遇到)则同时减1、遇到*则扩展范围(–L, ++R),同理:
-
如果任意时刻有R < 0,则失败
-
如果最后有L <= 0 <= R,才成功因为加1、减1的过程会保证[L, R]是连续整数区间,所以我们只需要维护区间的左右边界,而不需要维护该区间中的所有数字。
class Solution {
public:
bool checkValidString(string s) {
int min = 0, max = 0; // 维护当前左括号的数量范围:[min, max]
for (auto c : s) {
if (c == '(') {
++min;
++max;
} else if (c == ')') {
if (min > 0) min--;
if (max-- == 0) return false;// 左括号不够
} else {
if (min > 0) min--; // 可作为右括号,抵消
++max; // 可作为左括号
}
}
return min == 0;
}
};
class Solution:
def checkValidString(self, s: str) -> bool:
L,R=0,0
for ch in s:
if ch == '(':
L += 1
R += 1
elif ch == ')':
if L > 0:
L -= 1
if R > 0:
R -= 1
else:
return False
else:
if L > 0:
L -= 1
R += 1
return L==0
22. 括号生成
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例 1:
输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]
示例 2:
输入:n = 1
输出:["()"]
提示:
1 <= n <= 8
动态规划与回溯法
- 回溯法
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
ans = []
def backtrack(S, left, right):
if len(S) == 2 * n:
ans.append(''.join(S))
return
if left < n:
S.append('(')
backtrack(S, left+1, right)
S.pop()
if right < left:
S.append(')')
backtrack(S, left, right+1)
S.pop()
backtrack([], 0, 0)
return ans
- 动态规划
class Solution:
def generateParenthesis(self, n: int) -> List[str]:
res = []
cur_str = ''
def dfs(cur_str, left, right, n):
"""
:param cur_str: 从根结点到叶子结点的路径字符串
:param left: 左括号已经使用的个数
:param right: 右括号已经使用的个数
:return:
"""
if left == n and right == n:
res.append(cur_str)
return
if left < right:
return
if left < n:
dfs(cur_str + '(', left + 1, right, n)
if right < n:
dfs(cur_str + ')', left, right + 1, n)
dfs(cur_str, 0, 0, n)
return res
最长有效的括号(困难)
给你一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长有效(格式正确且连续)括号子串的长度。
示例 1:
输入:s = “(()”
输出:2
解释:最长有效括号子串是 “()”
示例 2:
输入:s = “)()())”
输出:4
解释:最长有效括号子串是 “()()”
示例 3:
输入:s = “”
输出:0
提示:
0 <= s.length <= 3 * 104
s[i] 为 ‘(’ 或 ‘)’
栈
- 对于遇到的每个 ‘(’ ,我们将它的下标放入栈中。
- 对于遇到的每个 ‘)’ ,我们先弹出栈顶元素表示匹配了当前右括号。
- 如果栈为空,说明当前的右括号为没有被匹配的右括号,我们将其下标放入栈中来更新我们之前提到的「最后一个没有被匹配的右括号的下标」。
- 如果栈不为空,当前右括号的下标减去栈顶元素即为「以该右括号为结尾的最长有效括号的长度」。
- 我们从前往后遍历字符串并更新答案即可。
class Solution:
def longestValidParentheses(self, s: str) -> int:
if not s:
return 0
res = 0
stack = [-1]
for i in range(len(s)):
if s[i] == "(":
stack.append(i)
else:
stack.pop()
if not stack:
stack.append(i)
else:
res = max(res,i - stack[-1])
return res
复杂度分析
-
时间复杂度: O(n),n是给定字符串的长度。我们只需要遍历字符串一次即可。
-
空间复杂度: O(n)。栈的大小在最坏情况下会达到 n,因此空间复杂度为O(n) 。