力扣刷题笔记-全排列

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

分析:

这一题是一个回溯算法题,因为是排列,所以第一次选择了1之后,再选跟在后面的数的时候1就不能再选了。可以把全排列问题想成给椅子安排人。假设有三把椅子排成一排,对应数组中的数字 1、2、3,规则是每个人只能坐一次。在这个过程中,我们需要一个标记来表示已经有了椅子,不能再选这个人了。可以用path 表示当前已经坐在椅子上的人,used 用来标记哪些人已经坐过,如果已经做工,那么就把对应的人标记成true。

最开始的时候三把椅子都是空的,然后首先站在第一把椅子前,依次尝试让 1、2、3 坐下。先让 1 坐在第一把椅子上,此时状态是 1 _ _。接着来到第二把椅子,因为 1 已经坐过,只能在 2 和 3 中选择,先让 2 坐下,状态变为 1 2 _。然后来到第三把椅子,此时 1 和 2 都已经使用过,只剩下 3 可以坐,于是形成 1 2 3。三把椅子全部坐满,说明得到了一种完整排列,于是把 [1,2,3] 记录下来。

记录完成后,开始回溯,先把 3 从第三把椅子上请走,椅子的状态回到 1 2 _。这一步的目的,是想看看在第三把椅子这个位置,还有没有别的人可以坐。但此时可以发现,除了 3 以外,1 和 2 都已经坐过了,第三把椅子已经没有任何其他选择。也就是说,在"第一把椅子是 1、第二把椅子是 2"这个前提下,第三把椅子的所有可能性已经全部尝试完了,这一层已经走到尽头,再往下不会产生新结果。

既然第三把椅子已经没路可走,就只能继续往回退,把第二把椅子上的 2 也请走,状态变回 1 _ _。回到这里之后,情况才发生变化:第二把椅子除了 2 以外,还可以选择 3。于是改让 3 坐在第二把椅子上,得到 1 3 _,再来到第三把椅子,此时只能让 2 坐下,形成 1 3 2,这又是一个完整排列,记录下来。

接着用同样的方式继续回溯和尝试:当以 1 开头的所有情况都尝试完后,就把第一把椅子上的 1 请走,改让 2 坐在第一把椅子上,得到 2 _ _,再安排后两把椅子,生成 [2,1,3] 和 [2,3,1];最后再让 3 坐在第一把椅子上,同理得到 [3,1,2] 和 [3,2,1]。整个过程就是不断地在每一把椅子前,从还没坐过的人里选一个坐下,坐满就记录,发现当前椅子没有新选择时就继续往回退,直到所有可能的坐法都被尝试一遍。

总之,算法就是从左到右逐个位置填数字,每一层递归负责"填一个位置",每一层都尝试所有还没用过的数字,当填满时记录结果,然后逐层撤销选择,继续尝试其他可能。

这里要注意一下,used的位数应该和nums的位数一样,这样才可以一 一对应进行标记。

C++代码:

cpp 复制代码
#include<iostream>
#include<vector>
using namespace std;
class Solution {
public:
	vector < vector<int>> res; // res:结果集
	vector<int> path;// path:当前排列
	vector<bool> used;// used:标记数组,防止重复使用
	vector < vector<int>> permute(vector<int>& nums) {
		used.resize(nums.size(), false);// 初始化 used
		backtrack(nums);
		return res;
	}
	void backtrack(vector<int>& nums) {
		if (path.size() == nums.size()) {// 终止条件:排列长度等于 nums 长度
			res.push_back(path);
			return;
		}
		for (int i = 0; i < nums.size(); i++) {
			if (used[i] == true)// 已用过,跳过
				continue;
			path.push_back(nums[i]);// 选择
			used[i] = true;
			backtrack(nums);// 标记为已使用
			path.pop_back();// 回溯
			used[i] = false;
		}
	}

};

用nums = [1,2,3]为例走一遍算法:

一、初始状态

当前还没有选择任何数字,三个变量的值是:

  • path = []
  • used = [false, false, false]
  • res = []

算法从第一个位置开始尝试所有可能的数字。

二、第一层递归:选择第一个元素

选择 nums[0] = 1

因为 used[0] 为 false,可以选择 1,于是把 1 加入 path 并标记已使用:

  • path = [1]
  • used = [true, false, false]

随后进入下一层递归,去确定第二个位置的数字。

第二层递归:在 [1] 的基础上选择第二个元素,选择 nums[1] = 2

  • path = [1, 2]
  • used = [true, true, false]

继续进入下一层递归,去确定第三个位置。

第三层递归:在 [1,2] 的基础上选择第三个元素,nums[2] 未使用,可以选择。选择 nums[2] = 3

  • path = [1, 2, 3]
  • used = [true, true, true]

此时 path 的长度等于 nums 的长度,说明已经形成一个完整排列。
将当前排列加入结果集:res = [[1, 2, 3]]

随后这一层递归结束,开始向上一层返回,回溯。撤销刚才的选择,把 3 从 path 中移除,并恢复使用状态:

  • path = [1, 2]
  • used = [true, true, false]

