题目
难度中等
数字 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 时,
( , (( , () , ((( , (() ()( , ((() , (()( , (()) , ()(( , ()() , ((()) , (()() , (())( , ()(() , ()()( , ((())) , (()()) , (())() , ()(()) , ()()()
这是队列的 存储情况,为了理解可以参考题解的作者画的二叉树。
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
其他解法:
二叉树的深搜遍历也是一种思路,也可以试试,归根到底,解法一二三和二叉树深搜的基本想法都是一样的,都是深搜+回溯,只不过理解的方式不同,剪枝优化程度不同,存储不同。