
本文揭示一段自定义括号匹配算法在本地返回 true 而 leetcode 在线判题返回 false 的根本原因——逻辑缺陷、状态重用错误及边界未覆盖,并提供符合题意的简洁、健壮、o(n) 时间 + o(n) 空间的标准栈解法。
本文揭示一段自定义括号匹配算法在本地返回 true 而 leetcode 在线判题返回 false 的根本原因——逻辑缺陷、状态重用错误及边界未覆盖,并提供符合题意的简洁、健壮、o(n) 时间 + o(n) 空间的标准栈解法。
该问题表面是“结果不一致”,实则是代码中存在多处严重逻辑漏洞,导致行为不可预测。以输入 "()[]{}" 为例,本地运行返回 true 属于偶然(依赖未初始化变量的默认值或未触发的分支),而 LeetCode 测试环境因更严格的执行路径或不同 JVM 行为暴露出真实缺陷。
? 核心问题定位
指针状态全局污染
t1l/t1r/t2l/t2r/t3l/t3r 是类成员变量,跨测试用例不重置。LeetCode 运行多个测试用例于同一 Solution18_1 实例(或多次复用),前一用例残留的 t1r = Integer.MAX_VALUE 或 t2l = -1 会直接污染后续判断,造成 checkMatch() 中的越界比较(如 t1l t2r 在 t2l == -1 时恒真,掩盖嵌套错误)。-
循环索引被非法修改
代码中频繁执行 i = t1l;(如第 76 行),强行将 for 循环下标回退。这不仅破坏了迭代顺序,还导致:- 同一字符被重复处理(如 i=0 处理 '(' 后跳回 i=0,再次进入分支)
- i++ 在下次循环时仍执行,实际跳过下一个位置
- 无法保证遍历完整性,对 "([)]" 等反例完全失效
-
checkMatch() 逻辑混乱且不完整
该方法试图通过比较左右边界判断嵌套关系,但:- 条件分支存在明显笔误(如 else if (t2l > t3l && t2l
- 仅检查两两组合(如 { vs [),却忽略三重嵌套(如 {[()]})中 ( ) 对 [ ] 的包裹关系
- 未处理 t1l 或 t2r 仍为初始值(-1/MAX_VALUE)时的非法比较,可能引发隐式逻辑错误
matchCheck() 仅验证数量平衡,未校验顺序
即使 matchCheck('{', s) 返回 0,也仅说明 { 和 } 数量相等,无法保证 "{[}" 这类乱序输入被拒绝——而这正是本题核心要求。
✅ 正确解法:栈驱动的线性扫描(推荐)
有效括号的本质是后进先出(LIFO)结构:每次遇到闭括号,必须匹配最近一个未闭合的同类型开括号。标准解法使用栈,清晰、高效、无状态污染:
import java.util.*;
class Solution {
public boolean isValid(String s) {
Map<character character> pairs = Map.of(
')', '(',
'}', '{',
']', '['
);
Deque<character> stack = new ArrayDeque();
for (char c : s.toCharArray()) {
if (pairs.containsValue(c)) {
// 开括号:入栈
stack.push(c);
} else if (pairs.containsKey(c)) {
// 闭括号:检查栈顶是否匹配
if (stack.isEmpty() || stack.pop() != pairs.get(c)) {
return false;
}
}
// 忽略非括号字符(题目保证输入合法,此步可选)
}
return stack.isEmpty(); // 所有开括号均被匹配
}
}</character></character>
⚠️ 关键注意事项
- 不要复用成员变量存储临时状态:每个测试用例应独立运行。若需缓存,务必在 isValid() 开头重置(如 t1l = t2l = t3l = -1; t1r = t2r = t3r = Integer.MAX_VALUE;),但更推荐彻底改用局部变量。
- 禁止手动修改循环变量 i:它破坏控制流确定性,是调试噩梦。所有“回溯”需求应通过栈、递归或预处理解决。
-
测试用例要覆盖边界:除 "()[]{}" 外,必测:
"" → true (空串) "([)]" → false (交叉非法) "(((" → false (无闭合) "}{" → false (顺序颠倒) "{[()]}" → true (多重嵌套)
? 总结
本题不是考察“空间复杂度优化”,而是检验对栈抽象模型的理解与应用能力。过度设计(如尝试 O(log n) 空间)反而引入不可控状态和隐蔽 bug。坚持“单一职责”原则:用栈处理顺序匹配,用哈希表管理映射,用局部变量隔离状态——这才是工业级代码的稳健之道。











