括号匹配专题

你可否记得,当年被【括号匹配】支配的恐惧?

>>> 本文先讨论最基本的括号匹配及其经典的【栈】思路
>>> 再分析其变体
>>> 最后是究极变体,从【栈】【动态规划】【括号计数(追赶法)】三种方法切入,彻底理解括号匹配问题
>>>
>>> 记住,所有的方法,归根结底还是从括号字符串的自身特性出发的
>>> 再记住两句话:
>>>        1.【尝试用')'去消去前面的'('】
>>>        2.【当右括号数量超过左括号时,必然串无效】

经典的栈

/*
* 经典的【栈】思路
*
* 遇到'(',就入栈;  遇到')',就出栈
* 扫描过程中,当出栈时发现栈空时,串无效
* 扫描结束后。若栈空,则串有效;如果栈不空,则串有效
*/
public boolean isValid(String s) {Deque<Character> stack = new ArrayDeque<>();for(char c : s.toCharArray()) {if(c == '(') {stack.push(c);}else {if(stack.isEmpty()) {return false;}else {stack.pop();}}}return stack.isEmpty();
}

变体1——不止是小括号

思路变体:【经典的栈】 + 【Map】

▊【Q20】(ez) 有效的括号(括号匹配问题)
 
   给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]'的字符串,判断字符串是否有效。
  
有效字符串需满足:
   左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 注意空字符串可被认为是有效字符串。
 
示例 1:
   输入: “([{}])”
   输出: true
示例 2:
   输入: “([)]”
   输出: false

