回溯法总结
组合
需要保存startIndex
重复元素的组合:利用if语句去重——if (i > startIndex && nums[i] == nums[i - 1]) continue;
分割
在结束递归的条件下进行最后一段子串的判断
从当前节点右侧进行分割:i+1
子集
输出所有组合遍历的元素
排列
不需要保存startIndex,但是需要used数组去重元素自身
重复元素的排列:used数组可以确定某个元素有没有在上层被使用过—— if (i > 0 && nums[i - 1] == nums[i] && used[i - 1] == false) continue;
491. 递增子序列
第一想法:一开始使用子集的解法,横向剪枝的时候使用了条件语句判断,但是用例没有全部通过
重点难点:本题的相同元素并不一定是连续的,造成可能横向剪枝遗漏。因此需要使用used集合进行当前层的元素去重。
总结&用时:相同连续元素可以用条件判断,相同不连续元素需要用数组保存
class Solution {
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> findSubsequences(int[] nums) {
backTrack(nums, 0);
return res;
}
public void backTrack(int[] nums, int startIndex) {
if (path.size() >= 2) {
res.add(new ArrayList<>(path));
}
if (startIndex >= nums.length) {
return;
}
// 横向剪枝
int[] used = new int[201];
for (int i = startIndex; i < nums.length; i++) {
// 相同元素不一定连续,所以不能这样横向剪枝
// if (i > startIndex && nums[i] == nums[i - 1]) continue;
// 横向剪枝新方法:used哈希集合去重
if (used[nums[i] + 100] == 1) continue;
used[nums[i] + 100]++;
// 保证path里面元素是单调的,不单调直接跳过
if (path.isEmpty() || (!path.isEmpty() && path.peekLast() <= nums[i])) {
path.add(nums[i]);
backTrack(nums, i + 1);
path.removeLast();
}
}
}
}
46.全排列
第一想法:排列相比于组合不需要定义startIndex,但是元素自己与自己不能重复。所以排列需要去重
重点难点:使用used数组储存nums[]的元素使用情况
class Solution {
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
// 使用used数组保存nums元素是否被使用的信息
boolean[] used;
public List<List<Integer>> permute(int[] nums) {
used = new boolean[nums.length];
backTrack(nums);
return res;
}
public void backTrack(int[] nums) {
if (path.size() == nums.length) {
res.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
// 去重
if (used[i] == true) continue;
used[i] = true;
path.add(nums[i]);
backTrack(nums);
path.removeLast();
used[i] = false;
}
}
}
总结&用时:需要思考排列与组合的区别
组合:需要定义startIndex
47.全排列 II
第一想法:used数组去重自身,但是重复元素怎么去重想了一阵子,类比了一下重复元素的组合,由于没有startIndex,想了好久如何实现横剪竖不剪
重点难点:回溯遍历层的时候条件判断可以通过used数组去充当类似startIndex的作用,代码如下:
class Solution {
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
// used数组:排除重复去自己
boolean used[];
public List<List<Integer>> permuteUnique(int[] nums) {
used = new boolean[nums.length];
Arrays.sort(nums);
backTrack(nums);
return res;
}
public void backTrack(int[] nums) {
if (path.size() == nums.length) {
res.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i] == true) continue;
// 前提:nums[i - 1] == nums[i]
//如果used[i-1]是true,说明是上一层已经使用了nums[i - 1],所以这一层的nums[i]可以用
//如果元素i-1是false,说明本层的nums[i - 1]已经被遍历过,再用nums[i]就会重复上轮循环的结果
if (i > 0 && nums[i - 1] == nums[i] && used[i - 1] == false) continue;
used[i] = true;
path.add(nums[i]);
backTrack(nums);
path.removeLast();
used[i] = false;
}
}
}
总结&用时:比组合的判断条件多了一个used数组的条件。用时40min。