网上有很多解法,但个人感觉不够清晰。首先这是个卡特兰数,学过组合数学的同学都知道。没学过的可以看下下面这个例子。
有2n个人排成一队进入剧场。入场费5元。其中只有n个人有一张5元钞票,另外n人只有10元钞票,剧院无其它钞票可找零,问有多少中方法使得只要有10元的人买票,售票处就有5元的钞票找零?(将持5元者到达视作将5元入栈,持10元者到达视作使栈中某5元出栈)。
对于这个例子,剧院要想总有零钱可找,那么目前进入剧院的人数中,揣着10元钞票的人数必须少于等于揣着5元钞票的,不然肯定在某个人那出现没零钱找的情况。
现在回到正题上来对于一个给定入栈序列,怎么求它的出栈序列呢?
我们可以把入栈记为1,出栈记为0.那么前缀子序列中1的个数必须大于等于0的个数,即入栈次数要大于等于出栈次数,如1 1 0 1 0 0,它的任意前缀序列中1的个数是大于等于0的个数的。
我们来看个例子:对于1 2 3这个入栈序列,1 1 0 1 0 0就是一个入栈出栈序列,第一个1代表元素1入栈,然后第二个1代表元素2入栈,然后第三个是0,代表出栈,即元素2出栈,然后第四个是1,代表元素3入栈,然后第五个是0,代表出栈,即元素3出栈,然后第六个是0,代表元素1出栈。最后1 1 0 1 0 0就代表了出栈序列2 3 1。
那么现在的问题就转换为如何求出所有符合条件的0 1序列了。其实这和以下问题相同:给定括号对数,输出所有符合要求的序列。如2对括号,输出有()()或者(())两种。1可以看成’(’,0可以看成‘)’。
下面贴上原博客的程序,并给出详细注释。
#include <iostream>
#include <vector>
using namespace std;
void func(vector<char>kind,int count[],int n)
{
if(count[0]>=1)
{
kind.push_back('(');
count[0]--;
func(kind,count,n);
count[0]++;
kind.pop_back();
}
if((count[1]>=1) && (count[1]>count[0]))
{
kind.push_back(')');
count[1]--;
func(kind,count,n);
count[1]++;
kind.pop_back();
}
if(kind.size()==2*n)
{
vector<char>::iterator iter;
for(iter=kind.begin();iter!=kind.end();iter++)
{
cout<<(*iter)<<" ";
}
cout<<endl;
}
}
int main()
{
int n;
cout << "please input the number of ():" << endl;
cin>>n;
int count[2]={n-1,n};
vector<char>kind;
kind.push_back('(');
func(kind,count,n);
return 0;
}
count[0]存着左括号数目,count[1]存着右括号数目。一开始kind中压入左括号,因为第一个肯定是左括号。然后count数组初始化为n-1个左括号,n个右括号。然后我们递归的处理。如果剩余左括号数count[0]大于0,就可以把左括号压栈。而对于右括号,栈中左括号个数必须多于右括号个数,也就是剩余右括号个数大于左括号个数,即count[1]>count[0]时,才能将右括号压栈。如果栈中元素个数达到2n时,就把栈中元素输出。
下面贴出出栈序列代码,几乎和上面相同。
#include <iostream>
#include <stack>
#include <vector>
using namespace std;
int number=0;
void func(vector<int>kind,int count[],int n,int A[])
{
if(count[0]>=1)
{
kind.push_back(1);
count[0]--;
func(kind,count,n,A);
count[0]++;
kind.pop_back();
}
if((count[1]>=1) && (count[1]>count[0]))
{
kind.push_back(0);
count[1]--;
func(kind,count,n,A);
count[1]++;
kind.pop_back();
}
if(kind.size()==2*n)
{
vector<int>::iterator iter;
stack<int>stk;
int j=0;
for(iter=kind.begin();iter!=kind.end();iter++)
{
//cout<<(*iter)<<" ";
if(1==(*iter))
{
stk.push(A[j]);
j++;
}
else
{
cout<<stk.top()<<" ";
stk.pop();
}
}
number++;
cout<<endl;
}
}
int main()
{
int n,i;
cout << "please input the number:" << endl;
cin>>n;
int A[n];
cout << "please input the push sequence:" << endl;
for(i=0;i<n;i++)
{
cin>>A[i];
}
int count[2]={n-1,n};
vector<int>kind;
kind.push_back(1);
cout<<"the result is:"<<endl;
func(kind,count,n,A);
cout<<"total:"<<number<<endl;
return 0;
}
时隔两年,再回过头看自己写的博客,发现原来的算法其实是可以优化的,然后分享下我自己的更加简洁高效的代码。其实可以不用count[0]和count[1]分别去统计0和1的数量,只要统计1的数量即可,每当遇到0,就把1的数量减1,因此需要保证1的数量大于0才能选择0。然后这题其实就是个回溯算法了
#include<bits/stdc++.h>
using namespace std;
int count=0;
void print(vector<int>&trace, vector<int>&push_order){
int i=0;
stack<int>stk;
for(auto n:trace){
if(n == 1){
stk.push(push_order[i++]);
}
else{
int num = stk.top();
stk.pop();
cout <<num << " ";
}
}
cout << endl;
count++;
}
void backtrack(vector<int>&trace,vector<int>&push_order,int left){
// left记录前缀序列中1的个数,每当trace中push一个0,就减一
if(trace.size() == 2 * push_order.size()){ // 递归出口
if(left == 0) // 只有left=0,才能使得0的数量==1的数量,才是合法的。
print(trace,push_order);
return ;
}
// 做选择
if(left >0 ){ // 如果left>0,则可以push 0进去 ,同时 left-1
trace.push_back(0);
backtrack(trace,push_order,left-1);
// 撤销选择
trace.pop_back();
}
trace.push_back(1);
backtrack(trace,push_order,left+1);
trace.pop_back();
}
int main(){
vector<int> push_order = {1,2,3,4,5,6};
vector<int>trace;
backtrack(trace, push_order,0);
cout << count << endl;
}
可以看到我写的代码更简短,并且函数调用起来也方便,不用初始化count数组,空间开销更小。