L22括号生成

给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。

例如,给出 n = 3,生成结果为:

[
“((()))”,
“(()())”,
“(())()”,
“()(())”,
“()()()”
]

动态规划方法
在这里插入图片描述
这里使用了LinekdList来实现二维数组,主要在于不知道数组的大小
String el = “(” + s1 + “)” + s2;
单纯地考虑左边q右边p,q+p=n要考虑繁重的重复问题, 最左边用一个括号括住,以此来切断左右的联系,从而去除重复的问题

public List<String> generateParenthesis(int n) {
        LinkedList<LinkedList<String>> result = new LinkedList<LinkedList<String>>();
        //dp二维数组,其中一维对应所需要的答案,一维就是集合了,可以直接返回
        if (n == 0)
            return result.get(0);
        LinkedList<String> list0 = new LinkedList<String>();
        list0.add("");//这一句必须得写上,不然会少很多种情况,没有的话不等同于空字符串
        result.add(list0);//n == 0;

        LinkedList<String> list1 = new LinkedList<String>();
        list1.add("()");
        result.add(list1);//n= 1对应的第一种情况,恰好为一对括号

        for (int i = 2; i <= n; i++) {//计算dp数组从2开始,从1-n,也就是与实际一一对应
            LinkedList<String> temp = new LinkedList<String>();
            for (int j = 0; j < i; j++) {//一对括号里面可以从0组开始
                List<String> str1 = result.get(j);//括号内
                List<String> str2 = result.get(i - 1 - j);//综合恰对应为i组
                for (String s1 : str1) {
                    for (String s2 : str2) {//组合
                        String el = "(" + s1 + ")" + s2;
                         //String s3 =  s1 + "(" + s2 + ")";这个也是一样的效果
                        //String s3 =  s1 + "()" + s2;这个就不行
                        temp.add(el);//放在最里层循环,接收所有的结果
                    }
                }
            }
            result.add(temp);
        }
        return result.get(n);
    }

2.回溯法(DFS)

由于String 的特殊性,每次拼接均生成了一个新的对象,所以不用再回溯,即撤销刚才的修改;
1.先用String, 具体解析看Stringbuffer

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> ans = new ArrayList<>();
        if(n == 0) return ans;
        dfs("", n, n, ans);//这里就利用String的特性,每次拼接生成一个新的对象
        return  ans;
    }
    void dfs(String st, int left, int right, List<String> ans){
        if(left == 0 && right == 0){
            //String ss = new String(st);//这里不new也是新对象
            ans.add(st);
            return;
        }

        //剪枝,这里left,right表示剩下的个数,即剩下的right过多不满足条件
        if(left > right) return;

        if(left > 0){
            dfs(st + "(", left - 1, right, ans);
        }
        if(right > 0){
            dfs(st + ")", left, right - 1, ans);
        }
    }
}

2.尝试用StringBuffe替代,但是并未成功,分析原因

import java.util.ArrayList;
import java.util.List;

public class Solution {

    // 做减法

    public List<String> generateParenthesis(int n) {
        List<String> res = new ArrayList<>();
        // 特判
        if (n == 0) {
            return res;
        }

        // 执行深度优先遍历,搜索可能的结果
        StringBuffer sb = new StringBuffer();
        dfs(sb, n, n, res);
        return res;
    }

    /**
     * @param curStr 当前递归得到的结果
     * @param left   左括号还有几个可以使用
     * @param right  右括号还有几个可以使用
     * @param res    结果集
     */
    private void dfs(StringBuffer curStr, int left, int right, List<String> res) {
        // 因为每一次尝试,都使用新的字符串变量,所以无需回溯
        // 在递归终止的时候,直接把它添加到结果集即可,注意与「力扣」第 46 题、第 39 题区分
        if (left == 0 && right == 0) {
        //这里每次添加都是new一个新的对象,但是对应下面要回溯
            String st = new String(curStr);
            //res.add(curStr.toString());
            res.add(st);
            return;
        }

        // 剪枝(如图,左括号可以使用的个数严格大于右括号可以使用的个数,才剪枝,注意这个细节)
        if (left > right) {
            return;
        }

        if (left > 0) {
            curStr.append("(");
            dfs(curStr, left - 1, right, res);
            
        }

        if (right > 0) {
            curStr.append(")");
            dfs(curStr.append(")"), left, right - 1, res);
           
        }
        //回溯操作
        //此题情况特殊在于,同时两次dfs,如果进行回溯,并不知道上面两次哪次成功了,
        //虽然我先加上了相同的判断条件,但是下面删除deleteCharAt()对应的index
        //无法确定是最后一个还是倒数第二个,所以导致无法得出正确结果
        if(left + 1 > 0){
            curStr.deleteCharAt(curStr.length() - 1);
        }
        if(right + 1 > 0){
            curStr.deleteCharAt(curStr.length() - 1);
        }
    }
}

3.暴力法

