给出 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;
}
}