class Solution {public boolean isValid(String s) {/** 思路:* 如果是左括号,就毫不犹豫的入栈;* 如果是右括号,不仅要先判断是否栈空,还要判断弹出的左括号是否对应此时的右括号* * 注:* 1.使用HashMap进行匹配,使得代码和逻辑都更具美感* 2.典型的栈结构问题。由于java 8,9有移除Stack的趋势,因此这里使用双端队列Deque,同样有PushPop特性*/if(s == null || s.length() == 0)  return true;    // leetcode传统艺能,null和0值不说就是trueMap<Character, Character> map = new HashMap<>();map.put(')', '(');map.put(']', '[');map.put('}', '{');Deque<Character> dq = new ArrayDeque<>(); for(char c : s.toCharArray()){if(c == '(' || c == '[' || c == '{'){dq.push(c);}else{if(dq.size() == 0 || map.get(c) != dq.pop()) return false;}}return dq.size() == 0;}// 另外:栈中的字符串必定是'(',因此在这个简单的括号匹配问题中,栈中是什么根本无所谓
}

变体2——自己生成括号

思路变体:【回溯】 + 【左右括号计数】

这里利用了一个括号字符串的性质进行【剪枝】:当右括号数量超过左括号时,必然串无效——这个性质之后还会再派上大用场

▊【Q22】(md) 括号生成
   数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例:
输入
   n = 3
输出:[
   “((()))”,
   “(()())”,
   “(())()”,
   “()(())”,
   “()()()”
   ]

class Solution {/** 【回溯算法】* 思路 :其实这是很典型的需要减枝的回溯算法,应该不难想到* * String tempString是暂存串* int L, int R 记录左右括号的个数,是用于减枝的(右括号不可能多于左括号)* * 其实,剪枝的条件(右括号不可能多于左括号)也是再给出现一个有效串做铺垫:*    如果出现left==right,则这串不一定有效*  当限制了右括号不可能多于左括号时,如果出现left==right则一定是right追上了left,则这串一定有效* * 这就是【左右括号计数】解决括号匹配问题的思路————抛开栈的一个新思路*/public List<String> generateParenthesis(int n) {traceBack(n, "", 0, 0);return resList; }public List<String> resList = new ArrayList<>();public void traceBack(int n, String tempString, int L, int R) {if(R + L == 2*n) {if( R == n && L == n) {resList.add(tempString.toString());return;}else {return;// 减枝}}if(L < R) {return;// 减枝}traceBack(n, tempString + "(", L+1, R);traceBack(n, tempString + ")", L, R+1);}
}

究极变体,由定性到定量——寻找最长有效括号的长度
 
我们将从【】【动态规划】【左右括号计数】三个角度去攻克这个问题

【Q32】(hd) 最长有效括号
 
给定一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长的包含有效括号的子串的长度。
 
示例 1:

输入: “(()”
输出: 2
 
示例 2:
输入: “)()())”
输出: 4
 
示例 3:
输入: “()(())”
输出: 4

class Solution {public int longestValidParentheses(String s) {/** 【栈】* * 我们维护一个栈,值为下标*        每次遇到'('时,毫不犹豫的将其下标入栈,等待被配对弹出;*       每次遇到')'时,就执行一次弹出操作:*                如果这时栈空了,说明没有'('能与其配对,它的下标入栈(除非遇到下一个无法配对的')',它将永远垫底)*               如果栈不空,则尝试更新maxLen,此时的有效括号长度为此时的小标,与弹出后此时的栈顶的下标之差* * 理解:*        1.每次'('下标入栈,都是【等待被配对弹出】的; *             每次')'下标入栈,都说明此时有效串断了,它的下标现在垫底用的,作为【新串开始的前一个字符位置】*        2.一个重要的技巧:先入栈一个-1。这相当于在原串的最前面加了个')'———这不影响结果,而且解决了第一个字符为')'的情况下,pop出错的问题*      3.解决【括号字符串】问题有一个看似奇怪但绝对安全的思路: ')'去配对它左边的第一个'(',然后消掉 ,准没错!!!!!*         4.还是那个重要思路: ')'是有可能作为结束一个有效串的标志的*      5.再理解一下-1吧!它是一个有效串结束的标志,作为参照下标,等价于多出的')'。当下一个')'将其顶替后,它的使命也就结束了*/Stack<Integer> stack = new Stack<>();stack.add(-1);int maxLen = 0;for(int i = 0; i < s.length(); i++) {if(s.charAt(i) == '(') {stack.add(i);}else {stack.pop();if(stack.isEmpty()) {stack.add(i);}else {maxLen = Math.max(maxLen, i - stack.peek());}}}return maxLen;}
}

class Solution {/** 【动态规划】* * 举个例子帮助理解:*  下标:0 1 2 3 4 5*       ( ) ( ( ) )*  dp: 0 2 0 0 2 6*  dp表的值表示以当前括号做为结尾的最长有效括号长度————以'('结尾,必然填0,不用管; 以')'结尾,长度由三部分组成(★☆★):*  *  以index=5处的')'为例:*     先找到左边未被匹配的第一个'('。index=3处的'('已经是已经匹配的有效字符串的一部分,所以应该看这个有效字符串之前的第一个是否为'(',*          即s.charAt(i - dp[i-1] - 1)是否为'('*     如果是'('的话,则匹配上,原先的串被【加长】。因此第一部分是原先的串长dp[i-1];第二部分是刚刚匹配上从而加长的2*        即如果此时的')'能和 i - dp[i-1] - 1  处的'('匹配上,则 dp[i] = dp[i - 1] + 2*     千万别忘了,当这括号匹配上时(index=5,2),就有可能接上之前的有效串(在index=5,2匹配之前,index=3,4显然和index=0,1是独立的,但现在接上了)*        即再尝试加上刚匹配上的'('之前1个位置的dp值,即再加上dp[i - dp[i-1] - 2]*  *  三部分呼之欲出:dp[i] = dp[i - dp[i-1] - 2] + dp[i-1] + 2*  但是:一来 i - dp[i-1] - 1 ,即有效串左边的第一个位置有可能不存在需要提前判断:i - dp[i-1] - 1 >= 0 && ...*       二来 一次匹配后,在尝试接上再之前的有效串的时候,也可能不存在:i - dp[i-1] - 2 >= 0 ...*  */public int longestValidParentheses(String s) {int len = s.length();int[] dp = new int[len];int maxLen = 0;for(int i = 1; i < len; i++) {if(s.charAt(i) == ')') {if(i - dp[i-1] - 1 >= 0 && s.charAt(i - dp[i-1] - 1) == '(') {dp[i] = (i - dp[i-1] - 2 >= 0 ? dp[i - dp[i-1] - 2] : 0) + dp[i-1] + 2;}maxLen = Math.max(maxLen, dp[i]);}}return maxLen;}
}

class Solution {/** 【括号计数】* * 之前在[回溯法生成有效括号]时,使用的就是这种思路:从左向右遍历,如果右括号')'的个数多于左括号'('的个数,那就没有继续的必要了* 这与【')'和'('相消】的括号的性质是相契合的* * 我们从左向右遍历,是'('就left计数器加一,是')'就给right计数器加一* 每次加一后都检查一遍:*        如果right > left,则这个串已经无效的,将计数器都置0重新开始*      如果right < left,说明右括号在【追赶】左括号,这个有效串的长度一直在加长*        如果right = left,说明恰好匹配,有效串长度为2*right 或 2*left* * 理解(!!!):*         你可能会有疑问,当左右括号数量相等,就一定有效?很容易举出这样的反例啊:"))(("*      其实,"数量相等"在计数时,当右括号多于左括号时,就清零一次,重新开始:*      "))(("的前两个括号,并没有计数,也不可能出现left==right的情况* *        这个"相等",并不是left和right齐头并进,而是right对left的单方面追赶————这就是')'尝试消去'('的过程*        当right超过left,这段"赛跑"就结束了(这个有效串的长度终止了);需要让left先跑几步,right再去追它(需要先有若干'(',再让')'取匹配它)* * 还有一个致命的问题:当right一直没追上left时,就不会出现2*right 或 2*left的有效串长度更新,比如"(()"这种情况* 如何解决呢?* 从另一个方向开始跑,让left去追right————让'('去匹配')'* * * 通过这样的"两个方向的追赶",也就保证得出了最终的正确答案*/public int longestValidParentheses(String s) {int len = s.length();int left = 0;int right = 0;int maxLen = 0;for(int i = 0; i < len; i++) {if(s.charAt(i) == '(') {left++;}else {right++;}if(right == left) {maxLen = Math.max(maxLen, 2 * right);}else if(right > left) {left = 0;right = 0;}}right = 0;left = 0;for(int i = len - 1; i >= 0; i--) {if(s.charAt(i) == ')') {right++;}else {left++;}if(left == right) {maxLen = Math.max(maxLen, 2 * left);}else if(left > right) {right = 0;left = 0;}}return maxLen;}
}

部分题目来源

【Leetcode Q20】有效的括号

【Leetcode Q22】括号生成

【Leetcode Q32】最长有效括号

End ♬ By a Lolicon

【图解算法】彻底搞懂(括号匹配)——图解带你直击本质相关推荐

