LeetCode 热题 HOT 100 第16天:“最长有效括号”

继续刷LeetCode 热题 HOT 100 的题目,并且在博客更新我的solutions。在csdn博客中我会尽量用文字解释清楚,相关Java代码大家可以前往我的个人博客jinhuaiyu.com中查看。
今天的题目解题方法很多,我只用了最直观的方法做出来了。而官方给的三种解答我我要不就是想不到要不就是没想出来……对于官方解答我看完只能说妙啊……
题目:最长有效括号
给你一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长有效(格式正确且连续)括号子串的长度。
示例 1:
输入:s = “(()”
输出:2
解释:最长有效括号子串是 “()”
示例 2:
输入:s = “)()())”
输出:4
解释:最长有效括号子串是 “()()”
示例 3:
输入:s = “”
输出:0
提示:
0 <= s.length <= 3 * 104
s[i] 为 ‘(’ 或 ‘)’
solution 1:栈 / 直观的遍历后分段
算法培养的是一个思路整理的过程,栈肯定是大家看到括号匹配题时首先想到的工具。之前我们做过一道判断当前括号串是否有效的题,而现在题目转变成了找出字符串中最长有效的括号串。判断一个括号串是否有效的方法自然是通过不匹配则入栈,匹配则出栈的思路来做。那么怎么通过栈在一次遍历字符串后能得到结果呢。
有两种方向,一种是一遍遍历一边计算到当前位置最长有效长度的大小;还有一种是一遍遍历后将整个字符串中所有尽可能长的有效括号串全部分割出来,而分割它们的就是那些无法被匹配上的字符串。第二种情况的解法相比之下更加直观,一次遍历后,我们只需要找到所有不能被匹配的括号所在的位置,它们会将字符串分割成多个有效括号串,最后我们看哪个有效括号串长度最长。
与之前判断字符串是否有效不同的地方有几处:1、我们压入栈的不再是括号字符,而是它在整个字符串中的位置,这样在后面计算长度的时候可以直接算出来;2、一开始我们要把字符串的左侧外边界压入栈(即-1),这样的话,做减法时会有一个左边界能被用来计算到最左侧的最长有效括号长度;3、最后我们还要用s.length()来当右外边界,至此所有不能被匹配的括号位置加上左右外边界构成一系列位置,它们中相邻的做减法(当然计算距离时还要再-1才是最终长度),其中最大的两个相邻位置夹住的就是最长有效括号。
另外,出栈的条件是能和当前遍历到的右括号匹配(栈顶元素在字符串中对应位置是左括号),其他情况一律入栈。遍历一轮后,匹配的都会出栈,留下的都是找不到匹配的。
如果大家不理解为什么栈可以用来判断括号匹配,大家可以看看之前的那道判断括号串是否有效的题目http://jinhuaiyu.com/leetcode-valid-parentheses/

solution 2:栈 / 一边遍历一边计算最大长度
这种方法我一开始也尝试过,但是思路没有整理清楚,看了官方解答后,觉得不太直观,不过确实是很巧妙地解决了一边遍历一边更新答案。前一种方法中提到,我们还可以一边遍历一边判断到目前为止扫描的子串的有效性,同时能得到最长有效括号的长度。以下是官方解答的原话:
具体做法是我们始终保持栈底元素为当前已经遍历过的元素中「最后一个没有被匹配的右括号的下标」,这样的做法主要是考虑了边界条件的处理,栈里其他元素维护左括号的下标:
对于遇到的每个 ‘(’ ,我们将它的下标放入栈中
对于遇到的每个 ‘)’ ,我们先弹出栈顶元素表示匹配了当前右括号:
如果栈为空,说明当前的右括号为没有被匹配的右括号,我们将其下标放入栈中来更新我们之前提到的「最后一个没有被匹配的右括号的下标」
如果栈不为空,当前右括号的下标减去栈顶元素即为「以该右括号为结尾的最长有效括号的长度」
我们从前往后遍历字符串并更新答案即可。
需要注意的是,如果一开始栈为空,第一个字符为左括号的时候我们会将其放入栈中,这样就不满足提及的「最后一个没有被匹配的右括号的下标」,为了保持统一,我们在一开始的时候往栈中放入一个值为 −1 的元素(注:栈里存放的也是括号在字符串中的位置,-1模拟的是假设在字符串最前面放一个右括号用于作为边界条件,不得不说,突然感觉和前面我自己想的方法有很多不谋而合的地方)。

总的来说,就是如果是’(‘就入栈,如果是’)‘就把当前栈顶元素弹出,如果此时栈空,就将当前’)'位置入栈。这种方法保证了栈里永远只有一个右括号,而且永远在栈底。如果当前的是左括号,则不可能有以当前括号为结尾和有效括号串,如果当前是右括号,而栈底唯一一个右括号被弹出了,说明当前的右括号并没有匹配上,它需要被入栈。除了前两种情况,才是真正的有效情况,此时计算以当前括号为结尾的有效括号串长度的方法为用当前括号位置减掉栈顶元素。
这里比较反直觉的是官方解答里不管栈顶是左括号还是右括号,只要遍历到右括号,都弹出,容易让人迷惑,建议大家找一些括号串模拟一遍流程。
当然,我们可以把它改造的稍微直觉性一点,就是如果当前遍历到右括号,先看看栈顶,如果是左括号才弹出,否则直接压入当前右括号,至于前面那个右括号,不弹出也不影响。不过这种方法每次看栈顶元素时,如果不是-1才能去字符串里找这个位置的字符甚至出栈。

solution 3:动态规划
具体太晚了让我偷个懒,直接搬运官方解答(说实话还是讲的很清楚的):
在这里插入图片描述
我们就是要找到dp[i]和前面的有什么关系,事实上,只有三种情况:1、(结尾,肯定无效;2、()结尾,有效长度+2;3、))结尾,两个位置要一起讨论,此外,不仅要考虑前一个位置为结尾的最长有效串和加上这个位置及其对称位置外,还要考虑在更前面有没有有效串,只要是连在一起的都要加起来。

solution 4:贪心算法
利用两个计数器 left 和 right 。首先,我们从左到右遍历字符串,对于遇到的每个‘(’,我们增加 left 计数器,对于遇到的每个‘)’ ,我们增加 right 计数器。每当 left 计数器与right 计数器相等时,我们计算当前有效字符串的长度,并且记录目前为止找到的最长子字符串。当 right 计数器比left 计数器大时,我们将 left 和 right 计数器同时变回 0。这是一种典型的贪心思想,让右括号来追左括号,但是大家可能会发现贪心的漏洞,如果右括号一直追不上左括号怎么办呢,这些情况可能会被漏掉。
一种巧妙的思想就是再从右往左遍历一遍,把这些漏掉的算上。这种情况下,就是左括号追右括号了。整个条件就是左右颠倒了,或者说其实是一回事。

Finally,大家对四种方法如果还有不明白的地方结合代码看,然后自己根据用例手动模拟一遍就明白了。代码放在我的个人博客http://jinhuaiyu.com/leetcode-longest-valid-parentheses/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值