3.1将2中方法直接改造为暴力法
其实不是暴力方法,就是稍微变一下形式

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> ans = new ArrayList<>();
        if(n == 0) return ans;
        dfs("", n, n, ans);//这里就利用String的特性,每次拼接生成一个新的对象
        return  ans;
    }
    boolean judge(String st){
        int len = st.length();
        int left = 0, right = 0;
        for(int i = 0; i <len; i++){
            if(st.charAt(i) == '(') left++;
            if(st.charAt(i) == ')') right++;
            if(left < right) return false;
        }
        return left == right;
    }
	//这个同样是可以判断的
	boolean judge2(String st){
        int len = st.length();

        int cnt = 0;
        for(int i = 0; i < len; i++){
            if(st.charAt(i) == '(') cnt++;
            else if(st.charAt(i) == ')') {
                cnt--;
                if(cnt < 0) return true;
            }
        }

        //return true;//这样是十分不严谨的
        if(cnt > 0) return false;
        else return true;
    }
    void dfs(String st, int left, int right, List<String> ans){
        if(left == 0 && right == 0 && judge(st)){
            //String ss = new String(st);//这里不new也是新对象
            ans.add(st);
            return;
        }
        //if(left > right) return;//这个剪枝不要也是可以通过的
        //如果加上的话,就不用judge来判断了
        if(left < 0) return;//增加的边界条件
        if(right < 0) return;//注意这些都需要放在dfs的前面,不然会超时
        dfs(st + "(", left - 1, right, ans);
        dfs(st + ")", left, right - 1, ans);
    }
}

3.2换用数组来暴力解决,其实方法更清晰些
直接用char[2n]数组来盛放

因为不涉及字符,不如存储在数组里,操纵比较方便

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> ans = new ArrayList<>();
        if(n == 0) return ans;
        dfs(new char[2*n], 0, ans);
        return  ans;
    }
   void dfs(char[] ch, int index, List<String> ans){
        if(index == ch.length){
            if(valid(ch)){//这个应该放里面,如果放外围增加复杂度
                ans.add(new String(ch));//注意这里就需要new一个了
                //return;//对于无返回值,不return也是可以的
            }
        }else{//如果不放在else中,会数组越界,具体加不加else,视题意而定
            ch[index] = '(';
            dfs(ch, index +1, ans);
            ch[index] = ')';
            dfs(ch, index +1, ans);
        }
        
   }
    boolean valid(char[] str){
        int len = str.length;
        int left = 0, right = 0;
        for(int i = 0; i <len; i++){
            if(str[i] == '(') left++;
            if(str[i] == ')') right++;
            if(left < right) return false;
        }
        return left == right;
    }
}

4.使用队列,自建栈

4.1队列

class Solution {
    class Node{
        //left,right 用来模拟两个分支,存放对应的个数,有点像指针
        //val用来存放当前节点的值,是左括号,还是右括号
        int left;
        int right;
        String res;

        public Node(int left, int right, String res){
            this.left = left;
            this.right = right;
            this.res = res;
        }
    }

    public List<String> generateParenthesis(int n) {
        List<String> list = new ArrayList<>();
        if(n == 0) return list;

        Node node = new Node(n, n, "");
        //根节点为空字符串

        LinkedList<Node> q = new LinkedList<>();
        q.add(node);

        while(!q.isEmpty()){
            Node now = q.poll();
            //left == null类似
            if(now.left == 0 && now.right == 0){
                list.add(now.res);//可以放入结果集中
            }

            if(now.left > 0){//只要>0就可以添加
                //类似于if left != null
                q.add(new Node(now.left - 1, now.right, now.res + "("));
            }

            if(now.right > 0 && now.left < now.right) {//这个需要满足left <right
                //q.add(new Node(n - 1, n,now.res + "(");不能直接写n,要用当前now代替具体的值
                q.add(new Node(now.left, now.right - 1, now.res + ")"));
            }
        }

        return list;
    }
}

2.使用自己的栈,不要系统栈,仅仅是将add改为了push

class Node{//内部类构建
        int left;//其实还是相当于指针了,通过了left, right判断究竟往左还是往右
        int right;
        String res;//相当于val,一步一步集成

        public Node(int left, int right, String res) {
            this.left = left;
            this.right = right;
            this.res = res;
        }
    }
class Solution {
    
    public List<String> generateParenthesis(int n) {
        List<String> ans = new ArrayList<>();
        if(n == 0) return ans;
        LinkedList<Node> q = new LinkedList<>();

        Node now = new Node(n, n, "");//根节点为空字符串

        q.add(now);
        while (!q.isEmpty()){
            now = q.pop();
            //边界条件
            if(now.left == 0 && now.right == 0){
                //此时括号已分配完毕
                ans.add(now.res);
            }
            if(now.left > 0){
                //q.add(new Node(n - 1, n,now.res + "(");不能直接写n,要用当前now代替具体的值
                q.push(new Node(now.left - 1, now.right, now.res + "("));
            }
            if(now.right > 0 && now.left < now.right){
                //q.add(new Node(n, n - 1, now.res + ")"));
                q.push(new Node(now.left, now.right - 1, now.res + ")"));
            }
        }
        return  ans;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值