为何相邻字符无法判断括号有效?

摘要:一、这题表面简单,但很容易想偏 今天这题是 LeetCode 20:有效的括号。 题目给你一个只包含下面六种字符的字符串: ( ) [ ] { } 你要判断这个字符串是不是“有效”。 有效的意思是: 左括号必须用相同类型的右括号闭合 左括号
一、这题表面简单,但很容易想偏 今天这题是 LeetCode 20:有效的括号。 题目给你一个只包含下面六种字符的字符串: ( ) [ ] { } 你要判断这个字符串是不是“有效”。 有效的意思是: 左括号必须用相同类型的右括号闭合 左括号必须按正确顺序闭合 每个右括号都要有对应的左括号 比如: 示例 1 s = "()" 输出: True 示例 2 s = "()[]{}" 输出: True 示例 3 s = "(]" 输出: False 这题一开始看着像“括号配对题”,好像不难。 但很多初学者第一次写,都会很自然地走到一个错误思路上: 看当前左括号后面是不是紧跟着对应的右括号 这个想法很自然,但其实不对。 二、为什么“只看相邻两个字符”会错 一开始最容易想到的写法,大概是这样: if s[i] == "(" and s[i + 1] != ")": return False 意思是: 如果当前是 (,那它后面必须立刻跟着 ) 但问题是,这题不是只判断“相邻两个字符能不能配对”,而是判断整个字符串的嵌套结构对不对。 比如这个字符串: s = "([])" 它其实是有效的。 因为: 最外层 ( 和 ) 配对 中间 [ 和 ] 配对 可如果你只看相邻字符: 第一个字符是 ( 它后面紧跟着的是 [,不是 ) 你就会误判成 False 可实际上它明明是对的。 所以这题最大的问题就在这里: 有些括号虽然匹配,但它们不一定挨在一起。 也就是说,这题不能只看 s[i] 和 s[i+1]。 三、这题真正考的是什么 这题真正想考的是: 每当你遇到一个右括号,它应该和“最近的那个还没匹配的左括号”配对。 注意这句话里的关键词: 最近的、还没匹配的左括号 这就是一个典型的“后进先出”问题。 也就是说: 后出现的左括号 要先被匹配掉 这正是 栈(stack) 的特点。 四、先别怕,栈没有那么玄乎 “栈”这个词听起来有点像数据结构课上的概念,但你可以先把它想得很简单: 它就像一摞盘子。 新放上去的盘子在最上面 你每次拿的时候,也只能先拿最上面的盘子 所以它的特点就是: 后放进去的,先拿出来 这就叫: 后进先出(LIFO,Last In First Out) 在 Python 里,我们通常直接用列表来模拟栈: stack = [] 入栈 stack.append("(") 出栈 stack.pop() 比如: stack = [] stack.append("(") stack.append("[") print(stack) 输出: ['(', '['] 然后: stack.pop() 会先弹出 [,因为它是最后放进去的。 这就很适合拿来处理括号问题。 五、这题的核心思路 我们从左到右遍历整个字符串。 遇到左括号 就先把它放进栈里。 比如: 遇到 (,入栈 遇到 [,入栈 遇到 {,入栈 遇到右括号 就去看栈顶的左括号能不能和它匹配。 比如: 遇到 ),栈顶必须是 ( 遇到 ],栈顶必须是 [ 遇到 },栈顶必须是 { 如果不能匹配,直接返回 False。 最后如果整个字符串遍历完了,栈也正好空了,说明所有括号都匹配成功,返回 True。 六、最适合初学者理解的写法 我先给出一版最容易看懂的代码: def is_valid(s): """ :type s: str :rtype: bool """ stack = [] for ch in s: if ch == '(' or ch == '[' or ch == '{': stack.append(ch) else: if not stack: return False top = stack.pop() if ch == ')' and top != '(': return False if ch == ']' and top != '[': return False if ch == '}' and top != '{': return False return len(stack) == 0 你先别急着背,下面我们慢慢拆开讲。
阅读全文