https://leetcode-cn.com/problems/generate-parentheses/submissions/
解法一
采用暴力解法,即找出所有可能的组合,挑选出其中有效的作为答案。一共有n对括号,也即字符串长度为2n,每个位置2个选择,所以一共有 2 2 n 2^{2n} 22n个可能的组合。
- 如何生成所有可能的组合?
采用递归的深度优先搜索是最方便有效的方法。搜索的最大深度就是2n,此时判断组合 是否有效并添加到答案中。 - 如何判断组合是否有效?
由于只有"()",无需借助栈,直接通过比较左右括号的个数即可。遍历字符串的过程中左括号个数一定不能小于右括号个数,最终左括号和右括号个数一定要相同。
代码
注意,用char数组而不是用string,效率更高,原因有两点:
- 数组作为函数参数,默认传递的是引用,不会额外造成开销
- 即便采用string的引用,也需要对string进行添加和删除最后一个元素的操作,额外耗时
亲测使用char数组比使用string的引用耗时更少,如果用的是string的拷贝的话,耗时会非常大。
另外要注意,由于我们是对char数组中一个个地进行赋值,因此当转string时,要在最后添加一个\0,这是c++的特点。
Solution {
public:
vector<string> generateParenthesis(int n) {
vector<string> result;
generateAll(result, 0, 2 * n, new char [2 * n + 1]);
return result;
}
private:
void generateAll(vector<string>& result, int pos, int length, char current[]) {
if (pos == length) {
if (isValid(current, length)) {
current[length] = '\0';
result.push_back(current);
}
}
else {
current[pos] = '(';
generateAll(result, pos + 1, length, current);
current[pos] = ')';
generateAll(result, pos + 1, length, current);
}
}
bool isValid(char str[], int length) {
int left = 0, right = 0;
for (int i = 0; i < length; ++i) {
if (str[i] == '(') left++;
else right++;
if (left < right) return false;
}
return left == right;
}
};
复杂度分析
一共有
2
2
n
2^{2n}
22n个组合,用于生成一个组合的时间复杂度是
O
(
2
n
)
=
O
(
n
)
O(2n)=O(n)
O(2n)=O(n),用于判断一个组合的时间复杂度是
O
(
2
n
)
=
O
(
n
)
O(2n)=O(n)
O(2n)=O(n),因此总的时间复杂度是
O
(
2
2
n
n
)
O(2^{2n}n)
O(22nn)。
除了用于存储答案的数组外,递归也需要占用空间,一层递归函数需要
O
(
1
)
O(1)
O(1)的空间,最多递归2n层,因此总的空间复杂度为
O
(
n
)
O(n)
O(n)。
解法二
采用回溯算法来优化暴力解法,回溯其实就是采用了剪枝的遍历,在构造一个可能的解的过程中,如果确定这个不满足求解条件,就“回溯返回”。
每当我们要往字符串的末尾添加一个"("或者")"时,先要判断这样会不会导致生成的组合无效。
要注意,在生成一个有效组合的过程中,它的左括号个数left和右括号个数right始终是会满足两个条件的:
left <= n,right <= nleft > right
这样一来,我们不仅剪枝掉了一些不必要的搜索,还能保证最终生成的组合就是有效的解。
代码
class Solution {
public:
vector<string> generateParenthesis(int n) {
vector<string> result;
string tmp = "";
generateAll(result, 2 * n, tmp, 0, 0);
return result;
}
private:
void generateAll(vector<string>& result, int length, string& current, int left, int right) {
if (current.length() == length) result.push_back(current);
else {
if (left < length / 2) {
current += "(";
generateAll(result, length, current, left + 1, right);
current.pop_back();
}
if (left > right) {
current += ")";
generateAll(result, length, current, left, right + 1);
current.pop_back();
}
}
}
};
复杂度分析
难以直接分析这种解法的时间复杂度,但有相关证明是 O ( 4 n n ) O(\frac{4^n}{\sqrt{n}}) O(n4n)。空间复杂度与解法一类似,为 O ( n ) O(n) O(n)。
解法三
回溯的方法简单易懂,效率也不错,但动态规划也能很好地解决此题。
- 首先定义状态
dp[i]:由i对括号所组成的所有有效组合。 - 再考虑定义状态转移方程:对于
dp[i]中的一个组合,它比dp[i-1]中的组合多了一个(和一个),我们要考虑的就是怎样有效地把这两个字符插入到这些组合中去。
这好像很难。
转换一下思路,一个组合一定是以(开头,以)结尾的。我们可以固定(和),转而去寻找在它们中间的可能的合法的括号对,剩下的括号对则放到)的右边:
dp[i] = "(" + dp[可能的括号对数] + ")" + dp[剩下的括号对数]
可能的括号对数j的范围是0 <= j <= i - 1,剩下的括号对数是i - 1 - j。 - 定义初始状态
dp[0] = {""}。
代码
class Solution {
public:
vector<string> generateParenthesis(int n) {
vector<vector<string>> dp(n + 1); // dp[i]:用i对括号可能生成的组合
dp[0] = {""};
for (int i = 1; i <= n; ++i) {
for (int j = 0; j < i; ++j) {
for (auto str1 : dp[j])
for (auto str2 : dp[i - j - 1])
dp[i].push_back("(" + str1 + ")" + str2);
}
}
return dp[n];
}
};
本文深入探讨了LeetCode上的括号生成问题,提供了三种解法:暴力法、回溯算法和动态规划。每种方法都详细讲解了实现原理,代码示例及复杂度分析。
365

被折叠的 条评论
为什么被折叠?



