回溯算法综述
解决一个回溯问题,实际上就是一个决策树的遍历过程,因此只需要思考如下的三个问题:
-
路径:已做出的选择
-
选择列表:可以做的选择
-
结束条件:已到达决策树的底层,无法再进行选择操作了
代码框架:
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack( 路径, 选择列表)
撤销选择
相当于,在一棵决策树中for循环可以理解是横向遍历,backtracking(递归)就是纵向遍历,这样就把这棵树全遍历完了,一般来说,搜索叶子节点就是找的其中一个结果了。
Tips
Tips
对于for循环选择中,我们是否是要从0开始遍历选择列表,还是要从start开始遍历选择列表
- 无序:从start开始,如对于[1,2,3]的子集,我们不能回头去取[2,1],这个不是子集。
- 有序:从0开始,即[a,b]和[b,a]是两个不同列表,是两种不同路径。
对于去重
-
树枝去重:同一树枝上,表明在同一个track中不能出现重复的,直接先对选择列表去重。
-
树层去重:同一树层上,在递归函数中,(注意去重需要先对集合排序),我们限制每次填入的字符一定是这个字符所在重复字符集合中「从左往右第一个未被填入的字符」;即满足相邻元素相同,但前一个元素还未使用(隐含着在之前循环已经使用过该元素了)vis[j] || (j > 0 && !vis[j - 1] && s[j - 1] == s[j])。
if (vis[j] || (j > 0 && !vis[j - 1] && arr[j - 1] == arr[j])) { continue; }
例子
leetcode 90. 子集 II
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/subsets-ii
输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
class Solution {
public List<List<Integer>> list;
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
list = new LinkedList<>();
LinkedList<Integer> l = new LinkedList<>();
int[] flag = new int[nums.length];
sub(nums, l ,flag, 0);
return list;
}
public void sub(int[] nums, LinkedList<Integer> l , int[] flag, int star){
list.add(new LinkedList<Integer>(l));
for(int i = star ; i < nums.length ; i++){
if( i>0 && flag[i-1] == 0 && nums[i] == nums[i-1]){
continue;
}
l.addLast(nums[i]);
flag[i] = 1;
sub(nums, l , flag, i+1);
l.removeLast();
flag[i] = 0;
}
}
}