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

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

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

相关推荐
Fcy6482 小时前
与队列有关练习题
算法
TracyCoder1232 小时前
LeetCode Hot100(71/100)——152. 乘积最大子数组
算法·leetcode·职场和发展
Z9fish2 小时前
sse哈工大C语言编程练习44
c语言·c++·算法
李日灐2 小时前
改造红黑树实现封装 map/set:感受C++ 标准容器的精妙设计与底层实现
开发语言·数据结构·c++·后端·算法·红黑树
李日灐2 小时前
【优选算法1】双指针经典算法题
数据结构·c++·后端·算法·刷题·双指针
LeeeX!2 小时前
玩转 3D 检测和分割(一):MMDetection3D 整体框架介绍
笔记·多模态感知
Frostnova丶2 小时前
(9)LeetCode 438.找到字符串中所有字母异位词
算法·leetcode
故事和你912 小时前
sdut-程序设计基础Ⅰ-期末测试(重现)
大数据·开发语言·数据结构·c++·算法·蓝桥杯·图论
努力学算法的蒟蒻2 小时前
day114(3.16)——leetcode面试经典150
算法·leetcode·职场和发展