leetcode46全排列

给定一个不含重复数字的数组 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]]

提示:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数 互不相同

步骤1:问题定义及分析

题目描述

给定一个不含重复数字的整数数组 nums,返回其所有可能的全排列。全排列指的是数组中所有元素的不同排列方式。题目要求输出所有排列,可以按照任意顺序返回。

输入输出条件

  • 输入:一个不含重复数字的整数数组 nums
  • 输出:包含所有可能排列的二维数组。

限制条件

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数互不相同。

边界条件

  • 数组只有一个元素时,输出应该是该元素的排列,只有一个排列方式。
  • 数组的长度较小(最大为6),因此可以使用递归或回溯算法,性能不会成为瓶颈。

步骤2:问题分解及解题思路

解题思路 : 要找出所有排列,我们可以采用回溯算法 ,回溯的核心思想是逐步选择每个位置的元素并进行递归搜索,当到达终点(即已选择了所有元素)时,就得到了一个排列。

回溯算法设计:
  1. 回溯的状态:每一次递归调用时,选择一个元素加入到当前排列中,剩余元素继续递归生成排列。
  2. 剪枝条件:当某个元素已经被选过时,不再选择该元素。
  3. 终止条件:当排列的长度等于数组长度时,表示已经生成了一个完整的排列,将其加入结果中。
递归步骤:
  1. 初始化一个空数组 path 用于存储当前排列。
  2. 使用一个布尔数组 used 来标记哪些元素已经被使用过。
  3. 通过递归生成排列:
    • 遍历数组中的元素,选择尚未使用的元素加入当前排列。
    • 标记该元素为已使用,递归进入下一层。
    • 当当前排列长度等于 nums 的长度时,表示找到了一个有效的排列,加入结果。
    • 回溯,撤销选择,继续尝试其他元素。
时间复杂度与空间复杂度:
  • 时间复杂度 :每个排列的生成时间是 O(n),生成排列的总数是 n!,因此总时间复杂度是 O(n!)
  • 空间复杂度 :需要存储每次递归状态的栈空间,最大深度为 n,因此空间复杂度是 O(n)

步骤3:代码实现

以下是基于回溯算法的 C++ 实现:

cpp 复制代码
class Solution {
public:
    // 回溯函数
void backtrack(vector<int>& nums, vector<int>& path, vector<bool>& used, vector<vector<int>>& result) {
    // 终止条件:当路径长度等于nums长度时,表示找到了一个排列
    if (path.size() == nums.size()) {
        result.push_back(path);
        return;
    }
    
    // 遍历nums数组,进行回溯
    for (int i = 0; i < nums.size(); i++) {
        // 剪枝条件:如果当前元素已经使用过,跳过
        if (used[i]) continue;
        
        // 做选择
        path.push_back(nums[i]);
        used[i] = true;
        
        // 递归生成排列
        backtrack(nums, path, used, result);
        
        // 撤销选择
        path.pop_back();
        used[i] = false;
    }
}

vector<vector<int>> permute(vector<int>& nums) {
    vector<vector<int>> result;
    vector<int> path;
    vector<bool> used(nums.size(), false);  // 用于标记元素是否被使用
    backtrack(nums, path, used, result);
    return result;
}

};
代码注释:
  1. backtrack 函数负责递归生成排列。它接收参数 nums(输入数组)、path(当前排列路径)、used(元素使用标记)、result(最终结果)。
  2. path 的长度等于 nums 的长度时,说明当前排列已经完成,将其加入到结果 result 中。
  3. 通过递归逐步构建排列,每次选一个未被使用的元素加入 path,然后递归。递归完成后,撤销选择并继续尝试其他元素。
  4. permute 函数是主函数,负责初始化回溯过程,并返回最终的排列结果。

步骤4:启发与总结

通过这个问题,我们可以获得以下启发:

  • 回溯算法是处理组合、排列等问题的有力工具,特别适用于解答具有约束条件(如每个元素只能出现一次)的排列问题。
  • 回溯算法的核心思想在于通过递归逐步构建解空间,利用"选择-递归-撤销选择"来枚举所有解。
  • 该算法尤其适用于小规模数据(如 n <= 6 的情况),可以迅速给出正确解。

步骤5:实际应用示例

实际应用场景

回溯算法中的排列生成不仅适用于基础的算法题,还可以广泛应用于多个实际问题中。例如:

  • 任务调度与资源分配:在某些任务调度问题中,需要将多个任务安排在多个资源上,保证每个资源的任务顺序不重复。回溯算法可以用来生成所有可能的任务安排,并对其进行评估和选择。

  • 旅行商问题(TSP):尽管旅行商问题本身是一个NP难问题,但可以通过回溯方法生成所有可能的路径并选择最短路径作为解。

  • 排列组合游戏:很多游戏和应用中需要生成或评估所有可能的排列。例如,在扑克游戏中,可能需要生成牌的所有组合,进行概率分析或策略优化。

示例

假设在某个电子商务平台中,存在多个商品,商家希望为每个商品选择一个配送方式,并且希望了解所有可能的配送方案(即排列)。通过回溯算法,我们可以生成所有配送方案,并进行分析,优化物流调度策略。

相关推荐
想跑步的小弱鸡8 分钟前
Leetcode hot 100(day 3)
算法·leetcode·职场和发展
xyliiiiiL2 小时前
ZGC初步了解
java·jvm·算法
爱的叹息2 小时前
RedisTemplate 的 6 个可配置序列化器属性对比
算法·哈希算法
独好紫罗兰3 小时前
洛谷题单2-P5713 【深基3.例5】洛谷团队系统-python-流程图重构
开发语言·python·算法
每次的天空3 小时前
Android学习总结之算法篇四(字符串)
android·学习·算法
请来次降维打击!!!4 小时前
优选算法系列(5.位运算)
java·前端·c++·算法
qystca4 小时前
蓝桥云客 刷题统计
算法·模拟
别NULL4 小时前
机试题——统计最少媒体包发送源个数
c++·算法·媒体
weisian1514 小时前
Java常用工具算法-3--加密算法2--非对称加密算法(RSA常用,ECC,DSA)
java·开发语言·算法
程序员黄同学6 小时前
贪心算法,其优缺点是什么?
算法·贪心算法