【LeetCode 热题 100】20. 有效的括号

Problem: 20. 有效的括号

给定一个只包括 '(',')','{','}','','' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。

左括号必须以正确的顺序闭合。

每个右括号都有一个对应的相同类型的左括号。

文章目录

整体思路

这段代码旨在解决一个非常经典的计算机科学问题:有效的括号 (Valid Parentheses) 。问题要求判断一个只包含 ( ) { } [ ] 六种括号的字符串是否是"有效"的。有效性有两个标准:1. 左括号必须用相同类型的右括号闭合。2. 左括号必须以正确的顺序闭合。

该算法采用的是解决此类问题的标准且最高效的方法:栈 (Stack)。其核心逻辑思想是,利用栈"后进先出"(LIFO)的特性来完美匹配括号的嵌套结构。

  1. 预处理与数据结构选择

    • 快速失败 :代码首先进行了一个简单的优化判断 s.length() % 2 != 0。一个有效的括号字符串,其长度必然是偶数。如果长度为奇数,可以直接判定为无效,提前返回 false
    • 栈的选择 :算法的核心是一个栈(在Java中通过 Deque 接口和 ArrayDeque 实现类来创建)。这个栈用来存储所有遇到的、但尚未匹配到相应右括号的左括号
    • 匹配关系 :为了方便地查找一个右括号对应的左括号,代码使用了一个 HashMapMap的键是右括号,值是对应的左括号。这使得匹配查询操作的效率为 O(1)。
  2. 单次遍历与匹配逻辑

    • 算法通过一次 for 循环遍历输入字符串 s 中的每一个字符 c
    • 遇到左括号 :如果当前字符 c 是一个左括号(通过 !mp.containsKey(c) 判断,因为只有右括号是 Map 的键),就将其压入栈中,等待后续的右括号来匹配。
    • 遇到右括号 :如果当前字符 c 是一个右括号,则进入匹配逻辑:
      a. 栈空检查 :首先检查栈是否为空。如果为空,说明遇到了一个右括号,但之前没有任何未匹配的左括号,这显然是无效的(例如 ")""{})")。
      b. 类型匹配 :如果栈不为空,就从栈顶弹出一个元素(st.pop())。这个弹出的元素应该是最近遇到的、未闭合的左括号。然后,将这个弹出的左括号与当前右括号 c 对应的正确左括号(mp.get(c))进行比较。如果它们不相等,说明括号类型不匹配(例如 "(]"),字符串无效。
  3. 最终校验

    • 当遍历完整个字符串后,如果字符串是完全有效的,那么所有的左括号都应该找到了对应的右括号并被从栈中弹出。因此,最后需要检查栈是否为空 (st.isEmpty())。
    • 如果栈为空,说明所有括号都完美匹配,字符串有效。
    • 如果栈不为空,说明有剩余的左括号没有被闭合(例如 "(""{[()"),字符串无效。

完整代码

java 复制代码
class Solution {
    /**
     * 判断一个只包含 '(', ')', '{', '}', '[' 和 ']' 的字符串是否有效。
     * @param s 输入的括号字符串
     * @return 如果字符串有效则返回 true,否则返回 false
     */
    public boolean isValid(String s) {
        // 快速失败优化:有效的括号字符串长度必为偶数。
        if (s.length() % 2 != 0) {
            return false;
        }

        // 创建一个 Map 存储括号的匹配关系(key:右括号, value:左括号)。
        // 使用实例初始化块(双大括号语法)来方便地初始化 Map。
        Map<Character, Character> mp = new HashMap<>() {
            {
                put(')', '(');
                put('}', '{');
                put(']', '[');
            }
        };

        // 使用 Deque 作为栈,这是 Java 中推荐的栈实现方式。
        // st 用于存储所有遇到的、但尚未匹配的左括号。
        Deque<Character> st = new ArrayDeque<>();
        
        // 遍历字符串中的每一个字符
        for (char c : s.toCharArray()) {
            // 如果当前字符 c 是一个左括号(即它不是 Map 的键)
            if (!mp.containsKey(c)) {
                // 将左括号压入栈中
                st.push(c);
            } 
            // 如果当前字符 c 是一个右括号
            else if (st.isEmpty() || st.pop() != mp.get(c)) {
                // 检查两种无效情况:
                // 1. st.isEmpty(): 栈中没有左括号来匹配当前的右括号 (例如 "())")
                // 2. st.pop() != mp.get(c): 栈顶的左括号与当前右括号类型不匹配 (例如 "(]")
                // 如果任一情况为真,则字符串无效。
                return false;
            }
        }
        
        // 遍历完所有字符后,如果栈为空,说明所有括号都完美匹配。
        // 如果栈不为空,说明有未闭合的左括号。
        return st.isEmpty();
    }
}

时空复杂度

时间复杂度:O(N)

  1. 字符串遍历 :算法的核心是一个 for 循环,它遍历输入字符串 s 的所有字符一次。如果字符串的长度为 N,那么循环将执行 N 次。
  2. 循环内部操作
    • mp.containsKey(): HashMap 的查找操作,平均时间复杂度为 O(1)
    • st.push(): ArrayDeque 作为栈的压入操作,均摊时间复杂度为 O(1)
    • st.isEmpty(): 检查是否为空,时间复杂度为 O(1)
    • st.pop(): ArrayDeque 作为栈的弹出操作,均摊时间复杂度为 O(1)
    • mp.get(): HashMap 的获取操作,平均时间复杂度为 O(1)
  3. 综合分析
    • 整个算法由 N 次 O(1) 的操作组成。因此,总的时间复杂度是线性的,即 O(N)

空间复杂度:O(N)

  1. 主要存储开销 :算法的额外空间主要由 MapDeque (栈) 占用。
    • Map<Character, Character> mp: 这个 Map 存储了固定数量的键值对(3对)。其大小与输入字符串的长度 N 无关,因此它占用的空间是常数级别的,即 O(1)
    • Deque<Character> st: 这个栈用于存储未匹配的左括号。在最坏的情况下,输入字符串可能全是左括号,例如 "((((..."。在这种情况下,栈的大小会增长到与输入字符串长度 N 相同。

综合分析

算法所需的额外空间主要由栈 st 的大小决定。因此,在最坏情况下,空间复杂度为 O(N)

参考灵神

相关推荐
lueluelue475 小时前
LeetCode:链表
算法·leetcode·链表
爱丶狸5 小时前
Grafana_Zabbix_ImageRenderer_部署与前端操作手册
linux·前端·zabbix·grafana·kylin
k4m7v2pz7 小时前
把 Bevy 0.14 游戏移植到 R36S 掌机:一场与“无窗口系统“的搏斗
linux·rust·bevy·r36s·rk3326·开源掌机
橘子汽水1688 小时前
Leetcode 23,543合并K个升序链表,二叉树的直径
算法·leetcode·链表
木子欢儿8 小时前
Intel 架构的 MacBook Pro 运行 Linux 发热量高解决
linux·运维·服务器
凌虚8 小时前
面向 MySQL 用户的 PostgreSQL 快速上手指南
数据库·后端·架构
五阿哥永琪8 小时前
MySQL中操作json的函数!
数据库·mysql·json
来者皆善8 小时前
了解Mysql优化吗?如何优化索引?
数据库·mysql
都叫我大帅哥9 小时前
Java JJWT 完全指南:从入门到生产级实践
java
前端开发张小七10 小时前
Java 学习笔记 · 第一课:基础语法与核心概念
java·后端