22.括号生成。(递归,深搜,回溯,图的遍历,动态规划)

1 篇文章 0 订阅
1 篇文章 0 订阅

题目

22. 括号生成

难度中等

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

 

示例 1:

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]

示例 2:

输入:n = 1
输出:["()"]

 

提示:

  • 1 <= n <= 8

解法一:(图的遍历)

拿到题首先想到的是将所有的方案都遍历一遍,然后筛选出所需要的组合,后来发现不行,因为这样暴力破解需要多层循环,确切的几层循环是不确定的,没有办法做,然后想到之前蓝桥杯练习过迷宫遍历,看了看之前自己的博客,想到了把它转化成迷宫进行深度搜索,递归,回溯,左括号右括号可以用1 和-1来表示, 在深搜过程中要保证左括号和右括号匹配 也就是保证每次1或-1 相加不能为负数,例如())是不行的 ,这样就减少了一部分的递归时间。

如下图所示,从最左上角开始,到最右下角结束,走迷宫,只能走下,左下,右下,如果越界了,或者路径上的数字相加小于0,就返回选择另一条路,这样递归,走到右下角最后一个,就存储一下路径(1是左括号,-1是有括号)存储完接着返回上一步 尝试另外一种走法,直到所有走法都走完,也就遍历完了, 也算深度优先遍历。

class Solution {

public static int dfs(int x, int y, int n, int[][] arr, String str,int sum,List<String> list){

        if(x>=n ||x<0  || y<0 || y>1||sum<0){
            return 0;

        }
        else{
            if(arr[x][y]==1){
                str+="(";
                sum+=1;
            }
            else if(arr[x][y]==-1){
                str+=")";
                sum+=-1;
            }
            if(x==n-1 && y==1&&sum==0){
                list.add(str);
                return 0;
            }
            x++;
            dfs(x,y,n,arr,str,sum,list);
            x--;

            y++;x++;
            dfs(x,y,n,arr,str,sum,list);
            y--;x--;

            y--;x++;
            dfs(x,y,n,arr,str,sum,list);
            y++;x--;
        }
        return 0;
    }

    public static List<String> generateParenthesis(int n) {
        List<String> list =new ArrayList<>();
        n=n*2;
        String str = "";
        int[][] arr =new int[n][2];
        for(int i=0;i<n;i++){
            arr[i][0]=1;
            arr[i][1]=-1;

        }
        dfs(0,0,n,arr,str,0,list);
        return list;

    }

}

解法二:(深搜+回溯)

发现解法一时间不是很快,想到了优化一下,将2*2n的格子优化成2*n的格子,再优化成两个1*n的格子,最后简化成两个变量,进行指针移动。

可以作为两个长度为n的一维数组来理解,x指向第一个数组,y指向第二个数组

两个指针x=0,y=-1,必须满足条件x>=y同时 x,y都小于等于2      才能进行x++,或者y++ 。进行递归。递归过程中记录x为左括号,y为右括号。(为什么x是0,y是-1?因为第一步肯定是加入左括号,所以一开始运行的时候就先默认x=-1,x++了)

flag是记录这次移动的是哪个数组,如果移动的是x指针指向的数组,那么就加入左括号,如果移动的是y指针指向的数组,就加入右括号。

最后代码如下。

class Solution {
        public static int dfs(int x, int y, int n, String str,List<String> list,int flag){

            if(flag==1){
                str+="(";
            }
            else if(flag==0) {
                str+=")";
            }
            if(x==n-1 && y==n-1){
              list.add(str);
              return 0;
            }

            if(x+1<n && x+1>=y) {
                dfs(x + 1, y, n, str, list, 1);
            }
            if(y+1<n && x>=y+1) {
                dfs(x,y+1,n,str,list,0);
            }
        return 0;
    }

    public static List<String> generateParenthesis(int n) {
        List<String> list =new ArrayList<>();
        String str = "";
        dfs(0,-1,n,str,list,1);
        
        return list;

    }
}

解法三:暴力破解

