回溯算法,正如其名所述,前进到某个地方能够折返到交叉路再选择另外一条路继续前进。相似咱们数据结构中的树。大部分回溯问题都离不开几个元素:递归、深度优先以及全排列。如下是笔者做为小萌新,以为比较不错的能够用来理解回溯的思想的例题,特此摘记。git
题目以下:算法
给定一个仅包含数字 2-9 的字符串,返回全部它能表示的字母组合。数组
给出数字到字母的映射以下(与电话按键相同)。注意 1 不对应任何字母。数据结构
示例:函数
input: "2,3"code
output:[“ad”, “ae”, “af”, “bd”, “be”, “bf”, “cd”, “ce”, “cf”]blog
思路:递归
拿到题目的时候,有些比较明显的特征在提示笔者此题应该是要用回溯的:rem
与其余的回溯问题有些不一样之处的是,若是使用的是局部字符串变量保存解集元素的话,此题在递归结束的时候能够不须要回溯。(详细见Code)字符串
public class LetteCombination { /** * 用来存储各个按键对应的字母 */ private List<String> letters = new ArrayList<String>(); /** * 将按键对应的字母保存进letters */ public void prepareForList(){ letters.add("abc"); letters.add("def"); letters.add("ghi"); letters.add("jkl"); letters.add("mno"); letters.add("pqrs"); letters.add("tuv"); letters.add("wxyz"); } /** * 入口函数 * @param digits 输入字符串 * @return */ public List<String> letterCombinations(String digits) { if(0==digits.length()||digits==null){ return new ArrayList<String>(); } prepareForList();// 记得进行数据的初始化 List<String> res = new ArrayList<>(); letterCombinations(digits,0,"",res); return res; } /** * 选取这些参数的是为了人为控制每次递归的值传递,digits 和 res 是必须的 * @param digits 输入的按键 * @param index 指向当前遍历到的digits的元素 * @param tmp 存放当前字母组合的结果 * @param res 存放全部组合的结果 */ public void letterCombinations(String digits,int index,String tmp,List<String> res){ // tmp达到预期长度就将tmp保存进res中 if(tmp.length()==digits.length()){ res.add(tmp); return; } // 读取当前digits遍历到的元素 对应的可选字符集 String choose = letters.get(digits.charAt(index)-'2'); for(int i=0;i<choose.length();i++){// 遍历可选字符集,每次固定当前位置的字符,再进行下一位的字符选择 // 笔者认为最核心的一句,相比于其余的回溯须要进行还原操做,这里使用局部变量的方式就不须要还原了 String curr = tmp+choose.charAt(i);// 这里遇到坑了,还原原始字符串,不比数组,字符串没有remove方法。必须利用物理栈进行变量的个阶段保存,即还原 letterCombinations(digits,index+1,curr,res);// 递归 } } public static void main(String[] args){ String input = "23"; LetteCombination lc = new LetteCombination(); List<String> l = lc.letterCombinations(input); for(String s:l){ System.out.print(l+"\t"); } } }
题目2以下:
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中全部可使数字和为 target 的组合。
candidates 中的数字能够无限制重复被选取。
说明:
全部数字(包括 target)都是正整数。 解集不能包含重复的组合。
示例 1:
输入: candidates = [2,3,6,7],target = 7 所求解集为: [ [7], [2,2,3] ]
思路:
与上面的题目特征类似,都是获取可枚举的排列中知足特定条件的值(此处是须要获取加载之List中的元素的和等于target的这些List)。所以几乎能够说是直接照搬上面的回溯部分,稍做修改便可使用了。
惟一须要注意的地方就是,为了去重(如:不去重的话获得的结果集多是:[2,2,3],[2,3,2]这样的),咱们须要引入一个index做为每次遍历的起点,这样保证每个解集元素List都是一个递增的List,也就不会出现重复的现象了。
private List<List<Integer>> result = new ArrayList<List<Integer>>(); public List<List<Integer>> combinationSum(int[] candidates, int target) { myMethod(candidates,target,new ArrayList<>(),0,0); return result; } /** * * @param candidates 可选元素集 * @param target 目标大小 * @param curr 解集 * @param sum 记录当前curr中全部元素之和 * @param index 记录上一次递归candidates使用的元素下标,例如:curr:2,3 说明index应该到了1 */ public void myMethod(int[] candidates,int target,List<Integer> curr,int sum,int index){ // 达到目标大小则将curr加入结果List中 if(sum == target){ result.add(new ArrayList<>(curr)); return; } // 大于目标大小就不必接着计算了直接返回 else if(sum>target) return; // 起点的选择笔者认为是此题的核心,由于笔者刚开始i从0开始,会出现重复的例如:2,2,3 和 2,3,2, // 若 i 从 index 开始则会维持输入元素的一个递增的序列,进而避开了重复的部分(由于没法选到小于index的坐标的值) for(int i=index;i<candidates.length;i++){ curr.add(candidates[i]);// 加入解集中 myMethod(candidates,target,curr,sum+candidates[i],i);// 递归调用 curr.remove(curr.size()-1);// 解集回溯 } } public static void main(String[] args){ CombinationSum cs = new CombinationSum(); List<List<Integer>> lr = cs.combinationSum(new int[]{2,3,6,7},7); for(List<Integer> l:lr){ System.out.println(l); } }
OK,就先记录到这!