最近几天力扣每日一题一直与回溯相关,因此想简单总结一下什么是回溯算法,如何解相关的回溯算法题,并给出一道例子。
一.什么是回溯算法
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
二.回溯算法的思想
其基本思想就是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。
三.回溯算法的参数与过程
假设有一个迷宫,里面充满了很多的岔路口,我们需要探索从起点到达终点走出迷宫的路径,此时就需要进行一步一步的探索。
1.我们是在这个迷宫进行的探索,当我们走过一个岔路口时,下一次探索还是在这个迷宫,因此这就是我们的第一个参数:我们需要处理的数据(迷宫)。
2.当你选择了一条路时,这条路可能是我们需要的结果,因此我们需要保存我们走过的路,这就是我们的第二个参数:结果集
3.当你路过了一个岔路口到达下一个路口的时候,发现此路不通,是个死胡同,怎么办?是不是得原路返回。但我不知道原路啊?因为我只保存了上一个路口应该走哪条路,因此我们需要知道上一个路口的位置,此时就需要我们的第三个参数,保存上一条路位置,便于我们探索失败的时候返回。
4.接3,当你发现此路不通的时候,那这个路径还在结果集中吗?那这就肯定不是我们的结果了对吧,因此我们的结果集状态是不是得回退,把我们探索到的不同的那一条路从结果集中删掉,会退到上一个路口的状态。然后继续沿着路口继续探索。
5.成功条件,当我们走出迷宫就是成功了,此时退出迷宫,得到结果。
四.力扣例(39 组合总数)
题目:给定一个无重复元素的数组 candidates
和一个目标数 target
,找出 candidates
中所有可以使数字和为 target
的组合。
candidates
中的数字可以无限制重复被选取。
分析:
找出所有的组合,可以一步一步的探索,即遍历数组,每次选取一个元素,满足条件加入结果集,否则回退。
因为数字可以被无限制重复被选取,因此我们在选取元素时,有两种选择,一是继续选择当前元素,一个是越过该元素,选取下一个
1.传入需要操作的数据 int[]candidates和我们的目标target。
2.传入我们的路径List<Integer>path,与所有的结果集List<List<integer>>res.
3.传入我们的目标sum,每进行一步探索,总和sum=sum-candidate[?]。
4.传入我们上一个路口的位置,int idx.
5.结束条件:a.我们的目标数被减完了,此时我们找到了一个结果,将结果加入到我们的结果集res中
b.因为我们沿着数组进行搜索,因此当我们搜索到最边缘时,此时不能够再继续下去,需要返回
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res=new ArrayList<List<Integer>>();
List<Integer> path=new ArrayList<>();
combine(candidates,target,path,res,0);
return res;
}
private void combine(int can[],int sum,List<Integer>path,List<List<Integer>>res,int idx){
if(sum==0){
res.add(new ArrayList<>(path));
return;
}
if(idx==can.length)return;
//选取当前元素
if((sum-can[idx])>=0){
path.add(can[idx]);
combine(can,sum-can[idx],path,res,idx);
//移除最后一个元素
path.remove(path.size()-1);
}
//不选当前元素
combine(can,sum,path,res,idx+1);
}
}
此处不涉及剪枝操作,针对每一个回溯算法,剪枝策略大不相同,总的来说,就是将不可能的结果直接删掉,比如一个递增的序列,我现在需要组合得到7,那么经过一个操作之后选取了1,那么我还需要搜索6之后的数字吗?显然不可能,就直接将6之后的数字全部剪掉即可。
本人水平有限,错误之处还请希望指正!感谢!