文章目录
前言
一、491.递增子序列
虽然和之前的90、子集(2)很像,但本题求自增子序列,是不能对原数组进行排序的,排完序的数组都是自增子序列了。所以不能使用之前的去重逻辑!
对于已经习惯写回溯的同学,看到递归函数上面的uset.insert(nums[i]);
,下面却没有对应的pop之类的操作,应该很不习惯吧,哈哈
这也是需要注意的点,unordered_set<int> uset;
是记录本层元素是否重复使用,新的一层uset都会重新定义(清空),所以要知道uset只负责本层!
所以不是所有的套路都可以,加强思考才是王道:
能做到6分钟写完(目前):
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> findSubsequences(int[] nums) {
backTracking(nums,0);
return res;
}
public void backTracking(int[] nums, int firstnum){
if(path.size() > 1){
res.add(new ArrayList<>(path));
}
HashSet<Integer> hs = new HashSet<>();
for(int i = firstnum;i<nums.length;i++){
if(!path.isEmpty() && path.get(path.size()-1) > nums[i] || hs.contains(nums[i])){
continue;
}
hs.add(nums[i]);
path.add(nums[i]);
backTracking(nums,i+1);
path.remove(path.size()-1);
}
}
}
二、46.全排列
本题重点感受一下,排列问题 与 组合问题,组合总和,子集问题的区别。 为什么排列问题不用 startIndex。
首先排列是有序的,也就是说 [1,2] 和 [2,1] 是两个集合,这和之前分析的子集以及组合所不同的地方。而used数组,其实就是记录此时path里都有哪些元素使用了,一个排列里一个元素只能使用一次。
大家此时可以感受出排列问题的不同:
- 每层都是从0开始搜索而不是startIndex
- 需要used数组记录path里都放了哪些元素了
排列问题是回溯算法解决的经典题目,大家可以好好体会体会。
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
boolean[] uesd;
public List<List<Integer>> permute(int[] nums) {
uesd = new boolean[nums.length];
backTracking(nums);
return res;
}
public void backTracking(int[] nums){
if(path.size() == nums.length){
res.add(new ArrayList<>(path));
return;
}
for(int i = 0;i<nums.length;i++){
if(uesd[i]) continue;
uesd[i] = true;
path.add(nums[i]);
backTracking(nums);
uesd[i] =false;
path.remove(path.size() - 1);
}
}
}
三、47.全排列 II
还要强调的是去重一定要对元素进行排序,这样我们才方便通过相邻的节点来判断是否重复使用;一般来说:组合问题和排列问题是在树形结构的叶子节点上收集结果,而子集问题就是取树上所有节点的结果。
树层去重和树枝去重的区别:
树层上去重(used[i - 1] == false),的树形结构如下:
树枝上去重(used[i - 1] == true)的树型结构如下:
大家应该很清晰的看到,树层上对前一位去重非常彻底,效率很高,树枝上对前一位去重虽然最后可以得到答案,但是做了很多无用搜索。
这里可能大家又有疑惑,既然 used[i - 1] == false
也行而used[i - 1] == true
也行,那为什么还要写这个条件呢?
其实并不行,一定要加上 used[i - 1] == false
或者used[i - 1] == true
,因为 used[i - 1] 要一直是 true 或者一直是false 才可以,而不是 一会是true 一会又是false。 所以这个条件要写上。
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
boolean[] used = new boolean[nums.length];
Arrays.fill(used,false);
Arrays.sort(nums);
backTracking(nums,used);
return result;
}
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 && used[i-1] == false && nums[i-1] == nums[i]){
continue;
}
if(used[i] == false){
used[i] = true;
path.add(nums[i]);
backTracking(nums,used);
used[i] = false;
path.remove(path.size()-1);
}
}
}
}
总结
越来越熟练了。