LeetCode.46全排列
难度:中等
目录
作者原始思路
API法
class Solution {
public List<List<Integer>> permute(int[] nums) {
var newList = new ArrayList<Integer>();
var set = new HashSet<List<Integer>>();
for (int num : nums) {
newList.add(num);
}
var n = nums.length;
var sum = 1;
while (n != 0) {
sum *= n;
n--;
}
while (set.size() < sum) {
var l = new ArrayList<>(newList);
Collections.shuffle(l);
set.add(l);
}
return new ArrayList<>(set);
}
}
思想简述与代码分析
- 将所有的元素放于ArrayList集合中,利用Collections工具类里面的一个方法,可以将集合里面的元素打乱顺序
- 将打乱的顺序放入新的List集合中,然后再加入Set集合,根据Set集合的无序不可重复性,就可以得到全排列的唯一解
- 根据推导,N个元素一共有N!种情况,最后返回结果即可
问题
反省
- 平常练习可以玩玩,到了考试或者是面试的时候可能就不给用API了,我们也不可能随时能看API,所以,这个方法仅供参考,不建议使用,所以,全排列的标准解法是什么呢?答案是DFS深度优先算法
官方解法-深度优先算法(DFS)
算法图解
示例:[1,2,3]全排列
图解分析
- 全排列问题我们可以构建一颗多叉树,利用子节点去逐步增加数据长度
- 假设刚开始的根节点是空集,则放置情况可以有三种,放1,放2或放3,所以对应图中一共有三个子节点
- 根据以上思路,可以把多叉树创建出来
- 我们发现,我们最终想要的结果都在叶子节点上,所以,我们得到结果的关键是构造一颗多叉树
- 构建多叉树有两种方式,即DFS或BFS,这里采用了DFS
算法思路
- 通过图解,我们可以知道什么时候取出结果,所以,我们需要有以下的状态变量
- 1.当前深度depth:深度depth用于记录DFS现在处于第几层,如果是最后一层就得到结果
- 2.路径path:路径path用于记录每一个节点的元素的情况,当然,path是动态的,因为DFS会有回溯过程,回溯后或递归中,与各节点的情况一致
- 3.层数len:层数len用于判断是否到达了叶子节点那一层
- 4.访问used:访问used用于记录元素是否被访问,避免同一个元素重复
代码实现
class Solution {
public List<List<Integer>> permute(int[] nums) {
var len = nums.length;
var path = new ArrayDeque<Integer>();
var used = new boolean[len];
var res = new ArrayList<List<Integer>>();
if (nums.length == 0) {
return res;
}
dfs(nums,res,0,path,used,len);
return res;
}
private void dfs(int[] nums, ArrayList<List<Integer>> res, int depth, ArrayDeque<Integer> path, boolean[] used, int len) {
if (depth == len) {
res.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < len; i++) {
if (used[i]) {
continue;
}
path.addLast(nums[i]);
used[i] = true;
dfs(nums,res,depth+1,path,used,len);
path.removeLast();
used[i] = false;
}
}
}
代码分析
- 1.创建对应的变量
- 1.1注意:path是一个栈,即用到了SUN公司所给的集合,便于我们最后的追加(在图例中,总是在后面追加)
- 2.dfs方法的参数
- 2.1nums数组,用于获取数据,追加到path中
- 2.2res集合,用于将结果放入集合中
- 2.3depth,path,used,len与思路一致,注意depth一开始为0,因为一开始是根节点
- 3.如果当前的depth等于len,说明深度优先到了最后一层,就是我们所需要的答案,直接假如res集合中即可,直接return
- 4.for循环
- 4.1如果当前元素被访问,直接跳过
- 4,2如果没有没访问,先将其追加到path后面,然后再设置为已访问
- 4.3调用dfs方法,进行深度优先
- 5.如果执行到这一步,说明已经发生了回溯,我们需要将之前追加和访问的元素删去,即设置为未访问,和把栈最后的元素删除
知识扩展
为什么要有for循环
- 从图解可知,一层中有多个节点,多叉树中有多层,我们调用dfs方法就是为了从上一层跳到下一层,for循环的原因是遍历同一层的多个节点!
结论
深度优先算法,和动态规划算法一样,都是比较常见的算法,而且算法的难度也存在,所以,我们需要掌握深度优先这种算法,最后,我来总结一下的几点
1.全排列问题如何构建多叉树
2.如何从多叉树提取出关键的状态变量
3.深度优先算法的实现