[特殊字符] 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

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

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

相关推荐
糖炒栗子03262 小时前
【笔记】高分卫星影像 TIF 切片处理
笔记
Nice_Fold2 小时前
Kubernetes DaemonSet、StatefulSet与Service(自用笔记)
笔记·容器·kubernetes
_深海凉_3 小时前
LeetCode热题100-寻找两个正序数组的中位数
算法·leetcode·职场和发展
踩坑记录4 小时前
leetcode hot100 寻找两个正序数组的中位数 hard 二分查找 双指针
leetcode
旖-旎4 小时前
深搜练习(电话号码字母组合)(3)
c++·算法·力扣·深度优先遍历
谭欣辰4 小时前
C++快速幂完整实战讲解
算法·决策树·机器学习
Mr_pyx4 小时前
【LeetHOT100】随机链表的复制——Java多解法详解
算法·深度优先
AIFarmer4 小时前
【无标题】
开发语言·c++·算法
AGV算法笔记5 小时前
CVPR 2025 最新感知算法解读:GaussianLSS 如何用 Gaussian Splatting 重构 BEV 表示?
算法·重构·自动驾驶·3d视觉·感知算法·多视角视觉
ZhiqianXia5 小时前
《The Design of Design》阅读笔记
前端·笔记·microsoft