PTA(栈和队列)2-2 列车厢调度

2-2 列车厢调度

分数 5

作者 周强

单位 青岛大学

1
2
3
4
5
       1  ======   <--移动方向
/
3 =====
\
2 ====== -->移动方向

大家或许在某些数据结构教材上见到过“列车厢调度问题”(当然没见过也不要紧)。今天,我们就来实际操作一下列车厢的调度。对照上方的ASCII字符图,问题描述如下:

有三条平行的列车轨道(1、2、3)以及1-3和2-3两段连接轨道。现有一列车厢停在1号轨道上,请利用两条连接轨道以及3号轨道,将车厢按照要求的顺序转移到2号轨道。规则是:

  • 每次转移1节车厢;
  • 处在1号轨道的车厢要么经过1-3连接道进入3号轨道(该操作记为”1->3”),要么经过两条连接轨道直接进入2号轨道(该操作记为”1->2”);
  • 一旦车厢进入2号轨道,就不可以再移出该轨道;
  • 处在3号轨道的车厢,只能经过2-3连接道进入2号轨道(该操作记为”3->2”);
  • 显然,任何车厢不能穿过、跨越或绕过其它车厢进行移动。

对于给定的1号停车顺序,如果经过调度能够实现2号轨道要求的顺序,则给出操作序列;如果不能,就反问用户 Are(你) you(是) kidding(凯丁) me(么)?

输入格式:

两行由大写字母组成的非空字符串,第一行表示停在1号轨道上的车厢从左到右的顺序,第二行表示要求车厢停到2号轨道的进道顺序(输入样例1中第二行CBA表示车厢在2号轨道的停放从左到右是ABC,因为C最先进入,所以在最右边)。两行字符串长度相同且不超过26(因为只有26个大写字母),每个字母表示一节车厢。题目保证同一行内的字母不重复且两行的字母集相同。

输出格式:

如果能够成功调度,给出最短的操作序列,每个操作占一行。所谓“最短”,即如果1->2可以完成的调度,就不要通过1->3和3->2来实现。如果不能调度,输出 “Are you kidding me?”

输入样例1:

1
2
ABC
CBA

输出样例1:

1
2
3
4
5
1->3
1->3
1->2
3->2
3->2

输入样例2:

1
2
ABC
CAB

输出样例2:

1
Are you kidding me?


我的思路:

  • 相当于1中的字符是即将入栈(2)的,可以暂存到3这个栈中,也可以直接出栈到(结果)2中

  • 相当于让你写进出栈顺序的,让你写成最佳的

    • 即如果1->2可以完成的调度,就不要通过1->3和3->2来实现
    • 最后一个进去的如果没进去,就不进去了,直接就出栈了(1->2)

  • 先检查当前放入的是不是结果中匹配的,

    • 如果不是,往栈中放1->3,每次getTop一次,检查是否跟结果数组中的一样,不一样就重复第一步
    • 如果直接1->2,不入栈
      • 然后当前栈非空就getTop,检查栈顶是否是匹配的,匹配就3->2
      • 不匹配或栈空就重复第一步,没有元素了就失败了
  • 我的问题:万一前面的能匹配,最后匹配不了了,我都已经输出了一半了,咋还只输出Are you kidding me?

  • 我想到了解决办法,可以先存起来存队列里面,然后不符合的话前面的就不输出,哦不对,要输出的是从哪到哪

我的乱思路代码

代码思路方向是对的(用栈模拟 3 号轨道)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <string>
#include <stack>

using namespace std;

void solution(string str1, string str2) {
stack<char> Stack3; // 定义一个栈,模拟3号轨道
int current = 0; // 指向str2某个字符,用于检查是否匹配
for (int i = 0; i < str1.length(); i++) {
char c = str1[i];
if (current < str2.length() && c == str2[current]) { // 当前字符就是匹配的
cout << "1->2" << endl;
current++;
while (!Stack3.empty()) { // 如果当前栈非空
// 从栈中开始匹配
if (current < str2.length() && c == str2[current]) { // 当前字符就是匹配的
cout << "1->2" << endl;
current++;
}

} else { // 不匹配就入栈
Stack3.push(str1[i]);
cout << "1->3" << endl;
}
}
}

int main() {
string str1; // 1号轨道出站顺序
string str2; // 进入2号轨道的顺序
cin >> str1 >> str2;
solution(str1, str2);



}
  1. 核心逻辑:1 号轨道的车厢需按顺序处理,要么直接进入 2 号轨道(1->2),要么先进入 3 号轨道暂存(1->3),之后从 3 号轨道进入 2 号轨道(3->2)。最终需让 2 号轨道的车厢顺序与目标一致。
  2. 关键错误:
    • 栈中元素匹配时,错误地用了当前 1 号轨道的车厢(c)去比较,而应使用栈顶元素。
    • 处理完 1 号轨道所有车厢后,未检查栈中剩余元素是否能按顺序进入 2 号轨道。
    • 未判断最终是否完全匹配,导致无法输出 “Are you kidding me?”。
  • 豆包是让输出序列先存到string里面,不匹配就不输出

正确答案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>
#include <string>
#include <stack>

using namespace std;

void solution(string str1, string str2) {
stack<char> Stack3; // 定义一个栈,模拟3号轨道
string operations; // 存储操作
bool possible = true; // 看最终能否成功
int j = 0; // 指向str2某个字符,用于检查是否匹配

// 处理1号轨道的每一节车厢
for (int i = 0; i < str1.length(); i++) {
char current = str1[i];
if (current == str2[j]) { // 当前字符就是匹配的
operations += "1->2\n";
j++;
// 检查栈顶是否能继续匹配(每次1->2后都要尝试弹出栈中可匹配的元素)
while (!Stack3.empty() && Stack3.top() == str2[j]) {
// 从栈中开始匹配
operations += "3->2\n";
Stack3.pop();
j++;
}
} else { // 不匹配就入栈
Stack3.push(str1[i]);
operations += "1->3\n";
}
}

// 1号车厢全部出完,处理3号
while (!Stack3.empty()) {
if (Stack3.top() == str2[j]) {
Stack3.pop();
j++;
operations += "3->2\n";
} else { // 不匹配,直接结束
possible = false;
break;
}
}
if (possible && str2.length() == j) {
cout << operations;
} else {
cout << "Are you kidding me?" << endl;
}
}

int main() {
string str1; // 1号轨道出站顺序
string str2; // 进入2号轨道的顺序
cin >> str1 >> str2;
solution(str1, str2);
}

java版

  • string类型——String引用类型
  • 输入:
    • 需创建 Scanner 对象,通过 scanner.next() 读取

栈的实现

维度 C++ 版本 Java 版本
栈的类选择 直接使用 std::stack<char> 推荐使用 Deque<Character>(双端队列)模拟栈(java.util.Stack 是遗留类,不推荐)
入栈操作 stack.push(current) deque.push(current)(与 C++ 同名,但类不同)
出栈操作 stack.pop()(无返回值) deque.pop()(无返回值,与 C++ 行为一致)
获取栈顶元素 stack.top() deque.peek()(Java 中 peek() 对应栈顶查询,top() 不存在)
判空操作 stack.empty() deque.isEmpty()(方法名差异,语义一致)
栈的初始化 stack<char> Stack3;(直接声明) Deque<Character> stack3 = new ArrayDeque<>();(接口 + 实现类,需实例化)
  • 集合类 Deque<Character> 只能存储对象,不能直接存储基本类型 char,所以用Character

  • Deque 的本质Deque 是 Java 中的 “双端队列” 接口(全称 Double Ended Queue),它允许在队列的两端进行添加 / 删除操作(既可以从头部操作,也可以从尾部操作)。

  • Deque 模拟栈的原理

    栈的核心是 “后进先出(LIFO)”,只需要在 Deque同一端(比如尾部)进行 “入栈” 和 “出栈” 操作,就能模拟栈的行为:

    • 入栈:push(element) → 向 Deque尾部添加元素(相当于栈的 “压栈”)。
    • 出栈:pop() → 从 Deque尾部移除并返回元素(相当于栈的 “弹栈”)。
    • 查看栈顶:peek() → 返回 Deque尾部元素(不删除,相当于栈的 “查看栈顶”)。

字符串操作

  • C++ 中str1[i]对应 Java 的str1.charAt(i)

  • 字符串拼接使用StringBuilder(比String直接拼接更高效)

  • StringBuilder 的作用:Java 中的 String 是 “不可变的”(一旦创建就不能修改内容),如果直接拼接字符串(比如 s = s + "1->2"),会频繁创建新的 String 对象,效率很低。

    StringBuilder 是专门用于高效拼接字符串的类,它的内容可以修改,避免了频繁创建对象的开销。

  • append() 方法StringBuilder 的核心方法,用于向当前字符串中 “追加” 内容(可以是字符串、字符、数字等),直接修改自身,不创建新对象。

编译错误答案

1
Compiler did not create the expected binary
  • 不知道哪错了(idea也没报错)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Scanner;

class TrainDispatcher {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String str1 = sc.next(); // 1号轨道出站顺序
String str2 = sc.next(); // 进入2号轨道的顺序
solution(str1, str2);
}

static void solution(String str1, String str2) {
Deque<Character> Stack3 = new ArrayDeque<>(); // 用Deque模拟3号轨道(栈)
StringBuilder operations = new StringBuilder(); // 存储操作
boolean possible = true; // 看最终能否成功
int j = 0; // 指向str2某个字符,用于检查是否匹配

// 处理1号轨道的每一节车厢
for (int i = 0; i < str1.length(); i++) {
char current = str1.charAt(i);
if (current == str2.charAt(j)) { // 当前字符就是匹配的
operations.append("1->2\n");
j++;
// 检查栈顶是否能继续匹配(每次1->2后都要尝试弹出栈中可匹配的元素)
while (!Stack3.isEmpty() && Stack3.peek() == str2.charAt(j)) {
// 从栈中开始匹配
operations.append("3->2\n");
Stack3.pop();
j++;
}
} else { // 不匹配就入栈
Stack3.push(str1.charAt(i));
operations.append("1->3\n");
}
}

// 1号车厢全部出完,处理3号
while (!Stack3.isEmpty()) {
if (Stack3.peek() == str2.charAt(j)) {
Stack3.pop();
j++;
operations.append("3->2\n");
} else { // 不匹配,直接结束
possible = false;
break;
}
}
if (possible && str2.length() == j) {
System.out.println(operations);
} else {
System.out.println("Are you kidding me?");
}
}
}