一.全排列:
1.题目描述:

2.题目链接:
3.算法原理:
第一步:画决策树
先别写代码,把"选择过程"画成树:

从图上读出三个结论:
| 概念 | 含义 |
|---|---|
| 层 | 已经选了几个数(path.size()) |
| 节点 | 当前的已选序列 path |
| 叶子 | 深度 = n,一个完整排列,只有叶子是答案 |
| 分支数 | 第 1 层 3 个、第 2 层 2 个、第 3 层 1 个 → 3! = 6 |
第二步:定状态,写骨架
2.1 三个状态变量
| 变量 | 类型 | 作用 |
|---|---|---|
ret |
vector<vector<int>> |
收集所有答案 |
path |
vector<int> |
当前路径(已选序列) |
check |
bool[] |
标记元素是否已被使用 |
2.2 DFS 骨架(六步口诀)
出口 → 枚举 → 剪枝 → 选 → 递归 → 撤
cpp
class Solution {
public:
vector<vector<int>> ret;
vector<int> path;
bool check[7];
vector<vector<int>> permute(vector<int>& nums) {
dfs(nums);
return ret;
}
void dfs(vector<int> nums)
{
if(path.size()==nums.size())
{
ret.push_back(path);// path满了,是一个合法排列,放进结果集
return;
}
for(int i=0 ;i<nums.size();i++)
{
if(!check[i])//nums[i]还没有被用
{
//1.做选择:把nums[i]拿进来
path.push_back(nums[i]);
check[i] = true;
//2.递归
dfs(nums);
//回溯核心和恢复现场
//递归跑完回来,把刚才选的数吐出去,标记改成未使用
path.pop_back();
check[i] = false;
}
}
}
};
写 DFS 时只需回答一个问题:站在某个节点上,我要做什么? ------挑一个没用过的数,递归下去,回来时把它放回去。仅此而已,不用在脑子里跑完整棵树。
第三步:三个细节
细节一:为什么要撤销?
path 和 check 是所有分支共用的一份状态,像一块黑板。走完一条路要擦干净,否则下一条分支会踩着上一条的粉笔字继续走。
cpp
path.push_back(nums[i]); check[i] = true; // 写
dfs(nums);
path.pop_back(); check[i] = false; // 擦
进去动了什么,出来就还原什么。
细节二:check 防什么?
防同一个数被用两次。没它的话,每层都能从 n 个里随便挑,叶子数变成 n^n(还有 [1,1,1] 这种废品);有了它,剩下几个就只能挑几个,正好 n!。
细节三:什么时候算找到一个?
凑满 n 个数才算(path.size() == n)。排列要选满,子集"选过就算"------这就是两者的分界线。
4.代码实现:

二.子集:
1.题目描述:

2.题目链接:
3.算法原理:
为了获得 nums 数组的所有⼦集,我们需要对数组中的每个元素进⾏选择或不选择的操作,即 nums数组⼀定存在 2^(数组⻓度) 个⼦集。对于查找⼦集,具体可以定义⼀个数组,来记录当前的状态,并对其进⾏递归。
对于每个元素有两种选择:
- 不进⾏任何操作;
- 将其添加⾄当前状态的集合。在递归时我们需要保证递归结束时当前的状态与进⾏递归操作前的状态不变,⽽当我们在选择进⾏步骤 2 进⾏递归时,当前状态会发⽣变化,因此我们需要在递归结束时撤回添加操作,即进⾏回溯。
递归流程如下:
- 递归结束条件:如果当前需要处理的元素下标越界,则记录当前状态并直接返回;
- 在递归过程中,对于每个元素,我们有两种选择:
- 不选择当前元素,直接递归到下⼀个元素;
- 选择当前元素,将其添加到数组末尾后递归到下⼀个元素,然后在递归结束时撤回添加操作;
- 所有符合条件的状态都被记录下来,返回即可。
解法一:对每个元素做选 / 不选
树是满二叉树 ,每层只处理 nums[i] 一个元素,分两条边:

cpp
void dfs(vector<int>& nums, int i) {
if (i == nums.size()) { // 所有元素都决策完了
ret.push_back(path); // 叶子 = 一个完整子集
return;
}
// 不选 nums[i]
dfs(nums, i + 1);
// 选 nums[i]
path.push_back(nums[i]);
dfs(nums, i + 1);
path.pop_back(); // 回溯
}
解法二:枚举下一个选谁

cpp
void dfs(vector<int>& nums, int pos) {
ret.push_back(path); // 进来就收:每个节点都是子集
for (int i = pos; i < nums.size(); ++i) {
path.push_back(nums[i]);
dfs(nums, i + 1); // 只能往后选,避免 {2,1} 重复
path.pop_back();
}
}
4.代码实现:

