回溯算法的模板
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {//横向遍历
处理节点;//这里一般可能进行剪枝操作
backtracking(路径,选择列表); // 递归,纵向遍历
回溯,撤销处理结果
}
}
是一个基本的回溯算法模板,其中的关键点包括:
- 定义终止条件:当满足终止条件时,表示找到了一个解或者不再需要继续搜索,可以进行相应的操作,如输出结果或返回。
- 遍历选择列表:遍历所有可能的选择,通常使用循环结构,对于每个选择,进行相应的操作。
- 做出选择:根据当前选择,更新状态或路径,表示对问题的一次选择。
- 递归进入下一层决策树:根据当前选择,进入下一层决策树,即进行下一步的选择。
- 撤销选择:在回溯到上一层之前,需要撤销当前选择,恢复状态或路径,以便进行下一个选择。
- 在实际应用中,根据具体问题的不同,模板中的代码需要进行相应的修改和扩展,以适应问题的特点和约束条件。同时,通过剪枝、优化等技巧,可以对模板进行改进,提高算法的效率。
需要注意的是,回溯算法是一种暴力搜索的方法,解空间的规模很大时,可能会导致算法效率低下。因此,在使用回溯算法时,需要根据问题的规模和特点进行合理的优化和剪枝,以提高算法的性能。
示例:组合和排序
组合
给定两个整数 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 {
public List<List<Integer>> combine(int n, int k) {
List<Integer> outList =new ArrayList<>();
List<List<Integer>> res =new ArrayList<>();
List<Integer> cur =new ArrayList<>();
for(int i =0;i<n;i++){
cur.add(i+1);
}
backtring(res,cur,outList,n,k,0);
return res;
}
public void backtring(List<List<Integer>> res,List<Integer> cur ,List<Integer> outList,int n,int k,int index){
if(outList.size()==k){
res.add(new ArrayList<Integer> (outList));//深拷贝
return;
}
for(int i=index;i<n;i++){
outList.add(cur.get(i));
backtring(res,cur,outList,n,k,i+1);
outList.remove(outList.indexOf(cur.get(i)));
}
}
}
优化:
public List<List<Integer>> combine(int n, int k) {
List<Integer> outList =new ArrayList<>();
List<List<Integer>> res =new ArrayList<>();
backtring(res,outList,n,k,1);
return res;
}
public void backtring(List<List<Integer>> res ,List<Integer> outList,int n,int k,int index){
// 剪枝:如果剩余数字数量不够补满 k 个,直接返回
if (outList.size() + (n - index + 1) < k) return;
if(outList.size()==k){
res.add(new ArrayList<Integer> (outList));
return;
}
for(int i=index;i<=n;i++){
outList.add(i);
backtring(res,outList,n,k,i+1);
outList.remove(outList.size()-1);
}
}
全排列
class Solution {
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
List<Integer> cur = new ArrayList<>();
for(int i=0;i<nums.length;i++){
cur.add(nums[i]);
}
backtraing(res,cur,nums.length,0);
return res;
}
public void backtraing(List<List<Integer>> res,List<Integer> cur,int n,int index){
if(index==n){
res.add(new ArrayList<>(cur));
return;
}
for(int i= index;i<n;i++){
Collections.swap(cur,i,index);
backtraing(res,cur,n,index+1);
Collections.swap(cur,i,index);
}
}
}
为什么组合用 i+1,而排列用 index+1?
✔ 组合(Combination)是“选 k 个”,顺序不重要
比如从
[1,2,3,4]选出两个:[1,4] 和 [4,1] 是同一个组合✔ 排列(Permutation)是“排 n 个”,顺序重要
比如
[1,4]和[4,1]是两个不同的排列。
1️⃣ 组合(choose k)回溯逻辑
每个数字只能选或不选
每条路径是 升序选择(保证不重复)
所以用 start/index 来限制下一次选择从哪里开始
🔥 下一层从 i+1 开始(不能再选择前面的数字,不然会重复!)
2️⃣ 排列(permute)回溯逻辑
每个数字的位置都要尝试
需要交换数字来“固定”当前 index 位置
下一层必须处理 index+1(不是 i+1)
🔥 排列永远用 index+1,而不是 i+1!
因为你是在固定 index 的数,然后进入处理下一个 index。
1010

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