前面两个解法是自己写的,后来看官方题解发现 可以用暴力破解法,就是递归遍历所有情况 最后再判断是否符合 ,用一个长度为8的字符数组存储字符串,每次添加左括号或者右括号,返回的时候记得回溯删掉。

 public static int dfs(int x,int n, char[] str,List<String> list){
        if(x==n){
            int sum=0;
            for (char s : str) {
                if(s=='('){//
                    sum++;
                }
                else if(s==')'){
                    sum--;
                }
                if(sum<0){
                    break;
                }

            }
            if (sum==0){
                list.add(new String(str));//
            }
        }
        else{
            str[x]='(';
            dfs(x+1,n,str,list);
            str[x]=' ';
            str[x]=')';
            dfs(x+1,n,str,list);
            str[x]=' ';
        }

        return 0;
    }

    public static List<String> generateParenthesis(int n) {
        n=n*2;

        List<String> list =new ArrayList<>();
        char[] str =new char[n];//
        dfs(0,n,str,list);


        return list;

    }

Tips:

1.char[] 中的每个字符 是单引号,String[]中的每个字符是双引号。不要记混了;

2.字符数组转化为 字符串 要用new String(char);

arr.toString()和String.valueOf(arr2)也行,但是,tostring()返回的是地址

3.这里最好用char[] 而不用包装类Character  因为有些函数用包装类会出错

 

解法四:(a)b递归

用一个大的LIst存储List<String> 也就是n为1,2,3.....n时的答案。 递归思想,( a)b  左括号在第一个位置,右括号的位置只能是第2,4,6,....位置,当在第二位置的时候也就是 ()b,这时候只要求n-1的括号生成 也就是b字符串的排列,同理。右括号在第4位置上,(a)b ,a只能是() ,b是n-2的排列括号。以此类推,用递归做。

static List[] allList = new ArrayList[100];

    public static List<String> dfs(int n){
        if(allList[n]!=null){
            return allList[n];
        }


        else {
            ArrayList<String> a =  new ArrayList<String>();
            if(n==0){

                a.add("");

            }
//
            for (int k = 0; k <n; k++) {
                for (String df : dfs(k)) {
                    for (String strings : dfs(n - k - 1)) {//

                        a.add("(" + df + ")" + strings);//list.get(i)

                    }

                }


            }
            allList[n]=a;
        }
        return allList[n];
    }

    public static List<String> generateParenthesis(int n) {


        List<String> list =new ArrayList<>();




        return dfs(n);

    }

Tiips:

list.get(i),是求第i个元素。必要的时侯用 iter 遍历会方便一些。

解法四是用了记忆化搜索,一般的题用常规的递归可以用记忆化搜索来优化,再用动态规划优化,在这个题中,写java代码效率不太高,思路很好,用在这个题中不算很合适,所以就不仔细写这一系列的解法了 以后碰到相似的题再说,记忆化搜索,是调用函数时为了减少重复的调用,将结果保存下来,下次调用直接取。动态规划优化的时候是不用递归,改用for循环,效率更高,可以拿斐波那契的例子参考一下思路 ,看https://blog.csdn.net/midnight_time/article/details/89760858?spm=1001.2014.3001.5501 的博客。

 

解法五:广度优先搜索

参考:

作者:liweiwei1419
链接:https://leetcode-cn.com/problems/generate-parentheses/solution/hui-su-suan-fa-by-liweiwei1419/
来源:力扣(LeetCode)

采用队列,先进先出,并且保证过程中 左括号的剩余数<右括号的剩余数  并且剩余数>=0,用队列<结点> 来存储, 例如n=3 时,  

