首先,我们需要理解题意。
我需要思考一个重要问题,什么是合法栈序列?
这里,读者可以自行思考一会。下面笔者会给出图解与文字说明。
题意理解:
首先第一个要义 “按顺序入栈” ,这是什么意思呢?
其实很简单,如果 n 入栈,那么其之前未出栈的所有车次均已入栈。
例如:n = 3时,且前面车次均未出栈
如果, n = 3, 但是2已经出栈,走了,上班去了,那么此时。
那么车站中,只有3、1在休息。
也就是说,栈中“休息”的元素,自底向顶有序但不一定连续。
接下来就是出栈的要义,这个就很容易理解了,出栈的元素有且只能时栈的首元素。
下面我们通过分析 “3412” 是否合法来简单捋一遍我们的思路。
首先,这是一个出栈序列,那么要抛出三意味着,栈中应当有:1、2、3(自底向顶)
抛出3后,为了准备抛出4,说明栈中应当有:1、2、4(自底向顶、3在上一轮中被派出干活了,故不应再栈中休息)
接下来,抛出 1。不能抛出,原因如下:抛出4后,2才是栈的首元素,而非1,所以1不能比2先抛出,所以“3412”非法。
代码实现:
理解完题目意思后,我们可以实现我们的代码了。一道题的解题思路是多样的,你可以正反面、可以检验正确性。
而这道题,因为其特殊性,我可以对每一个全排列进行检验,看看其是否是 “出栈序列” 即可(题目稍加限制,我们就检验到20个合法的出栈序列就可以结束我们的程序了)。
那么这种解法,我放在最后。
我们先来写写,正面解决 -- 模拟。
我相信大家再看题目的时候,一定发现了 “出栈序列” 就是 符合特定规则的 “全排列”
而枚举全排列,递归可就冲锋在前了,就算是检验法也不能绕开这一逻辑
那么我们心里大概有了一个底 , 大概需要 一个arr[] 存储枚举状态 ;vis[]存储访问状态;n来表示枚举元素最大值、枚举长度;cur_pos来标记现在的枚举位置;
但是这还没有结束,这道题还有他的特殊限制----栈,当然你可以使用栈直接来,事实上我也看到一些大佬用“递归+栈”直接来模拟整个过程。但是这里我采取一点变动,栈的核心有“进出”“完全包含关系”“以小问题撬动大问题”
当然这里没有那么复杂,我们只需要模拟栈的头元素变化即可,也就是“进出”关系
那么,思路又清晰了,我们需要一个 int x_h来模拟栈元素了变化
总结:arr[] 存储枚举状态 ;vis[]存储访问状态;n来表示枚举元素最大值、枚举长度;cur_pos来标记现在的枚举位置;int x_h来模拟栈元素了变化;times 记录 输出次数
于是我们可以定义出,或者说书写出以下框架:
#include <iostream>
#include <algorithm>
int arr[20], vis[21] = { 0 },times = 0;
void f(int x_h, int cur_pos, int n);
int main(){
int n;
std::cin >> n;
f(1, 0, n);
return 0;
}
整体框架搭好了,接下来,我们需要完善我们的递归函数f(...)就好了。
接下来,我们从需要进行的行为来模拟设计即可。
行为表:
1.只能抛出 x >= x_h;
2.先枚举自顶向下直接抛出的(向下扩展);例如 “321” “21” “4321” ---> if x==x_h
3.后枚举 先压入---再抛出的(向上扩展);例如“34”、“35” ---> if x > x_h
4.向下扩展每次只能抛出1个(扩展一次),也就是只能抛出第一个未访问结点。
void f(int x_h, int cur_pos, int ){
if(times == 20) return ;
if(cur_pos == n){
print_a_result(arr,n);
times++;
return;
}
//向下扩展,头节点在之前就抛出了;
if(vis[x_h] == 1){
for(int i = x_h-1; i >= 1; --i){
//向下(底)查找为访问元素
if(vis[i] == 0){
vis[i] = 1;
arr[cur_pos] = i;
f(i , cur_pos + 1, n);
vis[i] = 0;
//只扩展一次
break;
}
}
}
//向上扩展;
for(int i = x_h; i <= n; ++i){
if(vis[i] == 0){
vis[i] = 1;
arr[cur_pos] = i;
//i-1模拟了 填充至 i 再抛出后的 进出关系;
f(i-1, cur_pos, n);
vis[i] = 0;
}
}
return;
}
现在我们来完成 f(...) 中的print_a_result(...);
void print_a_result(int a[], int n){
for(int i = 0; i<n; ++i){
printf("%d",a[i]);
}
printf("\n");
return ;
};
至此,我们的程序完成,但是还有一个小小的bug,这是vis[]定义造成的,因为我定义开21个也就是vis[0] == 0;
但是我们不想让0出现,就要将vis[0] = 1;
至此大功告成。
#include <iostream>
#include <algorithm>
using namespace std;
int arr[20], vis[21] = {0};
int times = 0;
void print_a_result(int a[], int n) {
for (int i = 0; i < n; ++i) {
printf("%d", a[i]);
}
printf("\n");
return;
}
void f(int x_h, int cur_pos, int n) {
//if (x_h < 1) x_h = 1;
if (times == 20) return;
if (cur_pos == n) {
print_a_result(arr,n);
times++;
return;
}
//向下扩展一个
if (vis[x_h] == 1) {
for (int i = x_h-1; i >= 1; i--) {
if (vis[i] == 0) {
vis[i] = 1;
arr[cur_pos] = i;
f(i, cur_pos + 1, n);
vis[i] = 0;
break;
}
}
}
//向上扩展
for (int i = x_h; i <= n; ++i) {
if (vis[i] == 0) {
vis[i] = 1;
arr[cur_pos] = i;
f(i - 1, cur_pos + 1, n);
vis[i] = 0;
}
}
return;
}
int main() {
int n;
cin >> n;
vis[0] = 1;
f(1, 0, n);
return 0;
}
下面,我附上检验法:代码解释见注释;
#include <iostream>
#include <algorithm>
using namespace std;
//整个过程中,栈内元素只会出现一次。(需要再进一步理解,一个元素抛出后就不可能在出现)
bool IsValue(int a[], int n) {
stack<int> stk;
int x = 1;
for (int i = 0; i < n; ++i) {
//当处于初始状态(栈为空) 或者 栈顶元素小于a[i] 时需要压入操作
if (stk.empty() || stk.top() < a[i]) {
while (x <= a[i]) stk.push(x), ++x;
}
//抛出值不等于栈顶值 则不合法;特殊情况栈空,但需要抛出操作
//不能调换顺序 -- 涉及短路或
if (stk.empty() || stk.top() != a[i]) {
return false;
}
//模拟抛出栈的动作、抛出必然实行。
stk.pop();
}
return true;
}
int main() {
int n, a[25], cnt = 20;
cin >> n;
//模拟最开始的全排列;
for (int i = 1; i <= n; ++i) a[i - 1] = i;
//开始操作
do {
//判断,如果符合题意则输出,并且计数器-1
if (IsValue(a, n)) {
for (int i = 0; i < n; ++i) {
cout << a[i];
}
cout << endl;
cnt--;
}
} while (next_permutation(a, a + n) && cnt);//如果下一个排列存在而且未输出满20个,继续;
return 0;
}