每日一题:LeetCode 20:有效的括号——栈与哈希映射详解

LeetCode 20「有效的括号」是一道经典的栈(Stack)算法题,主要考察栈的后进先出特性、哈希映射以及边界条件判断。

题目描述

给定一个只包含以下字符的字符串 s:

复制代码
'(', ')', '{', '}', '[', ']'

判断字符串中的括号是否有效。

有效的括号必须满足:左括号和右括号类型相同,并且按照正确的顺序闭合。

例如:

复制代码
s = "()[]{}"  // true
s = "([{}])"  // true
s = "(]"      // false
s = "([)]"    // false
s = "(()"     // false

其中 "([)]" 虽然左右括号数量相同,但闭合顺序错误,因此也是无效的。

为什么使用栈?

这道题最重要的特点是:最后出现的左括号,必须最先被匹配。 这点至关重要。

例如:

复制代码
([{}])

括号的匹配顺序是:

复制代码
{ 先与 } 匹配
[ 再与 ] 匹配
( 最后与 ) 匹配

这恰好符合栈的 后进先出(LIFO) 特性。

栈可以理解成一摞盘子,最后放进去的盘子,最先被拿出来。

在 JavaScript 中,可以直接使用数组模拟栈:

javascript 复制代码
const stack = []

stack.push("(")
stack.push("[")

console.log(stack)
// ["(", "["]

stack.pop()
// 返回 "[",并从数组中移除

console.log(stack)
// ["("]

这里:

  • push():将元素添加到栈顶。

  • pop():移除并返回栈顶元素。

  • stack.length:获取当前栈中元素数量。

因此,我们可以把遇到的左括号放入栈中。当遇到右括号时,就取出最近的左括号进行比较。

JavaScript 实现

javascript 复制代码
var isValid = function(s) {
  const stack = []

  const map = {
    ')': '(',
    ']': '[',
    '}': '{'
  }

  for (const ch of s) {
    if (map[ch]) {
      if (stack.pop() !== map[ch]) {
        return false
      }
    } else {
      stack.push(ch)
    }
  }

  return stack.length === 0
}

整段代码的核心思路就是:

遇到左括号入栈,遇到右括号出栈比较,最后检查栈是否为空。

哈希映射 map 的作用

代码中定义了一个普通对象:

复制代码
const map = {
  ')': '(',
  ']': '[',
  '}': '{'
}

它用来保存右括号和对应左括号的关系。

例如:

复制代码
map[')'] // "("
map[']'] // "["
map['}'] // "{"

这里的:

复制代码
map[ch]

属于 JavaScript 对象的动态属性访问。

假设:

复制代码
const ch = ')'

那么:

复制代码
map[ch]

就相当于:

复制代码
map[')']

结果为:

复制代码
"("

而如果:

复制代码
const ch = '('

由于 map 中没有 '(' 这个键:

复制代码
map['('] // undefined

因此我们可以通过:

复制代码
if (map[ch])

判断当前字符是不是右括号。

如果是右括号,就检查栈顶;如果不是,就把当前左括号放进栈里。

需要注意,这里的 map 是普通 JavaScript 对象,不是 new Map() 创建的集合类型。

for...of 是怎么遍历的?

代码:

复制代码
for (const ch of s) {

for...of 用来依次获取字符串中的字符。

例如:

复制代码
const s = "([])"

for (const ch of s) {
  console.log(ch)
}

输出:

复制代码
(
[
]
)

也就是说,每次循环时,ch 都表示当前遍历到的括号。

为什么需要 stack.pop()?

最关键的判断是:

复制代码
if (stack.pop() !== map[ch]) {
  return false
}

假设当前字符串为:

复制代码
s = "([])"

执行到右括号 ] 时,栈里保存的是:

复制代码
stack = ["(", "["]

此时:

复制代码
stack.pop()

返回:

复制代码
"["

而:

复制代码
map["]"]

结果同样是:

复制代码
"["

两者相等,说明匹配正确,可以继续。

如果遇到的是 ):

复制代码
stack.pop() // "["
map[")"]    // "("

两者不相等,就说明括号类型或闭合顺序错误,直接:

复制代码
return false

