文章目录
46.全排列(入门,大排列中不包含重复数字)
给定一个 没有重复 数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
为什么 树 这么重要,因为暴力破解的多层for循环都可以用递归树来解决。
数学特性(题目隐患条件):全排列就是每个元素都要用到,所有只要取 叶子节点 ,不需要分支节点。
这里和组合问题、切割问题和子集问题最大l两个不同:
- for循环不是从startIndex开始,而是从0开始(输入给定一个数组就从0开始,输入给定一个n就从1开始)
- 递归函数的参数里面也不需要startIndex
- 需要used数组记录path里都放了哪些元素了
因为组合问题、切割问题、子集问题就用这两个来实现输出结果不同的,现在不需要了,就两个都不需要了
组合问题:题目给定明确条件,输出的子组合不能重复;
切割问题:
子集问题:题目给定明确条件,输出的子集合不能重复;集合的数学特性,一个大集合选子集合的时候,元素不能重复。
class Solution {
List<List<Integer>> result=new ArrayList<>();
List<Integer> path=new ArrayList<>();
public void backtracking(int[] nums,boolean[] used){
if (path.size() == nums.length){
result.add(new ArrayList<>(path));
return; // 得到一个全排列就好了,不用再深入了,就可以
}
for (int i=0;i<nums.length;i++){
if (true == used[i]) continue; // 如果用过了
path.add(nums[i]);
used[i]=true;
backtracking(nums,used);
path.remove(path.size()-1); // 算法题不用考虑多线程什么的,这种两句一段的代码顺序是可以随便写的
used[i]=false;
}
}
public List<List<Integer>> permute(int[] nums) {
boolean[] used=new boolean[nums.length];
for (int i=0;i<used.length;i++) // used中每一个元素就是代表nums中的元素是否使用过
used[i]=false;
backtracking(nums,used);
return result;
}
}
47.全排列 II(升级,大排列中包含重复数字)
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
示例 1:
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
示例 2:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
这道题目和回溯算法:排列问题!的区别在于「给定一个可包含重复数字的序列」,要返回「所有不重复的全排列」。
这里又涉及到去重了。
在回溯算法:求组合总和(三) 、回溯算法:求子集问题(二)我们分别详细讲解了组合问题和子集问题如何去重。
那么排列问题其实也是一样的套路。
核心:对于结果,元素在同一个子组合内是可以重复的,怎么重复都没事,但两子个组合不能相同
大组合中每个元素只能使用一次(非组合的数学特性,题目明确给定条件),且结果中不能包含相同子组合(题目明确给定输出结果要求,子组合中元素个数是无序的):大组合排好顺序之后,相同元素同一个树层中只能使用一次,出现两次,得到的子组合就有重复的了;
大集合中每一个元素只能使用一次(集合的数学特性,因为是集合,所以这样,题目隐含条件),结果中不能包含相同子集合(题目明确给定输出结果要求,子集合中元素个数是无序的):大集合排好顺序之后,相同元素只能使用一次,出现两次,得到的子集合就有重复的了。
递增子序列
大序列中每个元素只能使用一次(序列的数学特性,因为是序列,所以这样,题目隐含条件),结果中不能包含相同子序列(题目明确给定输出结果要求,子序列中元素个数要求是递增的,所以要剪枝if):大序列给定后(不能排序),同一树层相同元素只能使用一次,出现两次,得到的子序列就有重复的了。还有,子序列子序列中元素个数要求是递增的,所以要剪枝if。所以两个剪枝
「一般来说:组合问题和排列问题是在树形结构的叶子节点上收集结果,而子集问题就是取树上所有节点的结果」。
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public void backtracking(int[] nums, boolean[] used) {
if (path.size() == nums.length) {
result.add(new ArrayList<>(path));
return; // 得到一个全排列就好了,不用再深入了,就可以
}
for (int i = 0; i < nums.length; i++) {
if (i > 0 && nums[i - 1] == nums[i] && false == used[i - 1]) continue; // 如果用过了
if (used[i] == false) {
path.add(nums[i]);
used[i] = true;
backtracking(nums, used);
path.remove(path.size() - 1); // 算法题不用考虑多线程什么的,这种两句一段的代码顺序是可以随便写的
used[i] = false;
}
}
}
public List<List<Integer>> permuteUnique(int[] nums) {
boolean[] used = new boolean[nums.length];
for (int i = 0; i < used.length; i++) // used中每一个元素就是代表nums中的元素是否使用过
used[i] = false;
Arrays.sort(nums); // 包含重复元素,要求全排列,这里就必须排序
backtracking(nums, used);
return result;
}
}
相对于上面的,就是加上排序 和
if (used[i] == false)判断
为什么需要 if (used[i] == false) 判断,如果没有的话,