一、问题背景
骰子是一个立方体,每个面有一个数字。题目给定骰子的初始状态:
- 左:1
- 右:2
- 前:3(观察者方向)
- 后:4
- 上:5
- 下:6
用字符串 123456 表示。
骰子可以进行以下 6 种操作:
- L:向左翻转
- R:向右翻转
- F:向前翻转
- B:向后翻转
- A:逆时针旋转 90°(绕竖轴)
- C:顺时针旋转 90°(绕竖轴)
从初始状态出发,给定一串操作序列(长度 ≤ 50),计算最终的骰子状态。
二、常规解法的问题
最直观的写法通常是:
- 用 6 个变量表示 6 个面
- 每种操作都手写一套临时变量交换逻辑
这种方式的问题在于:
- 重复代码多
- 左右 / 前后 / 顺逆方向容易写错
- 扩展性差(多加一种旋转就要重写一套逻辑)
如果只追求 AC,这样当然没问题;但如果希望代码更稳定、更易验证,就需要更抽象的建模方式。
三、核心建模思想:把骰子看成“索引数组”
1️⃣ 状态表示
我们用一个长度为 6 的数组表示骰子状态:
index: 0 1 2 3 4 5
含义: 左 右 前 后 上 下
初始状态即:
[1, 2, 3, 4, 5, 6]
这样做的关键好处是:
任何一次骰子操作,本质上都是对这 6 个位置做一次固定的置换(permutation)
四、用“索引置换”描述一次翻转
以 向左翻转(L) 为例。
物理意义上:
- 左 ← 上
- 右 ← 下
- 上 ← 右
- 下 ← 左
用索引表示就是:
const left = [
[0, 4], // new left <- old up
[1, 5], // new right <- old down
[4, 1], // new up <- old right
[5, 0] // new down <- old left
];
这 4 个二元组完整描述了这次翻转。
同理:
- F / B:影响 前 / 后 / 上 / 下
- A / C:绕竖轴旋转,只影响 左 / 右 / 前 / 后
五、逆操作的自动生成(关键技巧)
注意到一个重要事实:
R 是 L 的逆操作,B 是 F 的逆操作,A 是 C 的逆操作
既然每个操作本质是“从哪来”的映射,那么逆操作只需要把映射反过来即可。
const oppsite = dir => dir.map(([a, b]) => [b, a]);
有了它,就可以非常干净地定义所有操作:
const front = [[2, 4], [3, 5], [4, 3], [5, 2]];
const left = [[0, 4], [1, 5], [4, 1], [5, 0]];
const clock = [[0, 2], [1, 3], [2, 1], [3, 0]];
const mapping = {
L: left,
R: oppsite(left),
F: front,
B: oppsite(front),
C: clock,
A: oppsite(clock)
};
这样可以保证:
- 正反操作逻辑完全对称
- 不会出现“L 写对了,R 写错了”的问题
六、统一执行置换
每次操作,只需要做一次 4 元循环赋值:
let nums = [1, 2, 3, 4, 5, 6];
for (let dir of ops) {
const i = mapping[dir];
[nums[i[0][0]], nums[i[1][0]], nums[i[2][0]], nums[i[3][0]]] =
[nums[i[0][1]], nums[i[1][1]], nums[i[2][1]], nums[i[3][1]]];
}
逻辑高度统一,没有任何分支判断。
七、完整代码(ACM / Node.js)
const rl = require("readline").createInterface({ input: process.stdin });
let lines = [];
rl.on("line", l => lines.push(l)).on("close", () => {
let nums = [1, 2, 3, 4, 5, 6];
const oppsite = dir => dir.map(([a, b]) => [b, a]);
const front = [[2, 4], [3, 5], [4, 3], [5, 2]];
const left = [[0, 4], [1, 5], [4, 1], [5, 0]];
const clock = [[0, 2], [1, 3], [2, 1], [3, 0]];
const mapping = {
L: left,
R: oppsite(left),
F: front,
B: oppsite(front),
A: oppsite(clock),
C: clock
};
if (!lines[0]) {
console.log(nums.join(''));
return;
}
for (let dir of lines[0]) {
const i = mapping[dir];
[nums[i[0][0]], nums[i[1][0]], nums[i[2][0]], nums[i[3][0]]] =
[nums[i[0][1]], nums[i[1][1]], nums[i[2][1]], nums[i[3][1]]];
}
console.log(nums.join(''));
});
八、复杂度分析
- 时间复杂度:O(n) ,n 为操作序列长度(≤ 50)
- 空间复杂度:O(1) ,状态始终是 6 个元素
九、总结
这道题的关键并不在于“怎么换变量”,而在于:
能否把骰子的旋转抽象成一组稳定、可逆、可组合的置换规则
一旦完成这种建模:
- 代码更短
- 逻辑更安全
- 思路可以直接迁移到魔方、方向旋转、有限状态机等问题
如果你也在刷类似的“状态旋转类”题目,强烈建议尝试这种 索引置换 的建模方式。