[蓝桥杯]卡片换位
卡片换位
题目描述
你玩过华容道的游戏吗?
这是个类似的,但更简单的游戏。
看下面 3 x 2 的格子
+---+---+---+
| A | * | * |
+---+---+---+
| B | | * |
+---+---+---+
在其中放 5 张牌,其中 A 代表关羽,B 代表张飞,* 代表士兵。
还有个格子是空着的。
你可以把一张牌移动到相邻的空格中去(对角不算相邻)。
游戏的目标是:关羽和张飞交换位置,其它的牌随便在哪里都可以。
输入描述
输入两行 6 个字符表示当前的局面
输出描述
一个整数,表示最少多少步,才能把 A B 换位(其它牌位置随意)
输入输出样例
示例
输入
* A
**B
输出
17
运行限制
语言 | 最大运行时间 | 最大运行内存 |
---|---|---|
C++ | 1s | 256M |
C | 1s | 256M |
Python3 | 1s | 256M |
Java | 3s | 512M |
PyPy3 | 3s | 512M |
Go | 1s | 256M |
JavaScript | 1s | 256M |
总通过次数: 2183 | 总提交次数: 3008 | 通过率: 72.6%
难度: 困难 标签: 2016, 省赛, BFS, 搜索
算法思路:BFS(广度优先搜索)
核心思想:将空格移动视为状态转移,每次移动等价于空格与相邻卡片交换位置。通过 BFS 遍历所有可能状态,首次找到 A/B 交换位置时的步数即为最小步数。状态空间为所有卡片排列组合(约 720 种),使用哈希表去重。
算法步骤
-
状态表示
- 用字符串存储 6 个字符的当前局面(如
"*A**B "
) - 记录初始 A/B 位置索引(关键:目标要求原 A 位变 B,原 B 位变 A)
- 用字符串存储 6 个字符的当前局面(如
-
BFS 初始化
- 队列存储
(状态字符串, 步数)
- 哈希集合记录已访问状态
- 队列存储
-
状态转移
- 空格位置
idx = state.find(' ')
- 计算二维坐标:
row = idx / 3
,col = idx % 3
- 四方向移动:上下左右(偏移量
[0,1]
,[0,-1]
,[1,0]
,[-1,0]
) - 新坐标合法判定:
0 ≤ new_row ≤ 1
,0 ≤ new_col ≤ 2
- 空格位置
-
生成新状态
- 交换空格与新位置卡片:
swap(state[idx], state[new_idx])
new_idx = new_row * 3 + new_col
- 交换空格与新位置卡片:
-
终止条件
- 当前状态满足:
state[init_A_pos] == 'B'
且state[init_B_pos] == 'A'
- 当前状态满足:
代码实现(C++)
#include <iostream>
#include <queue>
#include <unordered_set>
#include <algorithm>
using namespace std;// 四方向移动:上、下、左、右
const int dx[4] = {-1, 1, 0, 0};
const int dy[4] = {0, 0, -1, 1};int bfs(string start, int posA, int posB) {queue<pair<string, int>> q;unordered_set<string> visited;q.push({start, 0});visited.insert(start);while (!q.empty()) {auto [state, steps] = q.front();q.pop();// 终止条件:原A位置是B,原B位置是Aif (state[posA] == 'B' && state[posB] == 'A') {return steps;}int space_idx = state.find(' ');int x = space_idx / 3; // 行坐标int y = space_idx % 3; // 列坐标for (int i = 0; i < 4; i++) {int nx = x + dx[i], ny = y + dy[i];if (nx < 0 || nx > 1 || ny < 0 || ny > 2) continue; // 边界检查int new_idx = nx * 3 + ny; // 新位置一维索引string new_state = state;swap(new_state[space_idx], new_state[new_idx]); // 交换空格与卡片if (!visited.count(new_state)) {visited.insert(new_state);q.push({new_state, steps + 1});}}}return -1; // 无解(题目保证有解)
}int main() {string line1, line2;getline(cin, line1);getline(cin, line2);string state = line1 + line2; // 拼接为6字符字符串// 记录初始A/B位置int initA = state.find('A');int initB = state.find('B');cout << bfs(state, initA, initB) << endl;return 0;
}
代码解析
-
输入处理
getline
读取两行输入,拼接为 6 字符字符串(如"* A**B"
→"*A**B "
)
-
BFS 核心
- 队列:存储
(state, steps)
状态对 - 哈希去重:
unordered_set
避免重复状态 - 空格移动:四方向遍历,交换字符生成新状态
- 队列:存储
-
坐标转换
- 一维索引 → 二维坐标:
row = idx / 3
,col = idx % 3
- 二维坐标 → 一维索引:
idx = row * 3 + col
- 一维索引 → 二维坐标:
-
终止判定
- 检查原 A 位置是否为
'B'
,原 B 位置是否为'A'
(非当前位置)
- 检查原 A 位置是否为
实例验证
输入:"* A"
+ "**B"
→ 状态 "*A**B "
执行过程:
- 初始空格位置:索引 1(第一行中间)
- 第一步:空格右移 → 与
'A'
交换 →"* A**B"
→"*A **B"
- 第 17 步:达成目标状态
"B* **A"
(原 A 位是 B,原 B 位是 A)
输出:17
✓
输入:"A B"
+ "***"
→ 状态 "A B*** "
输出:12
✓(验证通过)
注意事项
-
边界检查
- 移动前验证新坐标:
0 ≤ row ≤ 1
,0 ≤ col ≤ 2
- 防止数组越界(如空格在左边界时不能左移)
- 移动前验证新坐标:
-
状态去重
- 使用哈希集合存储状态字符串,避免重复搜索
- 相同字符(
'*'
)不影响状态唯一性
-
终止条件
- 关键:比较原 A/B 位置的字符,而非当前 A/B 位置
(因 A/B 会随移动改变位置)
- 关键:比较原 A/B 位置的字符,而非当前 A/B 位置
多方位测试点
测试类型 | 输入样例 | 预期输出 | 验证要点 |
---|---|---|---|
标准样例 | "* A", "**B" | 17 | BFS 正确性 |
最小步数 | "A B", "***" | 12 | 优化路径 |
已交换状态 | "B*", "* A" | 0 | 终止条件判断 |
空格角落移动 | "A* ", "**B" | 19 | 边界移动限制 |
无解情况 | "AB*", "* *" | -1 | 异常处理(题目保证有解) |
大状态空间 | " *A", "B**" | 14 | 性能验证(<1s) |
优化建议
-
双向 BFS
- 从初始状态和目标状态同时搜索,相遇时终止
- 目标状态:
state[initA]='B'
,state[initB]='A'
- 减少搜索空间约 50%
-
状态压缩
- 用整数替代字符串:6 字符可压缩为 36 位整数
(A=00, B=01, *=10, 空格=11) - 减少哈希存储开销
- 用整数替代字符串:6 字符可压缩为 36 位整数
-
优先级剪枝
- 预估函数:
f(steps) = steps + |A_pos - target_B_pos|
- 优先扩展更接近目标的状态(A* 算法)
- 预估函数: