题目:
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且有效的括号组合。
示例:
输入:n=3
输出:
[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
解法一:暴力二叉树法
思路:
- 把所有的组合情况用二叉树表示出来 ,每一个不为空的节点代表一个前括号或者后括号。前括号标记为1,后括号标记为-1。
- 用改良版中序遍历找到合理的叶子节点。在此过程中只要控制两点:
- 路径上标记和大于等于0,即后括号数不能比前括号数多
- 叶节点的路径标记和为0,即前括号与后括号数目相等
class Node {
public:
int num;
Node* right;
Node* left;
Node(int i) :num(i) {
right = left = nullptr;
}
};
class Tree {
public:
Node* head;
vector<string> str;
Tree() {
head = new Node(1);
}
void add(Node* tem) {
if (tem->left!= nullptr&&tem->right!=nullptr) {//如果tem的孩子不空
add(tem->left);
add(tem->right);
}
else{
tem->left = new Node(1);
tem->right = new Node(-1);
}
}
void mida(Node* n,string str1,int sum) {//前提n不为空
if (n->num == -1) {
str1 += ')';
sum += -1;
}
else if (n->num == 1) {
str1 += '(';
sum += 1;
}
if (n->left != nullptr&&n->right!=nullptr) {//n不是叶节点
if (sum < 0) {//如果出现了不合理括号
return;
}
else {
mida(n->left, str1,sum);
mida(n->right, str1,sum);
}
}
else {//n是叶子节点
if (sum == 0) {//前括号与后括号数目相等
str.push_back(str1);
}
}
}
};
class Solution {
public:
vector<string> generateParenthesis(int n) {
Tree a;
for (int i = 0; i < 2*n-1; i++) {
a.add(a.head);
}
a.mida(a.head, "", 0);
return a.str;
}
};
这是我第一遍做的方法,优点:思路清晰,简单易懂;
缺点:耗时太长,内存消耗大
还是来学习大神们的方法吧:
解法二:直接递归法
思路:朴实无华的递归。
(哭了,为啥我就想不到这样做呢QAQ)
class Solution {
public:
vector<string> generateParenthesis(int n) {
vector<string> res;
func(res, "", 0, 0, n);
return res;
}
void func(vector<string>& res, string str, int l, int r, int n) {
if (l > n || r > n || r > l)
return;
if (l == n && r == n) {
res.push_back(str);
return;
}
func(res, str + '(', l + 1, r, n);
func(res, str + ')', l, r + 1, n);
return;
}
};
耗时和内存也十分优秀:
解法三:套括号法
思路:运用二维vector,由0对括号和1对括号推出2对括号的组成,再由0对、1对、2对括号推出3对括号的组成……以此类推。
这个vector dp的结构是这样的:
dp[0] | " " |
dp[1] | " () " |
dp[2] | " (())" , "()()" |
dp[3] | "((()))" , "(()())" , "()(())" , "()()()" , "(())()" |
class Solution {
public:
vector<string> generateParenthesis(int n) {
if (n == 0) return {};
if (n == 1) return { "()" };
vector<vector<string>> dp(n + 1);
dp[0] = { "" };
dp[1] = { "()" };
for (int i = 2; i <= n; i++) {
for (int j = 0; j < i; j++) {
for (string p : dp[j])
for (string q : dp[i - j - 1]) {
string str = "(" + p + ")" + q;
dp[i].push_back(str);
}
}
}
return dp[n];
}
};
解法四:往返的栈方法
(这栈真是该死的甜美,叹服)
思路:将每一步的加括号结果都弄进栈,同时标记原来的括号序列。得到一个结果后,将该结果出栈,回到初始的序列。
举个栗子:
n=2,此时的栈top为"()",status=0:
- 不能加")",status=1
- 还差一个"(", 于是进栈 "()(" ,原来的序列status=2,新进栈的序列status=0
- 栈top为"()(",加")" ,进栈"()()",原来的序列status=1,新进栈的序列status=0
- 栈top为"()()",满足要求,出栈
- 栈top为"()(",status=1,不能加"(",status=2
- 栈top为"()(",status=2,出栈
- ……
class Solution {
public:
struct Data {
string str;
int lc;
int rc;
int status;
};
stack<Data> st;
vector<string> generateParenthesis(int n) {
n *= 2;
vector<string> res;
st.push(Data{ "", 0, 0, 0 });
while (st.empty() == false) {
Data& t = st.top();
if (t.str.size() == n) {
res.push_back(t.str);
st.pop();
continue;
}
if (t.status == 2) {
st.pop();
continue;
}
if (t.status == 0) {
if (t.rc < t.lc) {
st.push(Data{ t.str + ")", t.lc, t.rc + 1, 0 });
}
t.status = 1;
continue;
}
if (t.status == 1) {
if (t.lc - t.rc < n - t.str.size()) {
st.push(Data{ t.str + "(", t.lc + 1, t.rc, 0 });
}
t.status = 2;
continue;
}
}
return res;
}
};
参考了以上代码后,感到无比快乐的我又写了列表法
方法五:暴力列表法
思路:与方法一类似,list str用来记录括号序列,list sign用来记录标记值的和。
class Solution {
public:
vector<string> generateParenthesis(int n) {
n = n * 2;
vector<string> a;
list<string> str;
list<int> sign;
str.push_back("(");
sign.push_back(1);
while (str.front().length() < n) {
if (sign.front() >= 0) {
string str1 = str.front() + "(";
str.push_back(str1);
sign.push_back(sign.front() + 1);
}
if (sign.front() > 0) {
string str2 = str.front() + ")";
str.push_back(str2);
sign.push_back(sign.front() - 1);
}
str.pop_front();
sign.pop_front();
}
while (str.empty() == false) {
if (sign.front() == 0) {
a.push_back(str.front());
}
sign.pop_front();
str.pop_front();
}
return a;
}
};
内存消耗有了很大进步,但是运行时间依旧不怎么样。
希望能早日熟练运用大神解法。
注:题目来自leetcode,部分代码来自评论区,感谢各位的分享。