[特殊字符] LeetCode 做题笔记(二):678. 有效的括号字符串

📝 LeetCode 做题笔记(二):678. 有效的括号字符串

题目链接678. 有效的括号字符串
难度 :中等 | 语言 :Java | 标签:栈、贪心、字符串、动态规划


🔍 题目理解

给定字符串 s,包含 '('')''*' 三种字符,其中 '*' 可视为:

  • 左括号 '('
  • 右括号 ')'
  • 空字符串 ""

判断是否存在一种替换方式,使字符串成为有效括号序列

📌 有效括号规则

  1. 左右括号数量匹配
  2. 任意前缀中,左括号数 ≥ 右括号数

💡 解题思路:贪心 + 区间维护(最优解)

核心洞察

不枚举 * 的所有可能,而是维护未匹配左括号数量的可能范围 [low, high]

状态定义:
  • low:当前最少可能的未匹配左括号数(尽量把 *) 用)
  • high:当前最多可能的未匹配左括号数(尽量把 *( 用)
遍历规则:
字符 low 变化 high 变化 说明
'(' +1 +1 必定增加未匹配左括号
')' -1 -1 必定消耗一个左括号
'*' -1 +1 三种可能:)/(/""
关键剪枝:
java 复制代码
// high < 0:即使把*全当(用,右括号还是太多 → 无效
// low = Math.max(low, 0):未匹配左括号数不能为负(前缀合法性)

🧩 代码实现(贪心法 - Java)

java 复制代码
class Solution {
    public boolean checkValidString(String s) {
        int low = 0, high = 0;
        
        for (char ch : s.toCharArray()) {
            if (ch == '(') {
                low++;
                high++;
            } else if (ch == ')') {
                low = Math.max(low - 1, 0);  // 关键:low不能为负
                high--;
            } else {  // ch == '*'
                low = Math.max(low - 1, 0);  // * 当作 )
                high++;                      // * 当作 (
            }
            
            if (high < 0) {  // 右括号太多,无法匹配
                return false;
            }
        }
        
        return low == 0;  // 最终能否完全匹配
    }
}

🔄 其他解法对比

方法二:双栈法(直观但空间略高)

  • 用两个栈分别记录 '(''*' 的位置
  • 遇到 ')' 优先匹配 '(',没有再匹配 '*'
  • 最后检查剩余 '(' 能否被右侧 '*' 匹配
java 复制代码
class Solution {
    public boolean checkValidString(String s) {
        Deque<Integer> leftStack = new ArrayDeque<>();
        Deque<Integer> starStack = new ArrayDeque<>();
        
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            if (ch == '(') {
                leftStack.push(i);
            } else if (ch == '*') {
                starStack.push(i);
            } else {  // ')'
                if (!leftStack.isEmpty()) {
                    leftStack.pop();
                } else if (!starStack.isEmpty()) {
                    starStack.pop();
                } else {
                    return false;
                }
            }
        }
        
        // 匹配剩余的 '(',确保 '(' 在 '*' 左侧
        while (!leftStack.isEmpty() && !starStack.isEmpty()) {
            if (leftStack.peek() < starStack.peek()) {
                leftStack.pop();
                starStack.pop();
            } else {
                break;  // '(' 在 '*' 右侧,无法匹配
            }
        }
        
        return leftStack.isEmpty();
    }
}

方法三:动态规划(通用但复杂)

  • dp[i][j] 表示 s[0:i] 是否能形成 j 个未匹配左括号
  • 时间复杂度 O(n²),适合理解但不推荐面试使用
java 复制代码
// 简略版思路,完整实现略长
boolean[][] dp = new boolean[n + 1][n + 1];
dp[0][0] = true;
// 状态转移略...

📊 复杂度分析

方法 时间复杂度 空间复杂度 适用场景
贪心区间 O(n) O(1) ✅ 首选
双栈法 O(n) O(n) 思路直观
动态规划 O(n²) O(n²) 理解状态转移

🎯 总结 & 易错点

贪心法精髓

  • 用区间 [low, high] 表示所有可能状态,避免指数级枚举
  • low = Math.max(low, 0) 是保证前缀合法的关键

常见错误

  1. 忘记 low 不能为负(前缀中右括号不能超过左括号)
  2. 最终判断写成 high == 0(应该是 low == 0,只要有一种可能完全匹配即可)
  3. * 当空字符串的情况被忽略(体现在 low-1high+1 的区间扩展)

💡 举一反三


🔧 Java 特性小贴士

java 复制代码
// 1. TreeSet 自动排序 + 去重
TreeSet<Integer> set = new TreeSet<>();
set.add(3); set.add(1); set.add(2);
// 迭代顺序: 1, 2, 3 (升序)
set.pollFirst();  // 移除并返回最小值
set.pollLast();   // 移除并返回最大值

// 2. Deque 作为栈使用(比 Stack 更高效)
Deque<Integer> stack = new ArrayDeque<>();
stack.push(1);      // 入栈
int top = stack.pop();  // 出栈
boolean empty = stack.isEmpty();

// 3. 字符串遍历推荐方式
for (char ch : s.toCharArray()) {  // 简洁
    // ...
}
// 或需要索引时:
for (int i = 0; i < s.length(); i++) {
    char ch = s.charAt(i);
    // ...
}

🌟 写在最后

两道题代表两类典型思维:

  • 1878:几何枚举 + 边界处理 → 锻炼坐标变换能力
  • 678:贪心区间 + 状态压缩 → 培养抽象建模思维

Java 选手注意:

  • TreeSet 是升序,转结果数组时要反转
  • Math.max() 处理边界比三元表达式更清晰
  • 优先用 ArrayDeque 替代 Stack

刷题不在多,而在每道题都吃透核心思想。共勉!🚀

如果这篇笔记对你有帮助,欢迎点赞收藏~ 有其他题想看我写笔记,评论区告诉我!

相关推荐
wabs6661 小时前
关于二叉树【力扣572.另一棵树的子树的思考】
数据结构·c++·算法·leetcode·二叉树
hanlin031 小时前
刷题笔记:力扣第41题-缺失的第一个正数
笔记·算法·leetcode
shehuiyuelaiyuehao2 小时前
算法50,分治归并,数组中的逆序对
java·算法
自不量力的A同学2 小时前
开源一站式智能终端 uniTerm v1.9.4 发布
笔记
阳明山水2 小时前
校准分位数驱动智能库存决策
人工智能·深度学习·算法·机器学习·架构
91刘仁德3 小时前
C++ 继承和多态 设计模式
c语言·c++·笔记
库玛西4 小时前
哈夫曼树与前缀编码:数据压缩的贪心核心
c语言·c++·笔记·考研
别动我齐刘海4 小时前
ROS2 Jazzy + C++ 实战路线——进阶学习3
c++·人工智能·vscode·python·算法·机器学习·机器人
zhangfeng11334 小时前
《从“人工适配“到“智能生成“:KernelSwift 跨国产芯片算子迁移全栈方案解读》 —— 强调范式跃迁和跨硬件属性,适合偏架构分析的写法
人工智能·算法·华为·ai编程·npu
0+1114 小时前
算法 --滑动窗口
c++·算法·leetcode