给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
示例 1:
输入: s = "()"
输出: true
示例 2:
输入: s = "()[]{}"
输出: true
示例 3:
输入: s = "(]"
输出: false
示例 4:
输入: s = "([])"
输出: true
示例 5:
输入: s = "([)]"
输出: false
提示:
1 <= s.length <= 104s仅由括号'()[]{}'组成
1. 生活案例:消消乐或吃掉火锅食材
想象你在玩一个括号版的“消消乐”或者吃火锅:
-
规则:左括号
(、[、{是你放进锅里的生食材。右括号)、]、}是对应的调料蘸料。 -
约束:
- 你必须按照“后放进去的先吃掉”的顺序。比如你最后放了金针菇(左括号),你必须先拿金针菇的蘸料(对应的右括号)把它消掉。
- 如果你拿错了蘸料(比如拿了肥牛的蘸料去配金针菇),或者锅里没食材了你却拿了蘸料,这顿饭就“无效”了。
- 最后吃完时,锅里不能剩下任何没配对的生食材。
2. 代码实现与详细注释
这是你图片中的代码,我为你添加了详细的中文注释。这段代码用了一个很聪明的技巧:遇到左括号,就在栈里存它对应的右括号,这样后面比对时会非常直接。
JavaScript
/**
* @param {string} s
* @return {boolean}
*/
var isValid = function (s) {
// 1. 建立一个【配对手册】:看到生食材(左括号),查一下它对应的蘸料(右括号)是什么
let mapping = {
'{': '}',
'[': ']',
'(': ')'
}
let res = []; // 【火锅/栈】:存放等待配对的“期望右括号”
for (let char of s) {
// 如果在手册里能找到这个字符,说明它是“左括号”
if (mapping[char]) {
// 【技巧】:我们不存左括号,而是直接把“它期待的右括号”扔进栈里
// 比如看到 '(',我们就往栈里扔一个 ')'
res.push(mapping[char]);
} else {
// 否则,它就是“右括号”
// 逻辑:把它和栈顶(最后放进去的那个期望)比一下
// 如果【栈空了】或者【对不上号】,说明顺序乱了,直接判死刑
if (char !== res.pop()) {
return false;
}
}
}
// 最后检查:如果栈空了,说明大家都消掉了,有效!
// 如果还剩下东西,说明有人没配上对
return res.length === 0;
};
3. 核心原理解析
为什么这个“提前存入右括号”的方法很妙?
通常的做法是存入左括号,匹配时判断 if(left == '(' && right == ')')。
但你代码里的写法更简洁:
- 遇到
[栈里压入]。 - 下一步如果遇到右括号,直接看它是不是等于栈顶弹出的那个元素即可。
- 代码逻辑从“判断是否配对”简化成了“判断是否相等”。
错误情况拆解:
-
类型不匹配:
([)]- 压入
),再压入]。 - 遇到
),但栈顶弹出的是],]不等于),返回false。
- 压入
-
左边多了:
(()- 遍历完后栈里还剩一个
),res.length !== 0,返回false。
- 遍历完后栈里还剩一个
-
右边多了:
())- 第二个
)匹配完后栈空了,第三个)进来时res.pop()是undefined,返回false。
- 第二个
复杂度分析
- 时间复杂度:。只需要把字符串从头到尾摸一遍。
- 空间复杂度:。最坏的情况是全是左括号
(((((,全堆在栈里。