LeetCode 32. 最长有效括号

题目:

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

示例 1:

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

示例 2:

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

题解:

如何分治:

定义 dp[i] 表示以下标 i 字符结尾的最长有效括号的长度。

1.当s[i-1]=='(' && s[i]==')'时,即字符串形如"...()"时

dp[i]=dp[i-2]+2

2.当s[i-1]==')' && s[i]==')'时,即字符串形如"...))"时

(这个情况很复杂,详细分析)

2.1观察这个括号串

01234567
(((())))

我们希望计算 dp[7] 的值

已知1~6是有效的括号串,所以 -- 条件1: dp[6]==6;  条件2:  s[7 - 6 - 1] 即 s[0] == '('

所以  dp[7] = dp[6] + 2 = 8

所以

if( s[i - dp[i-1] - 1] == '(' ){
    dp[i] = dp[i-1] + 2;
}
01234567
)((())))

已知1~6是有效的括号串,所以 -- 条件1: dp[6]==6;  条件2:  s[7 - 6 - 1] 即 s[0] == ')'

因为条件2 s[0] = ')' ; 所以  dp[7] = 0

 

然而实际上没有这么简单!!! 在情况2.1的基础上,还有一种进阶情况!!

2.2观察这个括号串

01234567891011
(((())))(())

我们希望计算 dp[11] 的值

按照前文的情况2.1, 

条件1: dp[10]==6;  条件2:  s[11 - 2 - 1] 即 s[8] == '('

所以 dp[11] =  dp[10] + 2 = 4

 

上面这个"递推结果"是错误的

为什么?

我发现,

dp[10]对应 "9~10这2个格子"

dp[11]在dp[10]的基础上,向左右两边都拓展了一格, 即"8~11这4个格子"

向左拓展一格对应于 情况2.1 中的这个判断:
s[i - dp[i-1] - 1] == '('
向右拓展一格对应于
s[i] = ')' 即我们正在求解的这一格

由于向左拓展了一格, 8号格子和前面的7号格子相连起来了~~~~

所以,我还需要把 dp[7] 计算在内

假如,7号格子是"某一有效括号串的结尾字符"

dp[11] =  dp[10] + 2 + dp[7]

假如,7号格子不是"某一有效括号串的结尾字符"

dp[11] =  dp[10] + 2 + 0

可以写出如下伪代码

if( dp[i - dp[i - 1] - 2] > 0 ){
    dp[i] = dp[i - 1] + 2 + dp[i - dp[i - 1] - 2];
}
if( dp[i - dp[i - 1] - 2] == 0 ){
    dp[i] = dp[i - 1] + 2 + 0;
}

//精简后实际上只需要一行
dp[i] = dp[i - 1] + 2 + dp[i - dp[i - 1] - 2];

写出递归树,检查有无漏掉情况

为什么要画递归树?

因为所谓"递归树"就是一种自顶向下分析问题的思路图

动态规划就是自顶向下分析,自底向上求解,所以做动态规划一定要画出递归树

 

就算已经推导出了状态转移方程,仍然需要画出递归树排查有无思维漏洞!!!!!

 

递归树可以很直观地看出动态规划的重叠子问题

 

边界条件:

有效的括号串一定以 ')' 结尾

所以 '(' 字符结尾的括号串有效长度移动为0,即

if(s.charAt(i)=='('){
    dp[i]=0;
}

我设置的边界条件出错了,实话说我不知道错在哪

public class Solution {

    public int trans(){
        return 0;
    }

    public int longestValidParentheses(String s) {
        int maxans = 0;
        int[] dp = new int[s.length()];
        Integer size = s.length();
        //设置边界条件(似乎又不需要设置???)
        for(int i=0;i<size;i++){
            if(s.charAt(i)=='('){
                dp[i]=0;
            }else if(s.charAt(i)==')' && i==0){
                dp[i]=0;
            }else if(s.charAt(i)==')'){
                dp[i]=-1;
            }
        }

        for(int i=0;i<size;i++){
            //递归树的"....()"分支
            if(i-1>=0){
                if(s.charAt(i-1)=='('&&s.charAt(i)==')'){
                    dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
                }
                //递归树的"....))"分支
                else if(s.charAt(i-1)==')'&&s.charAt(i)==')'){
                    if(i-dp[i-1]-1>=0){
                        if(s.charAt(i-dp[i-1]-1)=='('){
                            dp[i] = dp[i - 1] + ((i - dp[i - 1]) >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2;//加上"三元算符"是为了防止i - dp[i - 1] - 2处位于数组头部,防止数组从起始处越界
                        }
                    }

                }
            }
            maxans = Math.max(maxans, dp[i]);
        }
        return maxans;
    }

    public static void main(String[] args) {

    }
}

AC的代码

心得:

实际编码的时候和思路的时候不一样

解题思维分几个过程

1.纸上算法推演

2.书写伪代码

3.实际编码

1.纸上算法推演时,只是形成"人类可读"的算法思路

2.书写伪代码 已经开始关注"如何用代码表述算法"但是对于"数组越界,指针越界等"仍然缺乏探讨

3.实际编码要考虑到一切,比如数组越界等

dp[i] = dp[i - 1] + ((i - dp[i - 1]) >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2;

//加上"三元算符"是为了防止i - dp[i - 1] - 2处位于数组头部,防止数组从起始处越界

class Solution {
    public int longestValidParentheses(String s) {
        int maxans = 0;
        int[] dp = new int[s.length()];
        Integer size = s.length();
        

        for(int i=0;i<size;i++){
            //递归树的"....()"分支
            if(i-1>=0){
                if(s.charAt(i-1)=='('&&s.charAt(i)==')'){
                    dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
                }
                //递归树的"....))"分支
                else if(s.charAt(i-1)==')'&&s.charAt(i)==')'){
                    if(i-dp[i-1]-1>=0){
                        if(s.charAt(i-dp[i-1]-1)=='('){
                            dp[i] = dp[i - 1] + ((i - dp[i - 1]) >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2;//加上"三元算符"是为了防止i - dp[i - 1] - 2处位于数组头部,防止数组从起始处越界
                        }
                    }

                }
            }
            maxans = Math.max(maxans, dp[i]);
        }
        return maxans;
    }
}

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值