在程序设计中,有相当一类求一组解,或求全部解或求最优解的问题,例如读者熟悉的八皇后问题,不是根据某种特定的计算法则,而是利用试探和回溯的搜索技术求解。回溯法也是设计递归过程的一种重要方法,它的求解过程实质上是一个先序遍历一棵"状态树"的过程,只是这棵树不是遍历前预先建立的,而是隐含在遍历过程中。
—《数据结构》(严蔚敏)
首先,某种问题的解我们很难去找规律计算出来,没有公式可循,只能列出所有可能的解,然后一个个检查每个解是否符合我们要找的条件,也就是通常说的遍历。而解空间很多是树型的,就是树的遍历。
其次,树的先序遍历,也就是根是先被检查的,二叉树的先序遍历是根,左子树,右子树的顺序被输出。如果把树看做一种特殊的图的话,DFS就是先序遍历。所以,回溯和DFS是联系非常紧密的,可以认为回溯是DFS的一种应用场景。另外,DFS有个好处,它只存储深度,不存储广度。所以空间复杂度较小,而时间复杂度较大。
最后,某些解空间是非常大的,可以认为是一个非常庞大的树,此时完全遍历的时间复杂度是难以忍受的。此时可以在遍历的同时检查一些条件,当遍历某分支的时候,若发现条件不满足,则退回到根节点进入下一个分支的遍历。这就是“回溯”这个词的来源。而根据条件有选择的遍历,叫做剪枝或分枝定界。
LeetCode784字母大小写全排列
给定一个字符串S,通过将字符串S中的每个字母转变大小写,我们可以获得一个新的字符串。返回所有可能得到的字符串集合。
思路:用回溯法,从位置0开始,如果不是字符,就直接添加到字符串temp后,临时保存,继续向下深度搜索,如果是字符,就分别进行按大小写字母回溯,当temp达到S的长度时,就保存结果,返回到上层,继续回溯。
public List letterCasePermutation(String S) {
Listlist=new ArrayList();
String temp="";
dfs(S,0,temp,list);
return list;
}
public void dfs(String s,int i,String temp,List list){
if(i==s.length())
list.add(temp);
return ;
char c=s.charAt(i);
if(!Character.isLetter(c)){
dfs(s,i+1,temp+c,list);//是数字,就添加上继续回溯
}else{//不是数字,就要分大小写进行回溯
dfs(s,i+1,temp+Character.toUpperCase(c),list);
dfs(s,i+1,temp+Character.toLowerCase(c),list);
}
}
LeetCode78子集
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
思路:此题求解的树空间是二叉树,对于每一个数组中的元素,都可以分为两种情况,加入和不加入
public List> subsets(int[] nums) {
List> list = new ArrayList<>();
Listli=new ArrayList<>();
backtrack(list, li, nums, 0);
return list;
}
private void backtrack(List> list , List tempList, int [] nums, int start){
list.add(new ArrayList<>(tempList));
for(int i=start;i
leetcode 46全排列
给定一个没有重复数字的序列,返回其所有可能的全排列。
思路:解空间树是排列树,从根节点回溯到底,则把结果保存起来,向上回溯,为保证不重复,则相同元素要过滤掉。
public List> permute(int[] nums) {
List> list=new ArrayList<>();
Listl=new ArrayList<>();
backtrack(nums,list,l);
return list;
}
private void backtrack(int []nums,List> list,Listl){
if(l.size()==nums.length){
list.add(new ArrayList<>(l));
}else{
for(int i=0;i
leetcode39组合总数
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
思路:和前面求解子集的题一样的思路,不同的是每次递归可以从本身开始,因为元素可以重复。
public List> combinationSum(int[] candidates, int target) {
List> list=new ArrayList<>();
Listl=new ArrayList<>();
Arrays.sort(candidates);
back(list,l,candidates,target,0);
return list;
}
private void back( List> list, ListtempList,int []nums,int remain,int start){
if(remain < 0) return;
else if(remain == 0) list.add(new ArrayList<>(tempList));
else{
for(int i = start; i < nums.length; i++){
tempList.add(nums[i]);
back(list, tempList, nums, remain - nums[i], i); // not i + 1 because we can reuse same elements
tempList.remove(tempList.size() - 1);
}
}
}