【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())。
    • 如果栈为空,说明所有括号都完美匹配,字符串有效。
    • 如果栈不为空,说明有剩余的左括号没有被闭合(例如 "(""{[()"),字符串无效。

完整代码

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)

参考灵神

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xumistore

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值