1.题目描述:
2.题目描述:
给定一个不含重复数字的数组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 中的所有整数 互不相同
3. 解法:
算法思路:
典型的回溯题目,我们需要在每一个位置上考虑所有的可能情况并且不能出现重复。通过深度优先搜索的方式,不断地枚举每个数在当前位置的可能性,并回溯到上一个状态,直到枚举完所有可能性,得到正确的结果。
每个数是否可以放入当前位置,只需要判断这个数在之前是否出现即可。具体地,在这道题目中,我们可以通过一个递归函数 backtrack 和标记数组 visited 来实现全排列。
递归函数设计:void backtrack(vector<vector<int>>& res, vector<int>& nums, vector<bool>& visited, vector<int>& ans, int step, int len)
参数:step(当前需要填入的位置),len(数组长度);
返回值:无;
函数作用:查找所有合理的排列并存储在答案列表中。
递归流程如下:
1. 首先定义一个二维数组 res 用来存放所有可能的排列,一个一维数组 ans 用来存放每个状态的排列,一个一维数组 visited 标记元素,然后从第一个位置开始进行递归;
2. 在每个递归的状态中,我们维护一个步数 step,表示当前已经处理了几个数字;
3. 递归结束条件:当 step 等于 nums 数组的长度时,说明我们已经处理完了所有数字,将当前数组存入结果中;
4. 在每个递归状态中,枚举所有下标 i,若这个下标未被标记,则使用 nums 数组中当前下标的元 素:
a. 将 visited[i] 标记为 1;
b. ans 数组中第 step 个元素被 nums[i] 覆盖;
c. 对第 step+1 个位置进行递归;
d. 将 visited[i] 重新赋值为 0,表示回溯;
5. 最后,返回 res。
•特别地,我们可以不使用标记数组,直接遍历 step 之后的元素(未被使用),然后将其与需要递 归的位置进行交换即可。
Java算法代码:
class Solution {
List<List<Integer>> ret;
List<Integer> path;
boolean[] check;
public List<List<Integer>> permute(int[] nums) {
ret = new ArrayList<>();
path = new ArrayList<>();
check = new boolean[nums.length];
dfs(nums);
return ret;
}
public void dfs(int nums[]) {
if(nums.length == path.size()){
ret.add(new ArrayList<>(path));
return ;
}
for(int i = 0;i< nums.length ;i++){
if(check[i] == false) {
path.add(nums[i]) ;
check[i] = true;
dfs(nums);
//回溯 ->恢复现场
check[i] = false;
path.remove(path.size()-1);
}
}
}
}
执行结果:
递归展开:
注意这里笔者并没有展开完,而是只展开了一次循环(还差两次),强烈建议读者自己来展开。或者是简化[1,2],但是需注意,[1,2]展开可能并不是那么的好(使得你很好的理解)。
逻辑展开:
请注意,前面的递归题目,都在灌输的思想是,相信你的递归函数!
从这里题目开始,读者应该发现,题目难度直线上升,现在要开始关注递归函数的实现。
---------------------------------------------------------------------------------------------------------------------------------
记住,相信你的递归函数,它可以做到!
记住,不理解时候,去尝试手动展开!
记住,逻辑展开(你不可能对所有的题目都进行手动展开)!
记住,理解递归函数!