题目
设有一个数列的输入顺序为123456,若采用栈结构,并以A和D分别表示入栈和出栈操作,试问通过进栈和出栈操作的合法序列有多少种?
解题
三种办法,结果是132种序列。
1. 列举法
不是直接一个个去列,而是找技巧。先列数列元素个数为1的,再是2的,最后一直到6个,可以找到一定的规律得出结果。
2. 代码
毕竟作为一名软件工程的学生,用老师的话说,“你们写代码的能力是要求比计算机科学的同学强的,写代码对于你们应该像喝水吃饭一样简单,这是最基础的!”。于是我在列举法之后考虑是否能用代码解决这个问题。事实证明是可以的,我的想法如下:
入栈、出栈顺序(A,D的排列)有两个要求:
1. 共有12个位置,A、D各六个;
2. 任取前n个(n<=12),A的数量应该大于等于D的数量(入了才能出啊,入的不能少于出的),且n=12时,num(A)=num(D)=6
只用遍历每一种可能即可。那么应该如何遍历?使得容易检测这两个条件,且使得时间复杂度较小(12个for循环就算了)
我的想法是利用二进制数遍历。即遍历0~-1这4096个十进制数,转化为二进制数,为了检验第二个条件,我把12进制数中的0变为-1,1不变,只用检测前n位加起来是否>=0即可,取出每一位的方法是先除再取余。具体代码如下:
// main.cpp
// gcc编译通过
#include <iostream>
#include <stdio.h>
using namespace std;
// 10的11次方已经超过int范围
// 十进制转换为二进制,用的递归
long long transform(int num)
{
if(!num) return 0;
return num % 2 + 10 * transform(num / 2);
}
// 计算10的次方,目的是取数字的每一位
// C++自带的pow以及pow10返回double,不符合要求,只好自己写
long long pow10(int b)
{
long long sum = 1;
for (int i = 0; i < b; i++)
{
sum *= 10;
}
return sum;
}
int main()
{
int sum = 0;
int count = 0;
long long num2 = 0;
int tmp = 0;
bool flag = false;
// 遍历可能性
for (int a = 4095; a >= 1; a -= 1)
{
sum = 0;
flag = true;
num2 = transform(a);
for (int b = 11; b >= 0; b--)
{
// 取每一位
tmp = (num2 / pow10(b)) % 10;
if (tmp == 0) sum += -1;
else if (tmp == 1) sum += 1;
if (sum < 0)
{
flag = false;
break;
}
}
if (flag && !sum)
{
count++;
}
}
cout << count <<endl;
getchar();
return 0;
}
3. 公式法
我认为这个题目上面两种办法还是需要懂一点脑筋的,于是问其他几个同学他们是怎么做的,结果被告知他们都是直接上网查这个题目,发现有公式,就直接拿过来解题了((⊙o⊙)…,机智啊)。
这个栈的出栈序列是卡特兰数的一个运用。
“
首次出空之前第一个出栈的序数k将1~n的序列分成两个序列,其中一个是1~k-1,序列个数为k-1,另外一个是k+1~n,序列个数是n-k。
此时,我们若把k视为确定一个序数,那么根据乘法原理,f(n)的问题就等价于——序列个数为k-1的出栈序列种数乘以序列个数为n - k的出栈序列种数,即选择k这个序数的f(n)=f(k-1)×f(n-k)。而k可以选1到n,所以再根据加法原理,将k取不同值的序列种数相加,得到的总序列种数为:f(n)=f(0)f(n-1)+f(1)f(n-2)+……+f(n-1)f(0)。
”
卡特兰数的公式是:
其中n为元素个数