另外,如果遇到右括号时栈已经为空,pop() 会返回 undefined,同样会判断为无效。

用完整例子理解执行过程

以:

复制代码
s = "([{}])"

为例,逐个遍历字符:

当前字符 执行操作 操作后的栈
( 左括号入栈 ["("]
[ 左括号入栈 ["(", "["]
{ 左括号入栈 ["(", "[", "{"]
} 弹出 {,匹配成功 ["(", "["]
] 弹出 [,匹配成功 ["("]
) 弹出 (,匹配成功 []

所有括号都匹配成功,最终栈为空,因此结果为:

复制代码
true

反过来,如果字符串是:

复制代码
s = "([)]"

当遍历到 ) 时,栈顶还是 [。

复制代码
stack.pop() // "["
map[")"]    // "("

二者不相等,立即返回 false。

这也说明:栈不仅能判断括号类型是否匹配,还能保证括号闭合顺序正确。

为什么最后还要判断 stack.length === 0?

代码最后:

复制代码
return stack.length === 0

这是为了防止字符串中还有没有闭合的左括号。

例如:

复制代码
s = "((("

遍历结束之后:

复制代码
stack = ["(", "(", "("]

因为始终没有右括号,所以之前的匹配判断不会报错。

但是:

复制代码
stack.length === 0
// false

说明还有左括号没有匹配成功。

如果所有括号都正确匹配,栈最终一定为空:

复制代码
stack = []

stack.length === 0
// true

因此,判断有效括号需要同时满足两个条件:

  • 每次遇到右括号,都能匹配最近的左括号。

  • 所有字符遍历结束之后,栈必须为空。

时间复杂度与空间复杂度

时间复杂度:O(n)

代码只需要遍历一次字符串。每个字符最多入栈一次、出栈一次,因此整体时间复杂度是 O(n)。

空间复杂度:O(n)

最坏情况下,字符串全是左括号:

复制代码
s = "((((("

所有字符都会进入栈中,因此需要最多 O(n) 的额外空间。

总结

这道题的核心是使用栈来维护尚未匹配的左括号。

javascript 复制代码
var isValid = function(s) {
  const stack = []
  const map = {
    ')': '(',
    ']': '[',
    '}': '{'
  }

  for (const ch of s) {
    if (map[ch]) {
      if (stack.pop() !== map[ch]) return false
    } else {
      stack.push(ch)
    }
  }

  return stack.length === 0
}

整个算法可以概括为:

复制代码
遍历字符串
     ↓
遇到左括号
     ↓
push() 入栈
     ↓
遇到右括号
     ↓
pop() 取出最近的左括号
     ↓
检查是否匹配
     ↓
不匹配 → false
     ↓
全部遍历结束
     ↓
栈为空 → true
栈不为空 → false

一句话记住:左括号入栈,右括号匹配栈顶,最后栈必须为空。

这也是栈解决括号匹配问题最经典的应用。

相关推荐
hetao17338371 小时前
2026-10-07 hetao1733837 的刷题记录
c++·算法
Nil2082 小时前
leetcode 279完全平方数
算法·leetcode·职场和发展
longlongzihan2 小时前
前缀积与后缀积 —— LeetCode 238. 除了自身以外数组的乘积
开发语言·c++·leetcode
All for pursuit.2 小时前
【动态规划-1】121.买卖股票的最佳时机
数据结构·c++·算法·leetcode·动态规划
名不经传的养虾人2 小时前
从0到1:企业级AI项目迭代日记 Vol.117|学会忘记,学会证明,学会设边界
大数据·人工智能·算法·企业ai·多agent协作
weixin_307779132 小时前
C++代码实现MATLAB中的cvpartition函数功能
开发语言·c++·算法·matlab
(Charon)2 小时前
【C++面试】线程同步机制:互斥锁、条件变量、原子操作与读写锁
开发语言·c++·算法·面试
七牛云行业应用2 小时前
刚刚!GPT-6.1 Sol Ultrafast 上线:速度、价格、API 接入全指南
算法
Omics Pro2 小时前
研究证实AI虚拟细胞可用于药物靶点发现
数据库·人工智能·算法·机器学习·自然语言处理