力扣刷题笔记 32. 最长有效括号 C#

今日签到题,题目如下:

给定一个只包含 '(' 和 ')' 的字符串,找出最长的包含有效括号的子串的长度。

示例 1:

输入: "(()"
输出: 2
解释: 最长有效括号子串为 "()"
示例 2:

输入: ")()())"
输出: 4
解释: 最长有效括号子串为 "()()"

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-valid-parentheses
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

第一反应用一个 int 值 k 记录 "()" 连续出现的情况,然后返回 k * 2。这种思路忽略了 "(())" 这种括号对在括号对内的情况。提交失败后跑去打游戏了,间隙中时不时地对此题思考。

重新解题的思路是使用两个 int 值 left 和 right 分别记录 '('  和 ')' 出现的次数,当 right >= left 的时候将当前的 left * 2 和当前答案进行比较,并且重置两个 left 和 right。这种思路忽略了 "(()" 一类的情况。

所以我在循环结束之后,添加判断,如果 left >= right,则将当前 right * 2 和当前答案进行比较。这个改动实际上并没有什么改观,如果字符串仅为 "(()",的确能返回正确值 2。但是如果字符串为 "()(()" 时,返回值变成了错误的 4。因为中间的 '(' 中断了括号对的连续性,但是这种中断是由于向后遍历并没有与之对应的 ')',我没有找到办法标记这种不可预知的中断。

以下为错误代码:

public class Solution {
    public int LongestValidParentheses(string s) {
        char[] charArr = s.ToCharArray();
        int ans = int.MinValue;
        int left = 0;
        int right = 0;
        for (int i = 0;i < charArr.Length;i++)
        {
            if (right > left)
            {
            }
            else if (charArr[i] == '(')
            {
                left++;
            }
            else if (charArr[i] == ')')
            {
                if (right >= left)
                {
                    ans = Math.Max(left * 2,ans);
                    right = 0;
                    left = 0;
                }
                else 
                {
                    ans = Math.Max(right * 2,ans);
                    right++;
                }
            }
        }
        if (left >= right)
        {
            ans = Math.Max(right * 2,ans);
        }
        if (ans == int.MinValue)
        {
            return 0;
        }
        return ans;
    }
}

无奈之下还是看官方题解。

官方提供四种解法:

  • 暴力解法
  • 动态规划
  • 正向逆向结合

正向逆向结合

由于个人比较懒的关系,不深入讨论前三种解法和代码实现,只在最后记录思路。看到第四种解法,我发现基本思路一致。不同的部分是,官方题解中进行了一遍逆序遍历记录 "(()" 一类情况的结果。回顾自己的解法,的确原本能记录 "())" 的情况。所以相同的判断方式,正序一遍 + 逆序一遍可以获得所有 "())" 和 "(()" 的情况。但是我原本的代码存在另一个问题,并不是 left >= right 的时候重置两个 int 值,而是 left > right 时重置,left == right 对比结果。修改之后提交,通过。

复杂度分析:

虽然有两次循环,但是互相独立,故时间复杂度为O(N)。我把 字符串 转为字符数组进行判断,此时空间复杂度应该是 O(N),实际只需要直接将字符串当作数组使用,实际空间复杂度为 O(1),代码中已修改。

以下为代码:

public class Solution {
    public int LongestValidParentheses(string s) {
        int ans = 0;
        int left = 0;
        int right = 0;
        for (int i = 0;i < s.Length;i++)
        {
            if (s[i] == '(')
            {
                left++;
            }
            else if (s[i] == ')')
            {
                right++;
                if (right == left)
                {
                    ans = Math.Max(left * 2,ans);
                }
                else if (right > left)
                {
                    right = 0;
                    left = 0;
                }
            }
        }
        left = 0;
        right = 0;
        for (int i = 0;i < s.Length;i++)
        {
            if (s[s.Length - i - 1] == ')')
            {
                right ++;
            }
            else if (s[s.Length - i - 1] == '(')
            {
                left++;
                if (right == left)
                {
                    ans = Math.Max(left * 2,ans);
                }
                else if (left > right)
                {
                    right = 0;
                    left = 0;
                }
            }
        }
        return ans;
    }
}

暴力解法