  1. C++两个函数可以相互递归吗_[算法系列] 搞懂递归, 看这篇就够了 !! 递归设计思路 + 经典例题层层递进

    [算法系列] 搞懂递归, 看这篇就够了 !! 递归设计思路 + 经典例题层层递进 从学习写代码伊始, 总有个坎不好迈过去, 那就是遇上一些有关递归的东西时, 看着简短的代码, 怎么稀里糊涂就出来了. ...

  2. [算法系列] 搞懂递归, 看这篇就够了 !! 递归设计思路 + 经典例题层层递进

    [算法系列] 搞懂递归, 看这篇就够了 !! 递归设计思路 + 经典例题层层递进 从学习写代码伊始, 总有个坎不好迈过去, 那就是遇上一些有关递归的东西时, 看着简短的代码, 怎么稀里糊涂就出来了. ...

  3. C语言的指针\数组用图解一次搞懂

    目录 1.指针 1.1指针相关标识符 1.1.1 取地址符号 & 1.1.2 解引用符号 * 1.1.3 const 符号 1.1.4 算数运算(偏移概念) 1.2野指针 1.3多级指针 1. ...

  4. 算法之路(5)--括号匹配问题(Valid Parentheses)[leetcode]

    题目: Given a string containing just the characters '(', ')', '{', '}', '[' and ']', determine if the ...

