求栈的出栈方式的个数和打印出栈顺序
来源:互联网 发布:淘宝vip会员卡素材 编辑:程序博客网 时间:2024/05/22 02:05
无重复序列入栈的出栈方式个数
首先,出栈方式的个数为h(n)=C(2n,n)/(n+1) (n=1,2,3,…)
例如1、2、3这三个数字,入栈并出栈共有5种方式,分别为:321、312、231、213、123。
再例如,对于1、2、3、4这四个数字,4个元素的全排列共有24种,栈要求符合后进先出,按此衡量排除后即得:
1234√ 1243√ 1324√ 1342√ 1423× 1432√
2134√ 2143√ 2314√ 2341√ 2413× 2431√
3124× 3142× 3214√ 3241√ 3412× 3421√
4123× 4132× 4213× 4231× 4312× 4321√
入栈并出栈共有14种可能。
那么对于长度为n的无重复序列中所有的出栈方式有哪些呢?
1个元素进栈,有1种出栈顺序;
2个元素进栈,有2种出栈顺序;
3个元素进栈,有5种出栈顺序
我们把n个元素的出栈个数的记为f(n), 那么对于1,2,3, 我们很容易得出:
f(1) = 1 //即 1
f(2) = 2 //即 12、21
f(3) = 5 //即 123、132、213、321、231
然后我们来考虑f(4), 我们给4个元素编号为a,b,c,d,输出结果第一个出栈的位置为1号位置,第二个出栈的位置为2号位置, 那么考虑:元素a只可能出现在1号位置,2号位置,3号位置和4号位置(很容易理解,一共就4个位置,比如出栈结果为abcd,元素a就在1号位置)。
分析:
1) 如果元素a在1号位置,那么只可能a进栈之后马上出栈,此时还剩元素b、c、d等待操作,就是子问题f(3);
2) 如果元素a在2号位置,那么一定有一个元素比a先出栈,即有f(1)种可能顺序(只能是b),还剩c、d,即f(2), 根据乘法原理,无论cd什么顺序,元素a在2号位置的顺序只有f(1)种可能顺序,那么一共的顺序个数为f(1) * f(2);
3) 如果元素a在3号位置,那么一定有两个元素比1先出栈,即有f(2)种可能顺序(只能是b、c),还剩d,即f(1),根据乘法原理,一共的顺序个数为f(2) * f(1);
4) 如果元素a在4号位置,那么一定是a先进栈,最后出栈,那么元素b、c、d的出栈顺序即是此小问题的解,即 f(3);
结合所有情况,即f(4) = f(3) + f(2) * f(1) + f(1) * f(2) + f(3);
为了规整化,我们定义f(0) = 1;于是f(4)可以重新写为:
f(4) = f(0)f(3) + f(1)*f(2) + f(2) f(1) + f(3)*f(0)
然后我们推广到n,推广思路和n=4时完全一样,于是我们可以得到:
f(n) = f(0)*f(n-1) + f(1)*f(n-2) + … + f(n-1)*f(0)
即
f(n)=sum(0,n-1,f(i)*f(n-1-i) )
根据此递归公式,相应的动态规划代码如下(来自:http://blog.csdn.net/hacker00011000/article/details/51192390)
#include <iostream>#include <cstring>using namespace std;int main(){ int n; cin >> n; int* arr = new int[n+1]; memset(arr, 0, sizeof(int)*(n+1)); arr[0] = 1; arr[1] = 1; //递推关系式 for (int i=2; i<=n; ++i) { for (int j=0; j<i; ++j) { arr[i] += arr[j] * arr[i-1-j]; } } cout << arr[n] << endl; delete[] arr; return 0;}
由于递归公式的时间复杂度仍为O(n2),通项公式则更佳,因此,我还摘录了通项公式法的分析(来自:http://www.cnblogs.com/jiayouwyhit/p/3222973.html)
对于每一个数来说,必须进栈一次、出栈一次。我们把该数进栈设为状态‘1’,出栈设为状态‘0’。n个数的所有状态对应n个1和n个0组成的2n位二进制数。由于等待入栈的操作数按照1‥n的顺序排列、入栈的操作数b大于等于出栈的操作数a(a≤b),因此输出序列的总数目=由左而右扫描由n个1和n个0组成的2n位二进制数,1的累计数不小于0的累计数的方案种数。
(如果1的累计数小于0的累计数,则进栈元素的个数少于出栈元素,这显然不可能的。)
在2n位二进制数中填入n个1的方案数为c(2n,n),不填1的其余n位自动填0。从中减去不符合要求(由左而右扫描,0的累计数大于1的累计数)的方案数即为所求:
不符合要求的数的特征是由左而右扫描时,必然在某一奇数位2m+1位上首先出现m+1个0的累计数和m个1的累计数,此后的2(n-m)-1位上有n-m个1和n-m-1个0。如若把后面这2(n-m)-1位上的0和1互换,使之成为n-m个0和n-m-1个1,结果得1个由n+1个0和n-1个1组成的2n位数,即一个不合要求的数对应于一个由n+1个0和n-1个1组成的排列。
反过来,任何一个由n+1个0和n-1个1组成的2n位二进制数,由于0的个数多2个,2n为偶数,故必在某一个奇数位上出现0的累计数超过1的累计数。同样在后面部分0和1互换,使之成为由n个0和n个1组成的2n位数,即n+1个0和n-1个1组成的2n位数必对应一个不符合要求的数。
因而不合要求的2n位数与n+1个0,n-1个1组成的排列一一对应。
显然,不符合要求的方案数为c(2n,n+1)。
由此得出输出序列的总数目=c(2n,n)-c(2n,n+1)=c(2n,n)/(n+1)。其中,n为节点的个数
此时,通项公式得到,其成为卡塔兰数。
打印出栈顺序/打印出栈方式
为了设计打印出出栈顺序的算法,我们可以用队列(queue)来模拟输入,队列的输出则按照原先序列的顺序。使用一个栈(stack)来模拟入栈和出栈,结果保存在另外一个队列(queue)中。
现在的问题来了,怎么样可以实现所有的出栈入栈操作。
首先来看看出栈和入栈是怎么回事,对于123这个序列,1先入栈之后有两种选择,1出栈和不出栈(2入栈),而若1不出栈(2已经入栈)之后,在2出栈之前1则不能先行出栈,故对于1我们只需要考虑其在2入栈之前出栈的情况,若1在栈内时2入栈,则1与2只能看成一个整体。
这样就可以用递归的方式求解,以下为算法描述,其中input表示入栈元素的顺序,local表示模拟的栈,output表示在已经出栈的部分解。
伪代码如下:
Solution(input,local,output)If(input.empty)/*由于input队列已经没有内容,此时没有入栈元素的影响,local栈和output队列的内容就是最后的结果*/ If(local.empty() ) 输出output队列的内容 Else While(local栈不空) 将local的栈顶元素出栈,放进output队列之中 Solution(input,local,output) end ifElse/*当有入栈元素时,出栈元素顺序会受到入栈元素和栈顶元素是否出栈的影响*/ If(local.empty() ) 从input队列中出队列一个元素并放入local之中 Solution(input,local,output) end ifElse/*此时有两种可能,可以是local栈的栈顶元素出栈,也可以是不出栈并从input队列中取一个元素放进去,先模拟出栈,再把栈内元素放回去*/ 将local的栈顶元素出栈,暂存,并放入output队列之中 Solution(input,local,output) 将刚刚出栈的local的栈顶元素放回local栈之中 将刚刚放入output队列之中的元素拿出来 从input队列中出队列一个元素并放入local之中 Solution(input,local,output)/*两种方法,一种是这里的利用output和local进行分叉搜索,另一种搜索方法是从input中取出元素压栈(此时栈顶元素不出栈)时查找出栈顺序,然后把压入栈的元素弹出来放回input之中,然后local栈顶元素出栈,去搜索此时的出栈顺序。*/end ifEnd Solution
其基本思想为对于中间栈的每一个时刻拍照,都递归其后续的所有可能,由于在递归返回的时候还需要递归前的信息,所以每次递归都是新建数据结构而保存当前时刻的状态。若输入队列已经为空,则中间栈只有一种出栈方式,中间栈也为空时递归结束。
详细代码如下:
#include <iostream>#include <stack> #include <deque>#include <queue>using namespace std; void dfs(queue<int> input, stack<int> local, deque<int> output) { if (input.empty()) { if (local.empty()) { cout << endl; int temp; while (!output.empty()) { temp = output.back(); output.pop_back(); cout << temp << " "; } cout << endl; } else { while (!local.empty()) { output.push_front(local.top()); local.pop(); } dfs(input, local, output); } } else { if (!local.empty()) { int temp = local.top(); local.pop(); output.push_front(temp); dfs(input, local, output); local.push(temp); output.pop_front(); temp = input.front(); input.pop(); local.push(temp); dfs(input, local, output); } else { int temp = input.front(); input.pop(); local.push(temp); dfs(input, local, output); } }}int main() { queue<int> input_iterator_tag; for (int i = 1; i < 5; i++) { input_iterator_tag.push(i); } dfs(input_iterator_tag, stack<int>{}, deque<int>{}); return 0;}
类似的问题
(1)买票找零
有2n个人排成一行进入剧场。入场费5元。其中只有n个人有一张5元钞票,另外n人只有10元钞票,剧院无其它钞票,问有多少中方法使得只要有10元的人买票,售票处就有5元的钞票找零?(将持5元者到达视作将5元入栈,持10元者到达视作使栈中某5元出栈)
(2)一个有n个1和n个-1组成的字串,且前k个数的和均不小于0,那这种字串的总数为多少?
(3)饭后,姐姐洗碗,妹妹把姐姐洗过的碗一个一个地放进碗橱摞成一摞。一共有n个不同的碗,洗前也是摞成一摞的,也许因为小妹贪玩而使碗拿进碗橱不及时,姐姐则把洗过的碗摞在旁边,问:小妹摞起的碗有多少种可能的方式?
最终结果:C(2n,n)-C(2n,n+1)
有机会再补充吧,最近刷题这么猛,还是找不到工作,人艰不拆。
参考文献:
https://zhidao.baidu.com/question/198485664882428485.html
http://www.cnblogs.com/jiayouwyhit/p/3222973.html
- 求栈的出栈方式的个数和打印出栈顺序
- n个数顺序入栈后的出栈顺序
- n个数顺序入栈后的出栈顺序
- 求栈的容量(从出队的顺序可以得到入栈和出栈的顺序)
- N个数顺序进栈,出栈的情况
- 不可能的出栈顺序
- 检查出栈数据的合法性和求一个数二进制中1的个数
- navigaiton的出栈方式
- 链式栈的初始化,判空,进栈,出栈,求长,求顶,打印,清空和销毁
- 根据栈的入栈序列和出栈序列,打印push和pop的顺序-Amazon笔试题
- 4.统计和打印出各个字符的个数。
- (四)顺序栈的入栈和出栈
- 顺序栈的压栈和出栈
- 给定入栈顺序,求所有可能的出栈顺序
- 出栈顺序和卡特兰数的关系
- 元素出栈、入栈顺序的合法性/计算一个整数二进制位中1的个数。
- 栈的压栈、出栈顺序
- 栈的出栈顺序数
- Netty Failure to transfer io.netty:netty-tcnative:jar:${os.detected.classifier}:2.0.0.Final 问题解决
- 使用函数实现两个数的交换。
- FPGA 深度学习CNN加速
- elasticsearch(3) dymmic mapping
- 自定义echarts图形的提示格式
- 求栈的出栈方式的个数和打印出栈顺序
- Alibaba的OceanBase数据库环境配置与安装
- PSD位置传感器
- Android完美获取状态栏高度、标题栏高度、编辑区域高度的获取
- date2017.10.16
- debian下执行定时任务
- SpringMVC 三种访问静态资源的方式
- github使用入门教程
- 光线传媒副总裁刘同:我们为什么要读大学?