这次不可思议我居然不是先用的暴力解法(虽然没有自己解出题目)。

暴力解法即是先从假设括号对的最大长度为数组可能的最大偶数长度。然后逐渐缩小范围进行验证,如果验证成功则返回验证时的最大长度。

验证过程需要从大到小遍历最大长度的可能,内部嵌套遍历对应长度的起始位置,在这层循环内再嵌套遍历每个起始位置对应的内容是否满足括号对。

复杂度分析:

时间复杂度为三层嵌套的循环 O(N³ )。题解中,用一个栈验证第三层循环中括号对出现的情况,空间复杂度为 O(N)。不知道能不能直接用 left 和 right 验证,与前一个解法不同,此时仅验证目标偶数长度的,只在最后验证 left == right 即可,具体我没验证,如果可以的话空间复杂度为 O(1)。

动态规划

说实话我到现在还不是完全理解动态规划,个人理解就是把问题先拆解到最简单问题,同时较复杂问题的题解需要最简单的问题的题解,然后不断将较简单问题的题解返回给较复杂问题,直到最终解题(感觉自己说得好像就是递归而已)。

本思路建立了一个长度等于字符串长度的 int 型数组 dp,数组保存字符串中每个位置为结束的有效长度。'(' 不可能为有效括号对的结束,字符串中为 '(' 位置在 dp 对应位置都记作 0,在之后的遍历中不考虑计算。

现在我们来看 ')'。如果当前为 ')',我们需要验证他的前位,此时存在两种情况:

  1. 如果前一位为 '(' 则结束自身括号对 以及 自身括号对内部的情况判断,结果为 2。然后需要再向前一位判断,如果 dp 在该为上不为 0(由于正序采用正序遍历,所以之前的 ')' 如果时一个有效括号对的结束,它就不为 0),则在结果加上这个数。公式表示为dp[i] = 2 + dp[i -2],结果与 最终结果ans 比较后决定是否替换 最终结果 ans。
  2. 如果前一位为 ')' 则判断其是否有效,即 dp 在该为上是否为 0。如果无效则该位置也无效,开始判断下一个位置。如果有效,则加上其有效值假设为 k(该值表示如果当前位置形成有效括号对,k 值为它的内部括号对有效长度)。然后判断 i - k - 1 位是否为 '(',如果不是则自身无法形成有效括号对,该位置并非有效长度,开始判断下一个位置。结束如果是则自身形成有效括号对,有效长处为自身有效长度加内部括号有效长度,即 2 + k。最后像情况 1 的最后一样,加上 dp 在再前一位的值,最终表达式为 2 + k + dp[i - k - 2],其中 k = dp[i -1]。结果与 最终结果ans 比较后决定是否替换 最终结果 ans。

题解还需要考虑一些对数组的边界判定,在这里不做赘述。

复杂度分析:

一次正序遍历,时间复杂度为 O(N),使用一个新的 int 型数组记录有效长度,空间复杂度为 O(N)。

栈的思路是,开始在栈中存入一个-1,然后开始遍历字符串,将所有 '(' 的下标入栈等待配对,对于 ')' 先将栈顶元素出栈,然后分情况处理:

  1. 如果栈为空,则将当前下标入栈。因为在该次处理中,先对栈顶元素进行了一次出栈操作,即该情况为前一个元素也是 ')'。故入栈,替换最后一个无效 ')' 的记录值。
  2. 如果栈不为空,则获得一个新的有效长度,即当前下标减去栈顶元素。与上述情况对立,被弹出的是 '(',所以当前位置的 ')' 形成有效括号对,有效长度为与距离上一个无效 ')' 或者等待配对的 '('  的距离,即当前下标减去栈顶元素,结果与 最终结果ans 比较后决定是否替换 最终结果 ans。

通过以上,可以明白为什么在栈中先存入一个 -1,因为如果开始就产生有效括号对,栈中没有最后一个无效 ')' 或者等待配对的 '(' 的下标判断,所以存入 -1 用作为之后的有效括号对做长度计算。

复杂度分析:

一次正序遍历,时间复杂度为 O(N),使用一个栈保存下标,最大长度情况下栈长为数组长度,故空间复杂度为 O(N)。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值