一、题目回顾:全排列
题目描述
给定一个由不同小写字母组成的字符串(长度 1-6),输出它的所有全排列,按字典序从小到大输出。
输入格式
一行,一个字符串,字母已按从小到大排序。
输出格式
每行一个排列,按字典序输出。
样例输入
text
abc
样例输出
text
abc acb bac bca cab cba
二、常见误区
初学者看到这道题的第一反应往往是尝试手动模拟或使用栈来生成所有排列。但这种方法维护的状态太多,代码难以实现。
正确的思路是使用递归,但常伴随以下疑问:
- 为什么要用
bool used[]? - 为什么要用
char path[]? - 为什么要传一个
depth参数? - 为什么选完之后还要撤销?
三、算法原理:放牌游戏
理解全排列的最好方法是将其映射为现实场景。想象手上有 3 张牌(a、b、c),要摆出所有可能的排列。
自然的做法是:
- 先拿一张放桌上(比如 a)
- 从剩下的两张里再拿一张放后面(比如 b)
- 最后一张放最后(c)
此时顺序是 abc,记录结果。
接着回退:
- 把最后放的 c 收回手里,换另一张?只剩 c 了,所以再退一步
- 退到手里有 b、c 的状态,刚才选了 b,现在试试选 c
- 最后一张放 b → acb
继续这样退、试、进,就能得到所有排列。
四、数据结构映射
将'放牌游戏'的每一步翻译成代码中的数据结构:
| 现实中的东西 | 代码中的数据结构 | 为什么需要它 |
|---|---|---|
| 手里还有什么牌 | bool used[] | 标记哪些字母还没用 |
| 桌上已经摆了什么 | char path[] | 记录当前已选的排列 |
| 现在要摆第几个位置 | int depth | 知道该把牌放哪里 |
| 摆满一轮了 | depth == len | 可以输出当前排列 |
| 把牌收回手里 | used[i] = false | 回溯,尝试其他可能 |
五、核心数据结构解析
1. 为什么需要 bool used[]?
如果没有 used[],会在每个位置都尝试 a、b、c,导致重复使用字符(如 aaa),这变成了有放回的组合而非排列。
used[] 保证每个字母只用一次。
2. 为什么需要 char path[]?
当 depth == len 时,需要知道'当前排出来的是什么'。path[] 记录当前已选路径。