( , (( , () , ((( , (() ()( , ((() , (()( , (()) , ()(( , ()() , ((()) , (()() , (())( , ()(() , ()()( , ((())) , (()()) , (())() , ()(()) , ()()()    

这是队列的 存储情况,为了理解可以参考题解的作者画的二叉树。

image.png

 

 

 

     static class Node {//
        private String str;
        private int left;
        private int right;

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

    public static List<String> generateParenthesis(int n) {
        List<String> ans = new ArrayList<>();
        Queue<Node> queue= new LinkedList<>();//创建队列的方法;
        queue.offer(new Node("",n,n));//
        while(!queue.isEmpty()){
            Node tempNode = queue.poll();//

            if (tempNode.left==0 && tempNode.right==0){
                ans.add(tempNode.str);

            }
            if (tempNode.left>0){
//                System.out.println("1");//Java外部类可以访问内部类private变量
                queue.offer(new Node(tempNode.str+"(",tempNode.left-1,tempNode.right));

//                System.out.println(queue.element());
            }
            if(tempNode.right>0 && tempNode.left<tempNode.right){
                queue.offer(new Node(tempNode.str+")",tempNode.left,tempNode.right-1));

            }

        }



        return ans;

Tips:

1.这里用了一个内部类来存储 当前字符串 、当前还剩多少左右括号。(注意这个node不是链表中的node,最后那个node函数是构造函数)  如果node里面的类型一样,我觉得可以用数组代替,可惜里面是String int int类型,在c语言中用结构体可以定义,不知道在java里面是不是除了定义类还有别的方法。

2. Queue<Node> queue= new LinkedList<>();//创建队列的方法;

3./Java外部类可以访问内部类private变量   代码中在内部类和方法在同一级,方法中居然可以直接调用Node中的private的变量 tempNode.left.  

4.queue的操作:(摘自runoob菜鸟教程)

//add()和remove()方法在失败的时候会抛出异常(不推荐)
        Queue<String> queue = new LinkedList<String>();
        //添加元素
        queue.offer("a");
        queue.offer("b");
        queue.offer("c");
        queue.offer("d");
        queue.offer("e");
        for(String q : queue){
            System.out.println(q);
        }
        System.out.println("===");
        System.out.println("poll="+queue.poll()); //返回第一个元素,并在队列中删除
        for(String q : queue){
            System.out.println(q);
        }
        System.out.println("===");
        System.out.println("element="+queue.element()); //返回第一个元素 
        for(String q : queue){
            System.out.println(q);
        }
        System.out.println("===");
        System.out.println("peek="+queue.peek()); //返回第一个元素 
        for(String q : queue){
            System.out.println(q);
        }

运行结果:

a
b
c
d
e
===
poll=a
b
c
d
e
===
element=b
b
c
d
e
===
peek=b
b
c
d
e

 

其他解法:

二叉树的深搜遍历也是一种思路,也可以试试,归根到底,解法一二三和二叉树深搜的基本想法都是一样的,都是深搜+回溯,只不过理解的方式不同,剪枝优化程度不同,存储不同。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
建立二叉链表的算法步骤如下: 1. 定义二叉树结构体; 2. 定义一个栈,用于存储未构建完整的子树; 3. 从根节点开始遍字符序列; 4. 如果当前字符为左括号,说明根节点还没有构建完整的左子树,将当前节点入栈; 5. 如果当前字符为叶子节点,则将其作为当前根节点的左儿子节点; 6. 如果当前字符为右括号,则说明当前子树已建立完整,需要回溯到根节点。此时从栈顶取出根节点,将其右儿子节点设为当前节点; 7. 重复4-6直到完成遍。 中序遍归算法如下: 1. 若根节点为空,则返回; 2. 对根节点的左子树进行归遍; 3. 输出当前根节点值; 4. 对根节点的右子树进行归遍。 实现代码如下: struct TreeNode { char val; TreeNode* left; TreeNode* right; TreeNode(char c) : val(c), left(nullptr), right(nullptr) {} }; void buildTree(TreeNode*& root, string& s, int& i, stack<TreeNode*>& stk) { if (i == s.size() || s[i] == ')') return; if (s[i] == '(') { stk.push(root); i++; } root = new TreeNode(s[i]); if (!stk.empty() && stk.top()->left == nullptr) { stk.top()->left = root; } else if (!stk.empty()) { stk.top()->right = root; stk.pop(); } i++; buildTree(root->left, s, i, stk); buildTree(root->right, s, i, stk); } void inorderTraversal(TreeNode* root) { if (!root) return; inorderTraversal(root->left); cout << root->val; inorderTraversal(root->right); } int main() { string s = "A(B(C))(D)"; int i = 0; TreeNode* root = nullptr; stack<TreeNode*> stk; buildTree(root, s, i, stk); inorderTraversal(root); return 0; }

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值