括号问题思考题

括号问题集

有效的括号

给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。

示例 1:
输入:s = “()”
输出:true

示例 2:
输入:s = “()[]{}”
输出:true

示例 3:
输入:s = “(]”
输出:false

提示:
1 <= s.length <= 104
s 仅由括号 ‘()[]{}’ 组成
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/valid-parentheses
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

对于这类题来说,括号是需要合理的,对于括号问题就应该思考两个情况,一种是括号包括号,一种是括号外有括号。这里以两个括号来举例:
(()) 这就是括号包括号,这是合法形式之一
()()这就是括号外面有括号,除此之外没有合法形式
如果你认为(())()这样是例外,可以把他看作两种情况结合,上面只是讲述的base case

这里需要对括号进行配对,所以,我们首先考虑第二种,也就是对于一个右括号需要一个左括号来匹配,而右括号就在左括号的前一位,或者说,前合理位。
这里的前合理位表示以该右括号为止,前面子串都是合理的前一位
因为右括号独自存在本就是不合理的,所以的的前合理位是0,合理位的前一位就是-1。所以这里也就是(这个符号。

如果我们明白要满足一个括号序列是合理的,也就是在新的右括号出现时,必须要有左括号与之对应,而出现的位置也就是上述提到的前合理位的前一位,如果这个描述不清楚,不过没关系。
我们考虑第一种情况,当遍历到第一个右括号时,仅需要对比前一个位置就可以知道中间是满足的,而后一个右括号呢,这里如果使用前合理位前一位去对比这个括号的话,那么他前面已经出现了两位的合理字串,还需要继续前一位,所以就是第一个左括号,正好与之对应,我们就知道这是一个4长度的合理排列,而这个序列后面如果接着的是右括号,那么后面这个右括号的合理位应该就有4,而在前一位就应该距离5。

一种思路就是计算长度为n时的最大合理序列,如果就是n,那么就是正确的,否则就是错的。子问题就会是每找到一对合理序列就将其删除,继续递归求解。

和显然,这样子对于这样的题目就是大材小用了。
上述思考是一般的括号序列的思考思路

这里直接使用栈来描述这个场景。
设置一个栈,记录左括号的情况,遇见左括号就入栈,遇见右括号就出栈比对,如果遇见右括号的时候栈已经空了,说明就没有左括号,那么就是不匹配的,如果当遍历结束后栈还没空,那么就是左括号多了,也是不匹配的。
Code

public static boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();
        for(int i = 0;i<s.length();i++){
            char t = s.charAt(i);
            if(t == '('){
                stack.push(')');
            }else if(t == '['){
                stack.push(']');
            }else if(t == '{'){
                stack.push('}');
            }else {
                if(stack.isEmpty()){
                    return false;
                }
                if(stack.pop() != t){
                    return false;
                }
            }
        }
        return stack.isEmpty();
    }

这里我想考虑写一下他的递归版本,emmm,我还是太菜了。

括号生成

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

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

示例 2:
输入:n = 1
输出:[“()”]

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

根据上面的描述信息:
括号的合法有两种状态,一种包含,一种是相邻。所以可以将问题划分为小的子问题去解决。

当要知道1 对括号有多少组合时,可以知道,就一种。
而 要知道 2 对括号有多少组合时,可以知道,要不放括号里面,要不放括号外面,也就是两种。
而要知道 3 对括号有多少组合式,可以知道,随意拿出一个括号,而剩余两个括号要不放括号里面,要不放外面。也就是5
这里归纳一下,假如有n对括号,随意拿出一个括号,而其他括号或是放在里面,或是放在这个括号外面,这里假设放在里面的对数有k对,那么放在外面的也是n-1-k 对,右面用p来表示这个n-1-k。
那么所有的对数就是穷举k的情况求和就可以知道。

以上面的 n =3 为例做出推演:
假设 k = 0;那么 p =2 所以,形成的情况就会是()+两个的情况。这里两个的情况就是()或(()),所以该情况的就会有()()()和()(())
假设 k = 1;那么 p = 1 所以,形成的情况只会有一种(())(),也就是一个在里面,一个在外面
假设 k = 2;那么 p = 0 所以,形成的情况会是(+两个情况)就会是(()())和((()))至此,穷举就好了。

可以直接使用三部曲解法:
Code

// 暴力递归
private static ArrayList<String> insert_ok(int n){
        ArrayList<String> result = new ArrayList<>();
        if(n == 0){
            result.add("");
            return result;
        }
        if(n == 1){
            result.add("()");
            return result;
        }

        for(int k = 0;k<=n-1;k++){
            ArrayList<String> K = insert_ok(k);
            ArrayList<String> P = insert_ok(n-1-k);
            for (String s : K) {
                for (String s1 : P) {
                    result.add("("+s+")"+s1);
                }
            }
        }
        return result;
    }

DP


