leetcode回溯算法(46.全排列)

首先排列是有序的,也就是说 [1,2] 和 [2,1] 是两个集合,这和之前分析的子集以及组合所不同的地方

可以看出元素1在[1,2]中已经使用过了,但是在[2,1]中还要在使用一次1,所以处理排列问题就不用使用startIndex了。

但排列问题需要一个used数组,标记已经选择的元素,如图橘黄色部分所示:

可以看出叶子节点,就是收割结果的地方。

那么什么时候,算是到达叶子节点呢?

当收集元素的数组path的大小达到和nums数组一样大的时候,说明找到了一个全排列,也表示到达了叶子节点。

cpp 复制代码
if (path.size() == nums.size()) {
    result.push_back(path);
    return;
}

本题的for循环里不用startIndex了。

因为排列问题,每次都要从头开始搜索,例如元素1在[1,2]中已经使用过了,但是在[2,1]中还要再使用一次1。

而used数组,其实就是记录此时path里都有哪些元素使用了,一个排列里一个元素只能使用一次

cpp 复制代码
class Solution {
public:
    vector<vector<int>> result; // 存储所有排列结果的二维数组
    vector<int> path; // 存储当前正在构建的排列(路径)
    
    // 回溯函数
    // nums: 原始输入数组
    // used: 标记数组元素是否已被使用的布尔数组
    void backtracking (vector<int>& nums, vector<bool>& used) {
        // 终止条件:当前路径长度等于原始数组长度
        // 说明已经构造出一个完整的排列
        if (path.size() == nums.size()) {
            result.push_back(path); // 将当前排列加入结果集
            return; // 返回上一层递归
        }
        
        // 遍历所有可能的元素选择
        for (int i = 0; i < nums.size(); i++) {
            // 跳过已使用的元素(避免重复选择)
            if (used[i] == true) continue;
            
            // 做出选择
            used[i] = true; // 标记当前元素为已使用
            path.push_back(nums[i]); // 将当前元素加入路径
            
            // 递归进入下一层决策树
            backtracking(nums, used);
            
            // 撤销选择(回溯)
            path.pop_back(); // 从路径中移除最后一个元素
            used[i] = false; // 标记当前元素为未使用
        }
    }
    
    // 主函数:生成全排列
    vector<vector<int>> permute(vector<int>& nums) {
        result.clear(); // 清空结果集(防止多次调用时数据残留)
        path.clear(); // 清空当前路径
        vector<bool> used(nums.size(), false); // 初始化used数组,所有元素初始未使用
        backtracking(nums, used); // 开始回溯
        return result; // 返回所有排列结果
    }
};

nums = [1,2,3] 为例,详细分析代码执行过程

初始状态

复制代码
nums = [1,2,3]
used = [false, false, false]
path = []
result = []

递归 backtracking ([1,2,3],[false, false, false])

复制代码
if判断,path = [] (长度0 < 3,继续执行
cpp 复制代码
for (int i = 0; i < nums.size(); i++) {
    if (used[i] == true) continue; // path里已经收录的元素,直接跳过
    used[i] = true;
    path.push_back(nums[i]);
    backtracking(nums, used);
    path.pop_back();
    used[i] = false;
}
复制代码
开始for循环,i=0
if (used[i] == true) → 因为used[0]=false,所以不执行if语句
used[0]=true,used=[True,F,F]
path.push_back(nums[0]) → path.push_back(1),path变为[1]
调用backtracking(nums, used)

递归 backtracking ([1,2,3],[true, false, false])

复制代码
path=[1], used=[T,F,F]
if (path.size() == nums.size()) → 1 == 3 ❌,所以不执行if语句
for 开始循环  , i=0:
if (used[i] == true) → used[0]==true ✅,执行continue,跳到下一个i

i++,现在i=1:
if (used[i] == true) → used[1]==false ❌,所以不执行if语句
used[1]=true,used变为[T,T,F]
path.push_back(nums[0]) → path.push_back(2),path变为[1,2]
backtracking(nums, used)

递归 backtracking ([1,2,3],[true, true, false])

复制代码
path=[1,2], used=[T,T,F]
if (path.size() == nums.size()) → 2 == 3 ❌,不执行if语句
for开始循环 , i=0:
if (used[i] == true) → used[0]==true ✅,continue

i++,i=1:
第13行: if (used[i] == true) → used[1]==true ✅,continue

循环 i=2:
if (used[i] == true) → used[2]==false ❌,不执行if语句
used[i] = true → used[2]=true,used变为[T,T,T]
path.push_back(nums[2]) → path.push_back(3),path变为[1,2,3]
backtracking(nums, used) 

递归 backtracking ([1,2,3],[true, true, true])

复制代码
path=[1,2,3], used=[T,T,T]
if (path.size() == nums.size()) → 3 == 3 ✅,进入if
执行if语句,result.push_back(path) → result为[1,2,3]
return → 这个递归结束,返回到上一个递归

返回backtracking ([1,2,3],[true, true, false])

复制代码
之前path=[1,2,3], used=[T,T,T]
执行回溯
path.pop_back() → path变为[1,2]
used[i] = false → used[2]=false,used变为[T,T,F]
i++,i=3 ,for循环结束 (i从2增加到3,循环条件i<3不满足)
这个递归结束

.........

.........

.........

相关推荐
We་ct2 小时前
LeetCode 68. 文本左右对齐:贪心算法的两种实现与深度解析
前端·算法·leetcode·typescript
努力学算法的蒟蒻2 小时前
day67(1.26)——leetcode面试经典150
算法·leetcode·面试
iAkuya2 小时前
(leetcode) 力扣100 52腐烂的橘子(BFS)
算法·leetcode·宽度优先
老鼠只爱大米2 小时前
LeetCode经典算法面试题 #148:排序链表(插入、归并、快速等五种实现方案解析)
算法·leetcode·链表·插入排序·归并排序·快速排序·链表排序
木井巳2 小时前
【递归算法】计算布尔二叉树的值
java·算法·leetcode·深度优先
睡一觉就好了。2 小时前
直接选择排序
数据结构·算法·排序算法
哈哈不让取名字2 小时前
分布式日志系统实现
开发语言·c++·算法
芬加达3 小时前
leetcode221 最大正方形
java·数据结构·算法