491.递增子序列
- 链接:代码随想录
本题最关键点在去重逻辑上,注意不是排好序的数组,需要借助哈希数组来去重
-
解题思路:
①终止条件:遇到path收集的数组大于2的就收集到res中,不用做return,当遍历到一定深度的时候,for循环自动会结束
②参数:就传入一个保证每次遍历不重复的startIndex即可
③处理逻辑:1.要满足path的最后一个元素小于当前要收集的元素,否则continue
2.设置一个本层去重的哈希数组,如果当前树层有重复的元素,那么遍历到第二个重复的元素自动跳过不收集 -
图像理解
public class Solution {
private List<List<Integer>> res = new ArrayList<List<Integer>>();
private List<Integer> path = new ArrayList<>();
//易错点,千万不能用used,因为已经排好序了
public List<List<Integer>> findSubsequences(int[] nums) {
backtracking(nums,0);
return res;
}
private void backtracking(int[] nums, int startIndex) {
//终止条件
//收集的元素序列大于2才收集
if(path.size() >= 2){
res.add(new ArrayList<>(path));
}
// Set<Integer> uset = new HashSet<>();
int[] used = new int[201];//题中说范围为-100到100
//处理逻辑
for (int i = startIndex; i < nums.length; i++) {
//只有path里面最后一个数<当前遍历的值才添加,否则跳过
if(path.size() >= 1 && path.get(path.size() - 1) > nums[i]){//前面那个条件一定要加上,否则数组越界
continue;
}
//树枝去重
// if(i > 0 && uset.contains(nums[i])){
// continue;
// }
if(i > 0 && used[nums[i] + 100] != 0){
continue;
}
path.add(nums[i]);
// uset.add(nums[i]);
used[nums[i] + 100] = 1;
backtracking(nums, i + 1);
//这里不用再回溯uset,因为uset只在一层内使用
path.remove(path.size() - 1);
}
}
}
46.全排列
- 题目链接:代码随想录
排列问题:
1.首先排列是有序的,也就是说 [1,2] 和 [2,1] 是两个集合,这和之前分析的子集以及组合所不同的地方。
2.每层都是从0开始搜索而不是startIndex(从头开始)
3.需要used数组记录path里都放了哪些元素了(需要检查树枝间的重复问题)
-
解题思路:
①终止条件:当收集的元素个数等于nums数组的长度的时候收集
②参数:需要传入一个used数组,来判断这一深度的位置的元素是否使用过
③处理逻辑:如果used[i] == true,直接跳过,否则加入path数组,进行遍历 -
图像理解
public class Solution1 {
private List<List<Integer>> res = new ArrayList<List<Integer>>();
private List<Integer> path = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
//这里used[i]要设置为全局的目的是为了防止出现不同层之间,出现重复的情况
//如[2,2]选了两次2,每一向下深度的树选取的元素都不相同
boolean[] used = new boolean[nums.length];
backtracking(nums,used);
return res;
}
private void backtracking(int[] nums, boolean[] used) {
//终止条件
if(nums.length == path.size()){
res.add(new ArrayList<>(path));
}
//这里i要从0开始
for (int i = 0; i < nums.length; i++) {
if(used[i] == true){
continue;
}
used[i] = true;
path.add(nums[i]);
backtracking(nums,used);
//进行回溯
used[i] = false;
path.remove(path.size() - 1);
}
}
}
47.全排列 II
- 组合问题和排列问题是在树形结构的叶子节点上收集结果,而子集问题就是取树上所有节点的结果。
- 本题涉及到树层间的同一位置的重复元素的去重和树枝间的元素之间的去重
-
题目链接:代码随想录
-
解题思路:
①终止条件:就看收集结果的大小
②参数:可以传入两个数组,分别用来记录树层之间的重复和树枝之间的重复(代码有点冗余)
③处理逻辑:符合树枝去重和树层去重的逻辑 -
图像理解:
//解法一:借助了两个used数组来处理两种去重问题,空间较为冗余
private List<List<Integer>> res = new ArrayList<>();
private List<Integer> path = new ArrayList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
//定义一个used数组来判断树层去重
int[] used = new int[nums.length];
//树枝去重
boolean[] used1 = new boolean[nums.length];
//一定要先排序
Arrays.sort(nums);
backtracking(nums,used,used1);
return res;
}
private void backtracking(int[] nums, int[] used,boolean[] used1) {
//终止条件
if(nums.length == path.size()){
res.add(new ArrayList<>(path));
return;
}
//处理逻辑
for (int i = 0; i < nums.length; i++) {
//1.去重条件,树层去重
if(i > 0 && nums[i] == nums[i - 1] && used[i - 1] == 0){
continue;
}
//2.树枝去重
if(used1[i] == true){
continue;
}
used[i] = 1;
used1[i] = true;
path.add(nums[i]);
backtracking(nums, used,used1);
//回溯
used[i] = 0;
used1[i] = false;
path.remove(path.size() - 1);
}
}
//解法二:一个used数组来解决,主要靠树层去重used数组来解决
//存放结果
List<List<Integer>> result = new ArrayList<>();
//暂存结果
List<Integer> path = new ArrayList<>();
/**
* 解题思路:
* ①一个used数组在树层之间判断是否重复容易达到,只要保证used数组全为false即可,这个功能借助了遍历完树枝后恢复used数组
* ②树枝间的去重,借助if判断条件完成
* @param nums
* @return
*/
public List<List<Integer>> permuteUnique(int[] nums) {
boolean[] used = new boolean[nums.length];
Arrays.fill(used, false);
Arrays.sort(nums);
backTrack(nums, used);
return result;
}
private void backTrack(int[] nums, boolean[] used) {
if (path.size() == nums.length) {
result.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
// used[i - 1] == true,说明同⼀树⽀nums[i - 1]使⽤过
// used[i - 1] == false,说明同⼀树层nums[i - 1]使⽤过
// 如果同⼀树层nums[i - 1]使⽤过则直接跳过
//这个if解决了树层之间的判断
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue;
}
//如果同⼀树⽀nums[i]没使⽤过开始处理
//这个if判断解决了树枝之间去重的问题
if (used[i] == false) {
used[i] = true;//标记同⼀树⽀nums[i]使⽤过,防止同一树枝重复使用
path.add(nums[i]);
backTrack(nums, used);
path.remove(path.size() - 1);//回溯,说明同⼀树层nums[i]使⽤过,防止下一树层重复
used[i] = false;//回溯
}
}
}