// 自底向上
private static ArrayList<String> getList(int n){
        HashMap<Integer,ArrayList<String>> map = new HashMap<Integer,ArrayList<String>>();
        ArrayList<String> n1 = new ArrayList<>();
        n1.add("");
        map.put(0,n1);
        n1 = new ArrayList<>();
        n1.add("()");
        map.put(1,n1);
        for(int i = 2;i<=n;i++){
            ArrayList<String> result = new ArrayList<String>();
            for(int k = 0;k<=i-1;k++){
                for (String s : map.get(k)) {
                    for (String s1 : map.get(i - 1 - k)) {
                        result.add("("+s+")"+s1);
                    }
                }
            }
            map.put(i,result);
        }
        return map.get(n);
    }

像这样的解法对于java来说就跟吃土一样,能ac这道题,但是,最佳的当属与图算法中的深度优先搜索了。这里不展开了,车到山头必有路的,后话后话,这里埋个点,直接给出解法就好,其实也是dp差不多。

Code

 private static void print(int right, int left, String s, ArrayList<String> res,int n){
        if(left>n || right>n){
            return ;
        }

        if(left == n && right == n) {
            res.add(s);
        }

        if(left>=right){
            String s1 = new String(s);
            print(right,left+1,s+"(",res,n);
            print(right+1,left,s1+")",res,n);
        }
    }

最长有效括号

给你一个只包含 ‘(’ 和 ‘)’ 的字符串,找出最长有效(格式正确且连续)括号子串的长度。

示例 1:
输入:s = “(()”
输出:2
解释:最长有效括号子串是 “()”

示例 2:
输入:s = “)()())”
输出:4
解释:最长有效括号子串是 “()()”

示例 3:
输入:s = “”
输出:0

提示:
0 <= s.length <= 3 * 104
s[i] 为 ‘(’ 或 ‘)’
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/longest-valid-parentheses
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

这道题目的解法不需要使用技巧,看一眼这是困难题,数据量也比较大,直接往dp上想就好了。

巧用第一题的理论,也就是判断是否合理,就是看他的合理位前面的合理长度加本身长度。
换句话说,如果自己是合理的,意思就是说自己包括的里面也就是合理的,只需要考虑外面是否合理就好,也就是合理位的前一位或者后一位,为了方便,直接就采用减而治之的思路,只考虑前面的就好。

这里不在过多赘述怎样暴力递归的,仅需要知道,我们的子问题就是以i为下标的最长合理序列长度。这里以i为下标的元素一定要在合理序列中。

那么就会有n个解,直接想到用n长的数组来存放这些解,而这些解里面的最大值就会是问题的解。
那怎么获取第i下标的最长合理序列呢?
思路如下

当元素是左元素时,那么必然是不合理的,因为我们只考虑左边,而序列合理的话那么右括号不可能出现在左括号的左边,所以遇见左括号就直接是不合理的。
而当遇见右括号怎么判断合理性呢?
这时候就要考虑前合理位了。
因为单独一个右括号也是不合理的,所以初始值也就是0,那么他的合理位也就是0,所以前一位就是-1,那么我知道如果他前合理位是左括号,那么就证明这两个可以组合一个合理序列,更新状态为2,意思就是两个长度的合理序列。他的前合理位也就是-3了。
这样,可以知道递归方程的状态检测的是前合理位。

当合理长度前一位可以与i位置匹配,则dp[i] = dp[i-1]+2
也就是这一位的状态会随着前一个子问题变化而变化,变化的过程也就是连续的合理。

那么怎么暂存合理位的位置呢?
这里不需要开辟一个新的空间,如果前一位是合理的,那么就会记入前面的合理长度,在其基础上减一就是了。
现在也就解决了连续右合理性,还有放在括号外的怎么确定呢?很简单,直接加上前合理位的子问题解就好。如果合理序列前的子问题有解,那么肯定可以加入,我们之前就定义这个元素一定要在序列中,所以连接后还是有效的。
这样就可以完成这个题目了。

Code

public int longestValidParentheses(String s) {
        String s1 = ")"+s;
        return longestValidParenthesesWithArray(s1);
    }

    public static int longestValidParenthesesWithArray(String s) {
        int[] dp = new int[s.length()];
        int result = 0;
        for(int i = 1;i<s.length();i++){
            // 是否合理
            if(s.charAt(i) == ')' && s.charAt(i-dp[i-1]-1) == '('){
                dp[i] = dp[i-1]+2;
                dp[i] += dp[i-dp[i-1]-2];
            }
            result = Math.max(result,dp[i]);
        }
        return result;
    }

这里需要注意的是,提前对这个字符串进行了处理,其实是不必要的,可以改一下下标就OK。
时间复杂度就是O(n)
空间也是O(n)
提交后一看,2ms,尽然还有比这快的。好吧,我菜,做不来了。如果要降到lgn,我需要证明一下是否可行。不得不说,算法真美。
在这里插入图片描述

如果动态规划不太熟悉的话,可以看一下另一篇文章
人人都会动态规划
这篇主要针对括号问题来记录。回见。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BoyC啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值