  5. C++paranthesis matching括号匹配的算法(附完整源码)

    C++paranthesis matching括号匹配的算法 C++paranthesis matching括号匹配的算法完整源码(定义,实现,main函数测试) C++paranthesis mat ...

  6. 转载 硬核图解!30张图带你搞懂!路由器,集线器,交换机,网桥,光猫有啥区别?

    硬核图解!30张图带你搞懂!路由器,集线器,交换机,网桥,光猫有啥区别?... 煎鱼(EDDYCJY) 于 2022-01-17 08:51:00 发布 1219 已收藏 22 文章标签: 交换机 网 ...

  7. 帮你彻底搞懂JS中的prototype、__proto__与constructor(图解)

    帮你彻底搞懂JS中的prototype.__proto__与constructor(图解) 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文 ...

  8. 五分钟搞懂什么是红黑树(全程图解)

    红黑树(又叫平衡二叉树),对很多童鞋来说,是既熟悉又陌生.熟悉是因为在校学习期间,准备面试时,这是重点.然后经过多年的荒废,如今已经忘记的差不多了.如果正在看文章的你,马上快要毕业,面临着找工作的压力 ...

  9. (转)帮你彻底搞懂JS中的prototype、__proto__与constructor(图解)

    文章目录 1. 前言 2. _ _ proto _ _ 属性 3. prototype属性 4. constructor属性 5. 总结 提示:不要排斥,静下心来,认真读完,你就搞懂了!(可以先看一下 ...

  10. css股票曲线图图解,如何看懂股票曲线图,一文搞懂这些曲线所代表的含义!

    原标题:如何看懂股票曲线图,一文搞懂这些曲线所代表的含义! 在股票技术分析中,我们经常要分析K线图,分时图等等,有些新手在入门的时候常常把两者弄混,所以统称它们为曲线图,实际它们就是K线图和分时图了, ...

最新文章

  1. 关于一些Linux SVN的安装使用
  2. tableview的两个重用cell方法
  3. pcap文件解析工具_【免费毕设】PHP网络数据包分析工具的设计与开发(源代码+论文)...
  4. SQLite VC环境搭建
  5. scala学习 之 环境搭建(一)
  6. git clone 报错:error: RPC failed; curl 18 transfer closed with outstanding read
  7. 菜鸟入门【ASP.NET Core】6:配置的热更新、配置的框架设计
  8. Objective-C基础之基本数据类型
  9. 更适合私有云的网络部署模式-动态路由
  10. pyodbc 连接informix
  11. mysql图书管理系统设计答辩_基于微信的图书管理系统毕业论文+任务书+开题报告+答辩PPT+前后台(Java+Mysql)源码及数据库文件...
  12. vue axios常用写法
  13. java openCV调用摄像头并以窗体显示出来
  14. 国产M0核风机量产程序开发方案… FOC电机控制开发方案…3电阻采样
  15. CS224n_2019_Assignment1: Exploring Word Vectors Coding Solution
  16. 词频统计,中文分词FMM,BMM博客
  17. 疫情过后,35岁老程序员年后第一天上班被公司劝退,该何去何从?
  18. Windows docker 安装 home assistant
  19. kkt条件里面的松弛互补条件推导
  20. Linux下用户的密码

热门文章

  1. 多任务学习(Multi-Task Learning)
  2. Linux CentOS 学习笔记
  3. 查看服务器ip配置信息,怎么查看服务器ip地址,怎么查看ip地址和端口
  4. Springboot配置suffix指定mvc视图的后缀
  5. 团队管理19--团队分工
  6. php安装失败,PHP编译安装时常见错误解决办法
  7. 【2021 年终总结】一年涨粉100倍,有规划始执行~成功一半
  8. 第十五周博客作业西北师范大学|李晓婷
  9. android icloud照片恢复软件,iCloud照片怎么恢复到手机 iCloud照片恢复到手机方法【详细步骤】...
  10. 郑州计算机安全协会安全员考试成绩,安全员C证考试结果查询