【回溯-4】46.全排列

题目描述:

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

复制代码
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例 2:

复制代码
输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:

复制代码
输入:nums = [1]
输出:[[1]]

解题思路:

方法一:回溯 + used 数组

核心思路:

把问题看成树形结构:

  • 每一层选择一个数字

  • 每个数字只能用一次

  • 用 used 数组标记哪些数字已经被选

具体过程示例:

nums = [1,2,3]

复制代码
                    []
          /         |         \
         1          2          3
        / \        / \        / \
       2   3      1   3      1   2
       |   |      |   |      |   |
       3   2      3   1      2   1

所有路径:
1→2→3, 1→3→2
2→1→3, 2→3→1
3→1→2, 3→2→1

代码实现:

cpp 复制代码
class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> result;
        vector<int> path;
        vector<bool> used(nums.size(), false);
        backtrack(nums, used, path, result);
        return result;
    }
    
private:
    void backtrack(vector<int>& nums, vector<bool>& used,
                   vector<int>& path, vector<vector<int>>& result) {
        // 终止条件:path 长度等于 nums 长度
        if (path.size() == nums.size()) {
            result.push_back(path);
            return;
        }
        
        // 遍历所有数字
        for (int i = 0; i < nums.size(); i++) {
            if (used[i]) continue;  // 已使用,跳过
            
            used[i] = true;              // 标记已使用
            path.push_back(nums[i]);     // 选择
            backtrack(nums, used, path, result);  // 递归
            path.pop_back();             // 撤销
            used[i] = false;             // 取消标记
        }
    }
};

复杂度分析:

设 n 是数组长度。

维度 复杂度 说明
时间复杂度 O(n × n!) n! 个排列,每个排列长度 n
空间复杂度 O(n) 递归栈深度 + path + used

关键细节:

  1. 为什么用 used 数组?

因为每个元素只能用一次,需要标记哪些元素已经被选。

  • used[i] = true:表示 nums[i] 已经被选

  • used[i] = false:表示 nums[i] 还没被选

  1. 为什么 path.size() == nums.size() 是终止条件?

当 path 长度等于 nums 长度时,说明所有元素都被选了,得到一个完整排列。

  1. 和「组合总和」的区别
题目 区别
39. 组合总和 可重复选,用 start 避免重复组合
46. 全排列 不可重复选,用 used 标记已选
78. 子集 不可重复选,用 start 控制顺序

关键区别:

  • 组合:顺序不重要,用 start 避免重复

  • 排列:顺序重要,用 used 标记已选

方法二:交换法(不需要 used 数组)

思路:把数组分成"已选"和"未选"两部分,通过交换把未选的元素换到当前位置。

代码实现:

cpp 复制代码
class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> result;
        backtrack(nums, 0, result);
        return result;
    }
    
private:
    void backtrack(vector<int>& nums, int start, vector<vector<int>>& result) {
        if (start == nums.size()) {
            result.push_back(nums);
            return;
        }
        
        for (int i = start; i < nums.size(); i++) {
            swap(nums[start], nums[i]);           // 选择
            backtrack(nums, start + 1, result);   // 递归
            swap(nums[start], nums[i]);           // 撤销
        }
    }
};

优点 :不需要 used 数组,空间更省。

缺点:修改了原数组(但会恢复)。

方法三:插入法(迭代)

思路:逐个插入元素,生成所有排列。

代码实现:

cpp 复制代码
class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> result = {{}};
        
        for (int num : nums) {
            vector<vector<int>> next;
            for (auto& perm : result) {
                for (int i = 0; i <= perm.size(); i++) {
                    vector<int> newPerm = perm;
                    newPerm.insert(newPerm.begin() + i, num);
                    next.push_back(newPerm);
                }
            }
            result = move(next);
        }
        
        return result;
    }
};

复杂度:时间 O(n × n!),空间 O(n × n!)

三种方法对比:

方法 时间复杂度 空间复杂度 是否修改原数组 推荐度
回溯 + used O(n × n!) O(n) ❌ 不修改 ⭐⭐⭐⭐⭐
交换法 O(n × n!) O(n) ✅ 修改(会恢复) ⭐⭐⭐⭐
插入法 O(n × n!) O(n × n!) ❌ 不修改 ⭐⭐⭐

总结:

要点 说明
核心思想 回溯:每层选一个未使用的数字
关键操作 used[i] = true → 递归 → used[i] = false
终止条件 path.size() == nums.size()
时间复杂度 O(n × n!)
空间复杂度 O(n)
相关推荐
小鱼~~1 小时前
Recall@K、Precision@K、MRR、nDCG@K简介
人工智能·算法·机器学习
在所不辞兄1 小时前
【零基础学智能仿真-38】从规则矩形到带孔薄板:Gmsh 网格与 FEniCSx 联合求解
人工智能·深度学习·算法·机器学习·工程仿真
无名猿1 小时前
裸指针还能不能用:不拥有与拥有的边界
c++·内存管理·现代c++·语法基础
Yyyyyy~1 小时前
【C++】STLday1
c++
无名猿1 小时前
左值、右值、纯右值、将亡值:值类别入门
c++·内存管理·现代c++·语法基础
一木 之林1 小时前
DDPM扩散模型代码实战:前向加噪造数据、U-Net噪声预测与反向生成手写数字
人工智能·算法·计算机视觉
程序员老陆2 小时前
C++ 将亡值(xvalue)深度解析:从表达式分类到参数传递陷阱
c++·程序设计
嘎嘎风2 小时前
MiniMind 学习笔记之 02 字是怎么变成数字的?
算法
QAQmmttyyy2 小时前
ChatFly 开发实录 ⑧|一个 VAD 的自我修养
算法