参考:
https://labuladong.gitee.io/algo/1/4/
回溯:
总结几个核心:
针对子集组合问题(组合就是子集的条件中变化,相当于子集的子集):可重复选 就用i 不可重复选就用i+1
针对排列问题:不可以构造下标index!!!
针对元素重复问题:元素重复的话一定要使用排序,visited数组,然后剪枝
if(visited[i]) continue;
,特殊情况下还要多一个保证相同元素之间次序保持不变的操作
i > 0 && nums[i] == nums[i-1] && !visited[i-1]
子集问题
看元素是否重复,能否重复选!
元素不重复(可重复选), 那么使用index+1下标即可。
元素重复(可重复选),那么就要先排序然后引进visited数组,剪枝!还需要进行同一层树的标记(此前数组需要排好序),使得同一层相同元素剪枝掉
if(i > 0 && nums[i] == nums[i-1] && !visited[i-1]){
//保证相同元素在排列中的相对位置保持不变。!visited[i-1] 也就是前一个必须没有用过!这样就可以跳过
// 1 2 2` 和 1 2` 2 其实是一样的 但是如果 2` 没有用过,那么 1 2` 2就应该被剪枝掉
continue;
}
全排列问题(本身就默认不允许重复选择,所以只剩下元素重复问题)
一般都是要使用**visited数组进行标记是否使用过,并且不需要index下标。**然后再回溯遍历魔板中进行判断剪枝!
元素不重复情况下
if(visited[i]) continue;
遇到可重复元素的话,还需要进行同一层树的标记(此前数组需要排好序),使得同一层相同元素剪枝掉
if(i > 0 && nums[i] == nums[i-1] && !visited[i-1]){
//保证相同元素在排列中的相对位置保持不变。!visited[i-1] 也就是前一个必须没有用过!这样就可以跳过
// 1 2 2` 和 1 2` 2 其实是一样的 但是如果 2` 没有用过,那么 1 2` 2就应该被剪枝掉
continue;
}
- 子集(中等)
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
if(nums.length == 0) return res;
backtracking(nums,0);
return res;
}
public void backtracking(int[]nums,int index){
res.add(new ArrayList<>(path));
for(int i = index; i < nums.length; i++){
path.add(nums[i]);
backtracking(nums,i+1);
path.remove(path.size() - 1);
}
}
}
- 子集 II(中等)
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:
输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
boolean[]visited;
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
visited = new boolean[nums.length];
backstracking(nums,0);
return res;
}
public void backstracking(int[]nums,int index){
res.add(new ArrayList<>(path));
for(int i = index; i < nums.length; i++){
if(i > 0 && nums[i] == nums[i-1] && !visited[i-1]){
// 去重
continue;
}
visited[i] = true;
path.add(nums[i]);
backstracking(nums,i+1);
path.remove(path.size() - 1);
visited[i] = false;
}
}
}
- 组合(中等)
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。
示例 1:
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
示例 2:
输入:n = 1, k = 1
输出:[[1]]
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
backstracking(n,k,1);
return res;
}
public void backstracking(int n,int k,int val){
if(path.size() == k){
res.add(new ArrayList<>(path));
return;
}
for(int i = val; i <= n; i++){
path.add(i);
backstracking(n,k,i+1);
path.remove(path.size()-1);
}
}
}
- 组合总和(中等)
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。
示例 1:
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。
class Solution {
List<List<Integer>> res = new ArrayList();
List<Integer> path =new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
backstracking(candidates,target,0);
return res;
}
public void backstracking(int[]candidates,int target,int index){
if(target == 0 ){
res.add(new ArrayList<>(path));
return;
}
if(target < 0){
return;
}
for(int i = index; i < candidates.length; i++){
path.add(candidates[i]);
backstracking(candidates,target-candidates[i],i);
path.remove(path.size() - 1);
}
}
}
- 组合总和 II(中等)
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
boolean[]visited;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
visited = new boolean[candidates.length];
backstracking(candidates,target,0);
return res;
}
public void backstracking(int[]candidates,int target,int index){
if(target == 0){
res.add(new ArrayList<>(path));
return;
}
if(target < 0){
return;
}
for(int i = index; i < candidates.length; i++){
if( i >0 && candidates[i] == candidates[i-1] && !visited[i-1]){
continue;
}
path.add(candidates[i]);
visited[i] = true;
backstracking(candidates,target-candidates[i],i+1);
path.remove(path.size()-1);
visited[i] = false;
}
}
}
- 组合总和 III(中等)
找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:
只使用数字1到9
每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
示例 1:
输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
没有其他符合的组合了。
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
backstracking(k,n,1);
return res;
}
public void backstracking(int k,int n,int val){
if(k == path.size() && n == 0){
res.add(new ArrayList<>(path));
return;
}
for(int i = val; i <= 9; i++){
path.add(i);
backstracking(k,n-i,i+1);
path.remove(path.size() -1);
}
}
}
- 全排列(中等)
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer>path = new ArrayList<>();
boolean[]visited;
public List<List<Integer>> permute(int[] nums) {
visited = new boolean[nums.length];
backstracking(nums);
return res;
}
public void backstracking(int[]nums){
if(path.size() == nums.length){
res.add(new ArrayList<>(path));
return;
}
for(int i = 0; i < nums.length; i++){
if(visited[i]){
continue;
}
visited[i] = true;
path.add(nums[i]);
backstracking(nums);
path.remove(path.size() - 1);
visited[i] = false;
}
}
}
- 全排列 II(中等)
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
示例 1:
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer>path = new ArrayList<>();
boolean[]visited;
public List<List<Integer>> permuteUnique(int[] nums) {
Arrays.sort(nums);
visited = new boolean[nums.length];
backstracking(nums);
return res;
}
public void backstracking(int[]nums){
if(path.size() == nums.length){
res.add(new ArrayList<>(path));
return;
}
for(int i = 0; i < nums.length; i++){
if(visited[i]) continue;
if(i > 0 && nums[i] == nums[i-1] && !visited[i-1]){
continue;
}
visited[i] = true;
path.add(nums[i]);
backstracking(nums);
path.remove(path.size() - 1);
visited[i] = false;
}
}
}
形式一、元素无重不可复选,即 nums 中的元素都是唯一的,每个元素最多只能被使用一次,backtrack 核心代码如下:
/ 组合/子集问题回溯算法框架 /
void backtrack(int[] nums, int start) {
// 回溯算法标准框架
for (int i = start; i < nums.length; i++) {
// 做选择
track.addLast(nums[i]);
// 注意参数
backtrack(nums, i + 1);
// 撤销选择
track.removeLast();
}
}
/ 排列问题回溯算法框架 /
void backtrack(int[] nums) {
for (int i = 0; i < nums.length; i++) {
// 剪枝逻辑
if (used[i]) {
continue;
}
// 做选择
used[i] = true;
track.addLast(nums[i]);
backtrack(nums);
// 取消选择
track.removeLast();
used[i] = false;
}
}
形式二、元素可重不可复选,即 nums 中的元素可以存在重复,每个元素最多只能被使用一次,其关键在于排序和剪枝,backtrack 核心代码如下:
Arrays.sort(nums);
/* 组合/子集问题回溯算法框架 */
void backtrack(int[] nums, int start) {
// 回溯算法标准框架
for (int i = start; i < nums.length; i++) {
// 剪枝逻辑,跳过值相同的相邻树枝
if (i > start && nums[i] == nums[i - 1]) {
continue;
}
// 做选择
track.addLast(nums[i]);
// 注意参数
backtrack(nums, i + 1);
// 撤销选择
track.removeLast();
}
}
Arrays.sort(nums);
/* 排列问题回溯算法框架 */
void backtrack(int[] nums) {
for (int i = 0; i < nums.length; i++) {
// 剪枝逻辑
if (used[i]) {
continue;
}
// 剪枝逻辑,固定相同的元素在排列中的相对位置
if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
continue;
}
// 做选择
used[i] = true;
track.addLast(nums[i]);
backtrack(nums);
// 取消选择
track.removeLast();
used[i] = false;
}
}
形式三、元素无重可复选,即 nums 中的元素都是唯一的,每个元素可以被使用若干次,只要删掉去重逻辑即可,backtrack 核心代码如下:
/* 组合/子集问题回溯算法框架 */
void backtrack(int[] nums, int start) {
// 回溯算法标准框架
for (int i = start; i < nums.length; i++) {
// 做选择
track.addLast(nums[i]);
// 注意参数
backtrack(nums, i);
// 撤销选择
track.removeLast();
}
}
/* 排列问题回溯算法框架 */
void backtrack(int[] nums) {
for (int i = 0; i < nums.length; i++) {
// 做选择
track.addLast(nums[i]);
backtrack(nums);
// 取消选择
track.removeLast();
}
}
DP
子序列问题套路:
https://mp.weixin.qq.com/s/zNai1pzXHeB2tQE6AdOXTA
本文深入探讨了回溯算法在解决子集、排列、组合等问题中的应用技巧,通过实例讲解了如何根据不同问题特点选用合适的回溯框架。
553

被折叠的 条评论
为什么被折叠?



