|
1 | | -# 有效括号系列 |
2 | | - |
3 | | -**[20.有效括号](#20.有效括号)** |
4 | | - |
5 | | -- [方法 1:栈](#方法-1:栈) |
6 | | -- [方法 2:递归](#方法-2:递归) |
7 | | -- [方法 3:正则](#方法-3:正则) |
8 | | - |
9 | | -**[32.最长有效括号](#32.最长有效括号)** |
10 | | - |
11 | | -- [方法 1:滑动窗口](#方法-1:滑动窗口) |
12 | | -- [方法 2:动态规划](#方法-2:动态规划) |
13 | | -- [方法 3:栈](#方法-3:栈) |
14 | | - |
15 | | -# 20.有效括号 |
16 | | - |
17 | | -https://leetcode-cn.com/problems/valid-parentheses/ |
18 | | - |
19 | | -## 题目描述 |
20 | | - |
21 | | -``` |
22 | | -给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。 |
23 | | -
|
24 | | -有效字符串需满足: |
25 | | -
|
26 | | -左括号必须用相同类型的右括号闭合。 |
27 | | -左括号必须以正确的顺序闭合。 |
28 | | -注意空字符串可被认为是有效字符串。 |
29 | | -
|
30 | | -示例 1: |
31 | | -
|
32 | | -输入: "()" |
33 | | -输出: true |
34 | | -示例 2: |
35 | | -
|
36 | | -输入: "()[]{}" |
37 | | -输出: true |
38 | | -示例 3: |
39 | | -
|
40 | | -输入: "(]" |
41 | | -输出: false |
42 | | -示例 4: |
43 | | -
|
44 | | -输入: "([)]" |
45 | | -输出: false |
46 | | -示例 5: |
47 | | -
|
48 | | -输入: "{[]}" |
49 | | -输出: true |
50 | | -
|
51 | | -来源:力扣(LeetCode) |
52 | | -链接:https://leetcode-cn.com/problems/valid-parentheses |
53 | | -著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 |
54 | | -``` |
55 | | - |
56 | | -## 方法 1:栈 |
57 | | - |
58 | | -### 思路 |
59 | | - |
60 | | -- 遇到左括号就入栈。 |
61 | | -- 遇到右括号就从栈中弹出一个元素,判断两者是否是匹配的一对括号。 |
62 | | - |
63 | | -### 代码 |
64 | | - |
65 | | -JavaScript Code |
66 | | - |
67 | | -```js |
68 | | -/** |
69 | | - * @param {string} s |
70 | | - * @return {boolean} |
71 | | - */ |
72 | | -var isValid = function (s) { |
73 | | - const dict = { |
74 | | - '}': '{', |
75 | | - ')': '(', |
76 | | - ']': '[', |
77 | | - }; |
78 | | - const stack = []; |
79 | | - |
80 | | - for (let c of s) { |
81 | | - if (c in dict) { |
82 | | - if (stack.pop() !== dict[c]) { |
83 | | - return false; |
84 | | - } |
85 | | - } else { |
86 | | - stack.push(c); |
87 | | - } |
88 | | - } |
89 | | - return stack.length === 0; |
90 | | -}; |
91 | | -``` |
92 | | - |
93 | | -### 复杂度分析 |
94 | | - |
95 | | -- 时间复杂度:O(n),n 为字符串的长度。 |
96 | | -- 空间复杂度:O(n),n 为字符串的长度。 |
97 | | - |
98 | | -## 方法 2:递归 |
99 | | - |
100 | | -### 思路 |
101 | | - |
102 | | -还记得[产品经理法](https://github.com/leetcode-pp/91alg-1/issues/32#issuecomment-643620727)么?假设我们现在已经有了一个 `isValid` 方法,可以验证一个字符串 s 是否由有效括号组成。 |
103 | | - |
104 | | -**那么子问题是什么?** |
105 | | - |
106 | | -如果 s 的第一个字符是左括号的话,那我们应该可以找到它对应右括号的下标 index,如果找不到说明就不是有效括号啦。 |
107 | | - |
108 | | -如果找到了,那么现在问题就变成了: |
109 | | - |
110 | | -- (0, index) 中间这段字符串是否包含有效括号? |
111 | | -- (index, s.length) 这段字符串是否包含有效括号? |
112 | | - |
113 | | -> 开括号表示不包括边界 |
114 | | -
|
115 | | -**大问题和小问题的关系是什么?** |
116 | | - |
117 | | -显然必须两个小问题的结果都是 true 才行啦,也就是: |
118 | | - |
119 | | -`isValid(s) = isValid(1, index - 1) && isValid(index + 1, s.length)` |
120 | | - |
121 | | -**递归出口在哪里?** |
122 | | - |
123 | | -- 如果字符串第一个字符不是左括号,`return false` |
124 | | -- 如果字符串为空,`return true` |
125 | | -- 还有一个,如果字符串长度是奇数的话,我们也可以提前 `return false`。 |
126 | | - |
127 | | -**关于如何找到对应的右括号下标** |
128 | | - |
129 | | -用一个 `counter` 来记录当前遇到的括号,比如,我们要找的是 '(' 的对应右括号 ')',那么在遍历字符串时: |
130 | | - |
131 | | -- 遇到 '(' 时 `counter++` |
132 | | -- 遇到 ')' 时 `counter--` |
133 | | -- 当 `counter === 0` 时说明我们找到了 |
134 | | -- 如果遍历结束后 `counter > 0` 说明找不到匹配的右括号 |
135 | | - |
136 | | -### 代码 |
137 | | - |
138 | | -JavaScript Code |
139 | | - |
140 | | -```js |
141 | | -const dict = { |
142 | | - '(': ')', |
143 | | - '{': '}', |
144 | | - '[': ']', |
145 | | -}; |
146 | | - |
147 | | -const matchClose = (s, start, end, open, close) => { |
148 | | - let count = 0; |
149 | | - for (let i = start; i <= end; i++) { |
150 | | - if (s[i] === open) count++; |
151 | | - if (s[i] === close) count--; |
152 | | - if (count === 0) return i; |
153 | | - } |
154 | | - return -1; |
155 | | -}; |
156 | | - |
157 | | -/** |
158 | | - * @param {string} s |
159 | | - * @return {boolean} |
160 | | - */ |
161 | | -const isValid = function (s) { |
162 | | - if (!s) return true; |
163 | | - if (s.length % 2 === 1) return false; |
164 | | - if (!(s[0] in dict)) return false; |
165 | | - |
166 | | - const closeIndex = matchClose(s, 0, s.length - 1, s[0], dict[s[0]]); |
167 | | - if (closeIndex === -1) return false; |
168 | | - return ( |
169 | | - isValid(s.slice(1, closeIndex)) && |
170 | | - isValid(s.slice(closeIndex + 1, s.length)) |
171 | | - ); |
172 | | -}; |
173 | | -``` |
174 | | - |
175 | | -### 复杂度分析 |
176 | | - |
177 | | -- 时间复杂度:O(n _ 2(n/2),递归的时间复杂度是 O(2^(n/2)),n 为字符串的长度,树的最大深度应该是 n/2 吧。每次递归中 `matchClose` 寻找匹配的闭合括号的时间复杂度大概是 O(n) 吧,所以时间复杂度是 O(n _ 2(n/2) ??? 我凌乱了 |
178 | | -- 空间复杂度:O(n),n 为字符串的长度,调用栈的最大深度是 n/2。 |
179 | | - |
180 | | -## 方法 3:正则 |
181 | | - |
182 | | -### 思路 |
183 | | - |
184 | | -不断地把 `()` `{}` `[]` 替换成空字符,直到: |
185 | | - |
186 | | -- s 变成了空字符,那么结果就是 `true`,或者, |
187 | | -- s 长度不为空,但是 s 中已经没有 `()`,`{}` 或者 `[]` 括号对了,那么结果就是 `false`。 |
188 | | - |
189 | | -### 代码 |
190 | | - |
191 | | -JavaScript Code |
192 | | - |
193 | | -```js |
194 | | -/** |
195 | | - * @param {string} s |
196 | | - * @return {boolean} |
197 | | - */ |
198 | | -const isValid = function (s) { |
199 | | - const reg = /\[\]|\(\)|\{\}/; |
200 | | - while (reg.test(s)) { |
201 | | - s = s.replace(reg, ''); |
202 | | - } |
203 | | - return s.length === 0; |
204 | | -}; |
205 | | -``` |
206 | | - |
207 | | -### 复杂度分析 |
208 | | - |
209 | | -- 时间复杂度:O(n),n 为字符串的长度。最坏的情况下,每次循环只能替换一对括号,比如 "(((((())))))",需要循环 n/2 次。 |
210 | | -- 空间复杂度:O(1)。 |
211 | | - |
212 | 1 | # 32.最长有效括号 |
213 | 2 |
|
214 | 3 | https://leetcode-cn.com/problems/longest-valid-parentheses/ |
215 | 4 |
|
| 5 | +- [32.最长有效括号](#32最长有效括号) |
| 6 | + - [题目描述](#题目描述) |
| 7 | + - [方法 1:滑动窗口](#方法-1滑动窗口) |
| 8 | + - [思路](#思路) |
| 9 | + - [图解](#图解) |
| 10 | + - [代码](#代码) |
| 11 | + - [复杂度分析](#复杂度分析) |
| 12 | + - [方法 2:动态规划](#方法-2动态规划) |
| 13 | + - [思路](#思路-1) |
| 14 | + - [图解](#图解-1) |
| 15 | + - [代码](#代码-1) |
| 16 | + - [复杂度分析](#复杂度分析-1) |
| 17 | + - [方法 3:栈](#方法-3栈) |
| 18 | + - [思路](#思路-2) |
| 19 | + - [代码](#代码-2) |
| 20 | + - [复杂度分析](#复杂度分析-2) |
| 21 | + |
216 | 22 | ## 题目描述 |
217 | 23 |
|
218 | 24 | ``` |
@@ -302,8 +108,8 @@ var longestValidParentheses = function (s) { |
302 | 108 |
|
303 | 109 | ### 复杂度分析 |
304 | 110 |
|
305 | | -- 时间复杂度:O(n),n 为字符串的长度。 |
306 | | -- 空间复杂度:O(n),n 为字符串的长度。 |
| 111 | +- 时间复杂度:$O(n)$,n 为字符串的长度。 |
| 112 | +- 空间复杂度:$O(n)$,n 为字符串的长度。 |
307 | 113 |
|
308 | 114 | ## 方法 2:动态规划 |
309 | 115 |
|
@@ -363,16 +169,16 @@ var longestValidParentheses = function (s) { |
363 | 169 |
|
364 | 170 | ### 复杂度分析 |
365 | 171 |
|
366 | | -- 时间复杂度:O(n),n 为字符串的长度。 |
367 | | -- 空间复杂度:O(n),n 为字符串的长度。 |
| 172 | +- 时间复杂度:$O(n)$,n 为字符串的长度。 |
| 173 | +- 空间复杂度:$O(n)$,n 为字符串的长度。 |
368 | 174 |
|
369 | 175 | ## 方法 3:栈 |
370 | 176 |
|
371 | 177 | ### 思路 |
372 | 178 |
|
373 | 179 | 用一个栈来检查括号的有效性,用一个数组 `valid` 来记录匹配括号对的位置。 |
374 | 180 |
|
375 | | -- 栈的用法跟[20.有效括号](#方法-1:栈)里的一样,不过入栈的不是 `(`,而是它们的下标。 |
| 181 | +- 栈的用法跟[20.有效括号](./38.valid-parentheses.md)里的一样,不过入栈的不是 `(`,而是它们的下标。 |
376 | 182 | - 在遍历过程中,如果碰到 `)`,就从栈中弹出一个元素,这个元素就是 `)` 对应的 `(` 的下标。 |
377 | 183 | - 接着我们在 `valid` 中这两个下标对应的位置做个标识 `1`,说明这里找到了一对有效括号。 |
378 | 184 | - 等遍历结束之后,在 `valid` 中找到连续最长的 `1` 序列。 |
@@ -414,9 +220,5 @@ var longestValidParentheses = function (s) { |
414 | 220 |
|
415 | 221 | ### 复杂度分析 |
416 | 222 |
|
417 | | -- 时间复杂度:O(n),n 为字符串的长度。 |
418 | | -- 空间复杂度:O(n),n 为字符串的长度。 |
419 | | - |
420 | | -## 官方题解 |
421 | | - |
422 | | -https://github.com/azl397985856/leetcode/blob/master/problems/32.longest-valid-parentheses.md |
| 223 | +- 时间复杂度:$O(n)$,n 为字符串的长度。 |
| 224 | +- 空间复杂度:$O(n)$,n 为字符串的长度。 |
0 commit comments