第三层已经没有其他可选数字,继续返回上一层。回溯到第二层(撤销 2),撤销第二层的选择。

  • path = [1]
  • used = [true, false, false]

第二层的循环还没结束,因此继续尝试下一个未使用的数字,继续选择 nums[2] = 3

由于 nums[2] 还未使用,将其加入 path:

  • path = [1, 3]
  • used = [true, false, true]

进入下一层递归。第三层递归:在 [1,3] 的基础上选择第三个元素

此时只有 nums[1] 未使用,选择 nums[1] = 2

  • path = [1, 3, 2]
  • used = [true, true, true]

再次满足终止条件,将排列加入结果集,res = [[1, 2, 3], [1, 3, 2]]

随后这一层递归结束,开始向上一层返回,进行回溯。

首先撤销第三层的选择,把刚才加入的 2 从 path 中移除,并恢复其使用状态:

  • path = [1, 3]
  • used = [true, false, true]

第三层已经没有其他可选数字,因此继续返回上一层。撤销第二层中选择的 3,恢复状态:

  • path = [1]
  • used = [true, false, false]

第二层的循环已经结束,没有更多可选数字,继续返回上一层。

  • path = []
  • used = [false, false, false]

此时,以 1 作为第一个元素的所有排列已经生成完毕。

三、第一层选择 nums[1] = 2

由于 nums[1] 尚未使用,将其加入 path:

  • path = [2]
  • used = [false, true, false]

进入下一层递归。

第二层递归:在 [2] 的基础上选择第二个元素

nums[0] 未使用,选择 1

  • path = [2, 1]
  • used = [true, true, false]

进入第三层递归,在 [2,1] 的基础上选择第三个元素

此时只有 nums[2] 未使用,选择 3:

  • path = [2, 1, 3]
  • used = [true, true, true]
  • 形成完整排列,加入结果集:res = [[1, 2, 3], [1, 3, 2], [2, 1, 3]]

随后开始回溯,撤销 3

  • path = [2, 1]
  • used = [true, true, false]

第三层无其他选择,返回上一层。撤销 1

  • path = [2]
  • used = [false, true, false]

第二层循环继续,尝试下一个未使用的数字nums[2] = 3

  • path = [2, 3]
  • used = [false, true, true]

进入第三层递归,在 [2,3] 的基础上选择第三个元素。此时只有 nums[0] 未使用,所以选择 1

  • path = [2, 3, 1]
  • used = [true, true, true]

形成完整排列,加入结果集:res = [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1]]

随后开始回溯。撤销 1 → 撤销 3 → 撤销 2,依次返回并撤销选择,最终回到第一层初始状态:

  • path = []
  • used = [false, false, false]

四、第一层继续最后一次循环,选择 nums[2] = 3

  • path = [3]
  • used = [false, false, true]

进入第二层递归,先选择 nums[0] = 1

  • path = [3, 1]
  • used = [true, false, true]

再选择 nums[1] = 2,最终得到:

  • path = [3, 1, 2]
  • used = [true, true, true]

加入结果集:res = [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2]]

随后第三层递归结束,开始回溯,撤销刚才选择的 2:

回溯后状态为:

  • path = [3, 1]
  • used = [true, false, true]

第三层 for 循环已结束,没有其他可选数字,返回上一层。撤销第二层中选择的1:

  • path = [3]
  • used = [false, false, true]
    第二层 for 循环尚未结束,继续尝试下一个未使用的数字。检查 used[1],当前为 false,可以选择 nums[1] = 2。
  • path = [3, 2]
  • used = [false, true, true]

进入第三层递归。选择 nums[0] = 1,选择后状态为:

  • path = [3, 2, 1]
  • used = [true, true, true]

此时再次形成一个完整排列。
将当前排列加入结果集:res = [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

随后第三层递归结束,开始回溯。依次撤销 1 → 2 → 3,所有递归结束,算法执行结束。

相关推荐
菜鸟233号2 小时前
力扣669 修剪二叉搜索树 java实现
java·数据结构·算法·leetcode
光羽隹衡2 小时前
机械学习逻辑回归——银行贷款案例
算法·机器学习·逻辑回归
Code Warrior3 小时前
【C++】智能指针的使用及其原理
开发语言·c++
能源系统预测和优化研究3 小时前
创新点解读:基于非线性二次分解的Ridge-RF-XGBoost时间序列预测(附代码实现)
人工智能·深度学习·算法
执笔论英雄3 小时前
【RL】ROLL下载模型流程
人工智能·算法·机器学习
لا معنى له3 小时前
目标分割介绍及最新模型----学习笔记
人工智能·笔记·深度学习·学习·机器学习·计算机视觉
yaoh.wang3 小时前
力扣(LeetCode) 100: 相同的树 - 解法思路
python·程序人生·算法·leetcode·面试·职场和发展·跳槽
SadSunset3 小时前
力扣题目142. 环形链表 II的解法分享,附图解
算法·leetcode·链表
月光在发光3 小时前
多态(虚函数核心作用原理)--C++学习(0)
c++·学习