题目概述
题目链接:点我做题
题解
一、暴力解法
老实说,我暴力解法没看答案之前都实现不出来,因为我对递归太不熟悉了,这里递归是用来生成长度为n的括号字符串的,所以函数参数里头有n,表示要生成的括号字符串长度,用来比对是否已经生成长度为n的括号。
来看递归函数主体,先判断当前串的长度是否已经等于n,如果相等了,就去进行一次括号是否合法的判断,如果合法,就把串加入答案数组中,然后返回上一层;
如果串的长度不够n,我们就要增加串的长度。可以先增加左括号,然后递归的进入
_
g
e
n
e
r
a
t
e
a
l
l
p
r
o
b
a
b
l
i
t
y
\_generateallprobablity
_generateallprobablity;返回后,因为我们传的是串的引用,所以先pop()掉之前加入的左括号,然后增加右括号,递归的进入
_
g
e
n
e
r
a
t
e
a
l
l
p
r
o
b
a
b
l
i
t
y
\_generateallprobablity
_generateallprobablity,然后返回后再pop()掉加入的右括号,然后这个函数结束,返回上一层.
至于判断括号是否合法,可以用栈,但是有点浪费空间,我们发现如果括号合法,那么在遍历的过程中左括号个数一定大于等于右括号个数,并且遍历结束后左右括号个数一定相等,据此,我们可以用一个left变量标识当前左括号比右括号多的个数,如果遇到左括号,left++,如果遇到右括号,left–,每次还要检查left是否小于0,如果小于0,说明括号不合法,直接返回false即可。出了循环后还要检查left是否等于0,如果不等于0返回false,如果等于0返回true。
代码:
class Solution {
public:
vector<string> generateParenthesis(int n)
{
string tmp;
vector<string> ret;
_generateallprobablity(tmp, 2 * n, ret);
return ret;
}
void _generateallprobablity(string& tmp, int n, vector<string>& ans)
{
if (tmp.size() == n)
{
if (isValid(tmp))
{
ans.push_back(tmp);
}
return;
}
//利用递归枚举所有情况
tmp += '(';
_generateallprobablity(tmp, n, ans);
tmp.pop_back();
tmp += ')';
_generateallprobablity(tmp, n, ans);
tmp.pop_back();
}
bool isValid(string& s)
{
//判断是否为有效括号的条件是
//左括号数大于等于右括号数 且结束时左右括号数相等
int left = 0;
int len = s.size();
for (int i = 0; i < len; i++)
{
if (s[i] == '(')
{
left++;
}
else
{
left--;
}
if (left < 0)
{
return false;
}
}
return left == 0;
}
};
时间复杂度:
O
(
n
2
2
n
)
O(n2^{2n})
O(n22n),判断一次是否合法n,所有子串的可能情况有
2
2
n
2^{2n}
22n。
空间复杂度:
O
(
n
)
O(n)
O(n),最多递归2n层
二、控制递归只生成有效的解
所有长度为2n的有效串形成过程中,左括号一定大于等于右括号,如果左右括号数相等时,后一个只能放左括号,并且左括号和右括号最大数量都是n,据此,我们可以增加两个参数left,right表示左右括号的个数,如果left和right相等都等于n,则把串加入答案数组并返回上一层;否则先检查left是否等于right,如果相等,下一个加入的就只能是’(’,然后left+1,进入下一层;如果left > right,则可以增加左括号也可以增加右括号,只要增加的是当前括号数小于n的括号就行。
注意,我们这里没有传引用,所以不用像上个方法一样回到上一层还要pop().
class Solution {
public:
vector<string> generateParenthesis(int n)
{
//利用递归 当前的左括号数一定要大于等于右括号数
//如果当前的左括号数等于右括号数 那么下一个只能放左括号
//如果当前的左括号数比右括号数大 可以放左括号也可以放右括号
vector<string> ans;
string s;
_generate(s, 0, 0, ans, n);
return ans;
}
void _generate(string s, int left, int right,
vector<string>& ans, int n)
{
if (left == n && right == n)
{
ans.push_back(s);
return;
}
if (left == right)
{
_generate(s + '(', left + 1, right, ans, n);
}
else if (left > right)
{
if (left < n)
{
_generate(s + '(', left + 1, right, ans, n);
}
if (right < n)
{
_generate(s + ')', left, right + 1, ans, n);
}
}
}
};
时间复杂度空间复杂度的分析涉及一些数学知识,就不写了,有空再写。
三、动态规划
我们为了得到n对括号组成的有效括号串,在递归的过程中其实已经得到了到n-1的所有有效括号串可能,我们可以想一下如何表示长度为2n的串,发现长度为2n的有效括号串可以看做是
′
(
′
+
p
+
′
)
′
+
q
'(' + p + ')' + q
′(′+p+′)′+q,其中p和q都是有效括号串,且p的括号对数加q的括号对数等于n - 1,p和q的长度都可以是0,这是设计动态规划的关键,所有0到n-1的有效括号串的所有情况都会在前面的计算中被我们事先记录在一个数组中,通过遍历循环就可以得到所有有n对有效括号的串的情况。
代码:
class Solution {
public:
vector<string> generateParenthesis(int n)
{
//动态规划
//思路就是我们计算n个有效括号的所有状态一定会把到n-1所有有效括号状态都记录下来
//所以第n对有效括号等于
//( + p + ) + q
//p + q = n - 1 p和q都代表一组有效括号 它们的长度和是n - 1
if (n == 1)
{
return {"()"};
}
vector<vector<string>> dp(n + 1);
//开一个dp数组 dp[i]来记录长度为i的所有有效括号状态
dp[0] = {""};
dp[1] = {"()"};
for (int i = 2; i <= n; i++)
{
for (int p = 0; p <= i - 1; p++)
{
int sz1 = dp[p].size();
for (int j = 0; j < sz1; j++)
{
int q = i - 1 - p;
int sz2 = dp[q].size();
for (int k = 0; k < sz2; k++)
{
string s = '(' + dp[p][j] + ')'
+ dp[q][k];
dp[i].push_back(s);
}
}
}
}
return dp[n];
}
};
时间复杂度空间复杂度分析需要一定的数学计算,有空了再补上。