火车进栈
Posted onlyblues
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了火车进栈相关的知识,希望对你有一定的参考价值。
火车进栈
这里有$n$列火车将要进站再出站,但是,每列火车只有$1$节,那就是车头。
这$n$列火车按$1$到$n$的顺序从东方左转进站,这个车站是南北方向的,它虽然无限长,只可惜是一个死胡同,而且站台只有一条股道,火车只能倒着从西方出去,而且每列火车必须进站,先进后出。
也就是说这个火车站其实就相当于一个栈,每次可以让右侧头火车进栈,或者让栈顶火车出站。
车站示意如图:
出站<—— <——进站
|车|
|站|
|__|
现在请你按《字典序》输出前$20$种可能的出栈方案。
输入格式
输入一个整数$n$,代表火车数量。
输出格式
按照《字典序》输出前$20$种答案,每行一种,不要空格。
数据范围
$1 \\leq n \\leq 20$
输入样例:
3
输出样例:
123 132 213 231 321
解题思路
我的思路
首先,看到数据范围大小就知道应该要用dfs了。
这道题虽然打上了简单的标签,但当时想了很久,最后还是用直觉来AC的。
说一下我当时的思路吧,一开始想错了,但最后的思路与正解几乎一样。
我一开始是怎么想的呢。因为搜索嘛,所以肯定先想搜索的顺序或状态。所以我一开始想的是,分两种状态,一种是一个数字压栈里,另一种是数字出栈,所以搜索的顺序是先把数字压栈里,然后继续从下一个数字开始搜。当回溯到这个数字时,再把数字从栈顶弹出,然后继续搜。同时因为我是按数字从小到大的方式搜的,所以保证数字的的出入栈顺序是合法的(因为只有小的数字入栈后,后面的数字才可以入栈)。
按照这个思路,然后我就写出了这样的代码。
#include <cstdio> #include <vector> #include <queue> #include <algorithm> using namespace std; const int N = 30; int q[N], hh, tt = -1; int stk[N], tp; priority_queue<vector<int>, vector<vector<int>>, greater<vector<int>>> pq; void dfs(int cnt, int n) { // 递归边界为搜索到最后一个数字 if (cnt == n) { vector<int> ret; // 由于出栈的元素压到队列,所以先把队列的元素压入数组中 for (int i = hh; i <= tt; i++) { ret.push_back(q[i]); } // 同时由于最后一个数字要先压入栈,然后又最先从栈弹出,所以干脆直接把最后一个数字压入数组中 ret.push_back(n); // 最后把栈中的元素压入数组中 for (int i = tp; i; i--) { ret.push_back(stk[i]); } // 把出栈的结果压入优先队列,到时候直接从优先队列中输出前20个结果就可以了 pq.push(ret); return; } stk[++tp] = cnt; // 先把数字压入栈,然后往下一个数字搜 dfs(cnt + 1, n); q[++tt] = stk[tp--];// 恢复现场,同时把数字从栈顶压入队列 dfs(cnt + 1, n); // 继续从下一个数字搜 tt--; } int main() { int n; scanf("%d", &n); dfs(1, n); for (int i = 0; !pq.empty() && i < 20; i++) { vector<int> tmp = pq.top(); pq.pop(); for (auto &it : tmp) { printf("%d", it); } printf("\\n"); } return 0; }
当我们输入3时,会发现输出结果如下:
123 132 231 321
少了“213”这种情况,这就很奇怪了,说明这种搜索方式是有问题的,我们来看看问题出现在哪里。
要得到“213”,首先是1入栈,然后2入栈,接着把栈中的所有元素弹出,得到“21”,然后3再入栈出栈,就得到了“213”。
我们会发现,在递归到数字2时,1也跟着出栈了。顺序是2先入栈,然后2出栈,然后1出栈。要知道,按照我们上面的思路或者代码逻辑,应该是1出栈后才会有2入栈。
我们应该解决的是,当回溯到后面比较大的数字时,前面比较小的数字如果还在栈中,要将其弹出来。
发现问题后,我当时的直觉是,考虑一种情况,栈中有元素未弹出,我们先把栈顶的一个元素弹出,再把数字压入栈。回溯到这个数字时,先把这个数字从栈弹出,然后再把栈顶元素弹出,再把这个数字压到栈。只要还能回溯到这个数字,就一种重复这个操作,直到栈为空时,再把数字压入,继续搜索。
但事实时,我当时的代码实现是反过来的,是先把所有的元素从栈中弹出,然后再逐个把这些元素从队列中压回栈。操作和上面说的一样,只不过我是反过来做的。
我也不知道为什么在代码实现上是反过来做的,可能是因为当时想到这两种情况是等价的,然后这种方法好实现,所以就反过来做了。然后很幸运的是,正因为我是反过来做的,就阴差阳错地实现了题目要求地按字典序输出(后面我会解释为什么这样子做可以保证是按字典序输出)。所以才说这题我是凭着直觉AC的。
AC代码如下:
#include <cstdio> #include <vector> #include <algorithm> using namespace std; const int N = 30; int q[N], hh, tt = -1; int stk[N], tp; int tot; void dfs(int cnt, int n) { if (tot >= 20) return; // 因为是按字典序输出,所以搜索完前20种结果后就可以结束了 // 其中每种结果递归边界是已经搜索到第n+1个数,说明已经得到前n个数的出栈顺序了 if (cnt > n) { tot++; // 由于出栈的元素压到队列,所以先把队列的元素压入数组中 vector<int> ret; for (int i = hh; i <= tt; i++) { ret.push_back(q[i]); } // 最后把栈中的元素压入数组中 for (int i = tp; i > 0; i--) { ret.push_back(stk[i]); } for (auto &it : ret) { printf("%d", it); } printf("\\n"); return; } int m = tp; // 记录栈中元素个数 // 把栈中的元素先全部压到队列中 while (tp) { q[++tt] = stk[tp--]; } // 要把m个元素压回栈中。同时还要再考虑栈中没有元素,把数字压入栈这种情况,所以一个循环m+1次 for (int i = 0; i <= m; i++) { stk[++tp] = cnt; // 把数字压入栈,然后往下一个数字搜 dfs(cnt + 1, n); tp--; // 把数字弹出 if (i != m) stk[++tp] = q[tt--]; // 然后再把队列的元素压入栈。执行到最后一次时,已经把原来的元素全部压到栈中了 } } int main() { int n; scanf("%d", &n); dfs(1, n); return 0; }
y总的思路
假设我们已经把前k个元素压入过栈(这其中可能有些元素出栈了),例如下图这种情况:
很自然会想到有两种操作,第一种是把k压入栈顶,第二种是把栈顶元素弹出。
递归的边界是队列中元素的个数是n。按照这种搜索方式我们就可以把所以的出栈顺序给枚举出来。
接下来我们来考虑如何按字典序输出。我们来考虑操作1和操作2这两种执行顺序的先后对出栈顺序结果的影响。
首先我们会发现,序列中的元素都是大于栈中的元素,这是因为我们是按照编号从小到大的顺序来进栈的。所以还没有进栈的元素一定是比进栈的元素要大。
因此,如果我们一开始先执行操作2,把栈顶元素弹到队列中去,那么队列中的下一个元素(也就是从栈顶弹出的元素),一定会比先执行操作1再执行操作2得到的队列中下一个元素要小(原来栈顶的元素没有弹出,反而一个更大的数被压入栈顶,再按照出栈的顺序,你可以想象一下,是更大的数弹出)。
这是因为,如果先执行操作1,也就是把一个更大的元素压入栈,然后再去执行下一个操作(再去搜索下一层)。当回溯到这层时,这时就要把栈顶元素弹出压入队列,也就是这个更大的数压入队列。所以,可以发现,如果一开始就执行操作2,把栈顶那个相对更小的数压入队列,就可以得到一个更小的出栈顺序了(字典序)。
好了,思路已经讲完了,AC代码如下:
#include <cstdio> #include <algorithm> using namespace std; const int N = 30; int tot; int q[N], hh, tt = -1; int stk[N], tp; void dfs(int cnt, int n) { if (tot >= 20) return; // 递归的边条件是队列中的元素个数为n if (tt - hh + 1 == n) { tot++; for (int i = hh; i <= tt; i++) { printf("%d", q[i]); } printf("\\n"); return; } // 先执行操作2,也就是先把栈顶元素弹出 if (tp) { // 栈不为空才可以弹出 q[++tt] = stk[tp--]; dfs(cnt, n); stk[++tp] = q[tt--]; } // 再执行操作1,把序列中的元素压入栈 if (cnt <= n) { // 要压入的元素大小不可以大于n stk[++tp] = cnt; dfs(cnt + 1, n); tp--; } } int main() { int n; scanf("%d", &n); dfs(1, n); return 0; }
参考资料
AcWing 129. 火车进栈:https://www.acwing.com/video/67/
以上是关于火车进栈的主要内容,如果未能解决你的问题,请参考以下文章