python 判断括号字符串是否有效(栈-简单)含源码(十一)
问题说明(含示例)
问题描述:给定一个只包含括号((、)、{、}、[、])的字符串 s,判断其是否为有效字符串。有效字符串需满足:
- 左括号必须用相同类型的右括号闭合;
- 左括号必须以正确的顺序闭合(嵌套关系正确);
- 每个右括号都有对应的相同类型左括号。
示例:
- 输入:
s = "()"→ 输出:true(单个括号匹配)。 - 输入:
s = "()[]{}"→ 输出:true(多个括号依次匹配)。 - 输入:
s = "(]"→ 输出:false(类型不匹配,(对应]错误)。 - 输入:
s = "([])"→ 输出:true(嵌套关系正确,[被()正确包含)。 - 输入:
s = "([)]"→ 输出:false(顺序错误,(应匹配),但中间被[打断)。
解题关键
利用栈(Stack) 的 “先进后出(LIFO)” 特性处理括号的嵌套关系,核心思路:
- 栈的作用:存储未匹配的左括号,遇到右括号时,通过栈顶元素判断是否匹配。
- 匹配规则:用字典建立 “右括号→左括号” 的映射(如
')'→'('),便于快速查找对应关系。 - 异常情况:
- 右括号无对应左括号(栈为空时遇到右括号);
- 右括号与栈顶左括号类型不匹配;
- 遍历结束后仍有未匹配的左括号(栈不为空)。
对应代码
class Solution:
def isValid(self, s: str) -> bool:
# 优化:长度为奇数时,必然无效(括号成对出现)
if len(s) % 2 != 0: ## %表示取余数
return False
# 建立右括号到左括号的映射,便于快速匹配
parentheses_map = {
')': '(',
'}': '{',
']': '['
}
stack = [] # 栈:存储未匹配的左括号
for char in s:
if char in parentheses_map: ## 这里是与字典里的快速匹配
# 当前字符是右括号:需与栈顶左括号匹配
# 若栈为空(无左括号)或栈顶不匹配,直接返回False
if not stack or stack[-1] != parentheses_map[char]:
return False
# 匹配成功,弹出栈顶左括号
stack.pop()
else:
# 当前字符是左括号:压入栈中等待匹配
stack.append(char)
# 遍历结束后,栈必须为空(所有左括号都已匹配)
return len(stack) == 0
对应的基础知识
实现该算法需掌握以下基础概念与操作:
-
栈的基本操作
- 用列表模拟栈:
stack.append(char)实现 “压栈”(左括号入栈),stack.pop()实现 “弹栈”(匹配成功后移除左括号); stack[-1]访问栈顶元素(获取最近未匹配的左括号)。
- 用列表模拟栈:
-
字典的映射作用
- 字典
parentheses_map存储右括号到左括号的对应关系(如')'对应'('),通过char in parentheses_map快速判断字符是否为右括号,通过parentheses_map[char]直接获取应匹配的左括号,时间复杂度O(1)。
- 字典
-
条件判断与异常处理
- 长度为奇数时直接返回
false(括号必须成对出现,优化判断); - 遇到右括号时,先检查栈是否为空(无对应左括号)或栈顶不匹配,及时返回
false。
- 长度为奇数时直接返回
-
遍历与最终校验
- 遍历字符串的每个字符,处理左括号入栈、右括号匹配逻辑;
- 遍历结束后,栈必须为空(所有左括号都被匹配),否则存在未闭合的左括号,返回
false。
对应的进阶知识
该问题的解决涉及栈的应用与复杂度分析的深层逻辑:
-
时间与空间复杂度
- 时间复杂度:
O(n)(n为字符串长度),每个字符仅遍历一次,栈的append、pop、[-1]操作均为O(1); - 空间复杂度:
O(n),最坏情况下(如全为左括号'(((('),栈需存储所有左括号,占用O(n)空间。
- 时间复杂度:
-
栈与嵌套结构的适配性
- 括号的嵌套关系(如
([]))本质是 “后出现的左括号先匹配”([比(晚出现,先被]匹配),与栈的 “先进后出” 特性完全吻合,因此栈是处理嵌套结构的最优数据结构。
- 括号的嵌套关系(如
-
边界情况的全覆盖
- 空字符串(
s = ""):长度为 0(偶数),栈为空,返回true(符合有效定义); - 单括号(
s = "("或s = ")"):长度为 1(奇数),直接返回false; - 交叉嵌套(
s = "([)]"):处理)时,栈顶为[,不匹配,返回false,正确识别顺序错误。
- 空字符串(
-
与其他数据结构的对比
- 若用列表模拟队列(先进先出),无法处理嵌套关系(如
([])中[无法优先匹配]); - 若用哈希表直接计数(如统计左右括号数量),无法判断顺序是否正确(如
([)]左右括号数量相等但无效)。
- 若用列表模拟队列(先进先出),无法处理嵌套关系(如
编程思维与启示
-
“特性匹配” 的数据结构选择:针对嵌套结构,优先选择栈(LIFO),因其特性天然适配 “后入先出” 的匹配逻辑,这是解决括号、表达式解析等问题的通用思路。
-
“映射表” 的效率提升:用字典存储对应关系,避免多重
if-else判断(如if char == ')' and stack[-1] == '('),使代码更简洁,且查找效率从O(1)级提升。 -
“提前剪枝” 的优化意识:先判断字符串长度是否为奇数,直接排除不可能有效的情况,减少后续无效计算,体现 “优化从边界开始” 的思维。
-
“状态校验” 的完整性:遍历结束后检查栈是否为空,确保所有左括号都被匹配,避免遗漏 “有剩余左括号” 的无效情况,体现对 “全局状态” 的把控。
栈的基础知识(小白能懂版)
一、栈是什么?—— 生活中的 “栈”
栈是一种特殊的容器,它的操作有严格的 “顺序限制”:只能从容器的 “顶部” 放入或取出元素,就像生活中的这些场景:
- 叠盘子:洗完的盘子只能放在最上面(不能插在中间),取盘子时也只能从最上面拿;
- 堆书:新书只能放在书堆的最上面,取书时也先拿最上面的;
- 浏览器的 “后退” 功能:最近访问的页面在 “最上面”,后退时先回到上一个访问的页面。
这些场景的共同特点是:“先进后出”(First In Last Out,简称 FILO)—— 先放进去的元素,最后才能取出来。
二、栈的 3 个核心操作(小白必懂)
栈的操作非常简单,核心只有 3 个(用 Python 的列表模拟栈时,对应这些方法):
| 操作名称 | 作用 | Python 列表实现 | 例子(栈:[1,2,3],顶部是 3) |
|---|---|---|---|
| 压栈(Push) | 往栈的顶部添加一个元素 | stack.append(元素) |
压栈 4 → 栈变为 [1,2,3,4](顶部是 4) |
| 弹栈(Pop) | 从栈的顶部移除并返回一个元素 | stack.pop() |
弹栈 → 返回 3,栈变为 [1,2](顶部是 2) |
| 查看栈顶(Peek) | 查看栈顶部的元素(不移除) | stack[-1](列表最后一个元素) |
查看栈顶 → 返回 3,栈还是 [1,2,3] |
注意:栈没有 “从中间插入” 或 “从中间删除” 的操作,只能操作顶部元素,这是它和列表的核心区别。
三、为什么括号匹配问题需要用栈?
回到括号匹配问题,我们需要处理嵌套关系,比如 ([]) 这种情况:
- 先出现
(,然后出现[; - 匹配时,
[要先被]匹配(后出现的左括号先匹配),再轮到(被)匹配。
这完全符合栈的 “先进后出” 特性:后出现的左括号([)先被处理(弹出栈),先出现的左括号(()后被处理。
四、用栈解决括号匹配问题的步骤(小白能看懂的拆解)
以示例 s = "([])" 为例,一步步看栈的作用:
步骤 1:初始化栈和括号映射表
- 栈
stack = [](空栈,等待存放左括号); - 映射表
parentheses_map = {')':'(', '}':'{', ']':'['}(右括号对应左括号,方便快速查找)。
步骤 2:遍历字符串的每个字符,用栈处理
-
第一个字符
'('(左括号):- 左括号需要等待匹配,所以压栈 →
stack = ['('](栈顶是'(')。
- 左括号需要等待匹配,所以压栈 →
-
第二个字符
'['(左括号):- 同样需要等待匹配,压栈 →
stack = ['(', '['](栈顶是'[')。
- 同样需要等待匹配,压栈 →
-
第三个字符
']'(右括号):- 右括号需要找对应的左括号,通过映射表知道
']'对应'['; - 查看栈顶元素 → 正好是
'[',匹配成功!弹栈(移除栈顶的'[')→stack = ['(']。
- 右括号需要找对应的左括号,通过映射表知道
-
第四个字符
')'(右括号):- 映射表显示
')'对应'('; - 查看栈顶元素 → 是
'(',匹配成功!弹栈 →stack = [](栈空)。
- 映射表显示
步骤 3:判断最终结果
遍历结束后,栈是空的(stack = []),说明所有左括号都找到了匹配的右括号 → 返回 true。
五、栈如何处理 “无效情况”?
再看无效示例 s = "([)]",看看栈如何发现问题:
- 字符
'('→ 压栈 →stack = ['(']; - 字符
'['→ 压栈 →stack = ['(', '[']; - 字符
')'→ 右括号,对应左括号是'(';- 查看栈顶 → 是
'['(不是'('),不匹配 → 直接返回false(无效)。
- 查看栈顶 → 是
通过栈顶的实时检查,能立刻发现 “顺序错误” 的无效情况。
六、栈的其他常见应用(帮助理解)
除了括号匹配,栈还在很多场景中发挥作用:
- 函数调用:比如
A()调用B(),B()调用C(),执行完C()要先返回B(),再返回A(),用栈记录函数调用顺序; - 浏览器后退:访问页面 A→B→C,后退时先回到 B,再回到 A,栈记录访问历史;
- 表达式计算:比如
(1+2)*(3-4),用栈处理括号和运算符的优先级。
总结:栈的核心要点
- 特性:先进后出(FILO),只能操作顶部元素;
- 核心操作:压栈(添加)、弹栈(移除)、查看栈顶;
- 适合场景:处理嵌套关系、需要 “回溯”(回到上一步)的问题;
- 在括号匹配中:用栈存储左括号,遇到右括号时通过栈顶判断是否匹配,高效解决嵌套和顺序问题。
更多推荐
所有评论(0)