题目:数字
n
代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合示例 1:
输入:n = 3 输出:["((()))","(()())","(())()","()(())","()()()"]示例 2:
输入:n = 1 输出:["()"]
这是一道非常非常经典的潜在剪枝的dfs问题,下面是题解
首先估计可能结果集的大小,原问题可以划归为(二元的)合法出栈序列问题,所以理论上界为卡特兰数(自行搜索)
using ullong = long long unsigned;
ullong __Cattelan__(std::size_t N, std::size_t M){
if(!N) return 1ULL;
if(!M) return __Cattelan__(N - 1, 1ULL);
return __Cattelan__(N-1, M+1)
+ __Cattelan__(N, M-1);
//通过递推公式,也可以通过记忆化搜索或迭代DP优化
}
inline ullong Cattelan(std::size_t N)
//可以证明合法出栈序列的规模是卡特兰数Cattelan(N, 2)
{ return __Cattelan__(N, 0ULL); }
下面的主调接口
std::vector<std::string> Generator(std::size_t N){
std::vector<std::string> result; //结果集
result.reserve(Cattelan(N));//预分配
std::string __template {};//括号序列的模式串,初始化为空“”
dfs(__template, result, N);//dfs
return result;
}
这里的dfs有两种策略
1 暴力枚举
2 状态剪枝
先看暴力枚举(标准的深搜回溯),然后再进一步优化
bool check(std::string_view sequence){
//对一个长度足够的括号序列做合法性判断
int balance {};
for(auto _Char : sequence){
int face
= ((int)( _Char == '(' ) << 1) - 1;
//代数技巧,减小分支预测性能消耗。。 \
//bool 0:1 映射为 int -1:+1
if((balance += face) < 0)
return false;
}
return !balance;
}
void dfs(std::string& __template,
std::vector<std::string>& result,
std::size_t N)
{
if(__template.length() == N){
//递归出口,判断合法性
if(check(__template))
result.push_back(__template);
return;
}
//每次有两种对模板串的修改选择,+='(' or += ')'
//一一试探,并回溯
__template.push_back('(');//深搜
dfs(__template, result, N);
__template.pop_back();//回溯
__template.push_back(')');//深搜
dfs(__template, result, N);
__template.pop_back();回溯
}
然后是状态剪纸,通过记录当前状态下的左花括号,和右花括号的数目,来限制下一步的决策空间,达到剪枝效果
void dfs(std::vector<std::string>& result,
std::string& __template,
std::size_t IOpen, //当前左括号数目
std::size_t IClose,//当前右括号数目
std::size_t N ) //给定括号的对数
{
if(__template.length() == (N << 1))
return (void)(result.push_back(__template));
if(IOpen < N){
//合法性剪枝 + 同样的深搜回溯
__template.push_back('(');
dfs(result, __template, IOpen + 1, IClose, N);
__template.pop_back();
}
if(IClose < IOpen){
//合法性剪枝
__template.push_back(')');
dfs(result, __template, IOpen, IClose + 1, N);
__template.pop_back();
}
}