电话号码的字母组合组合总和II 回溯注意事项(Java)
电话号码的字母组合
思路:多个循环可以考虑回溯。
首先明确:
- 循环的宽度是多少,即从哪些区间取数(本题目中每个数字都是3个字母,都是从三个字母中取一个数,所以可以确定循环宽度就是每个数字对应的字符串的长度)
- 循环的高度是多少,即循环终止条件(分别从每个数字里取一个字母,所以循环高度就是给出的数字的数量)
- 循环的区间是什么,用什么可以统一表达并递归(字母的联系是数字,需要找出每层循环中的循环数字,那就使用Index取数字)
- 是否需要剪枝:如果求sum问题可以提前剪枝,本题目不用
class Solution {
List<String> res = new ArrayList<>();
List<String> sub = new ArrayList<>();
StringBuffer sb = new StringBuffer();
Map<Character, String> map = new HashMap<>(); //Character用的好
int n = 3;
public List<String> letterCombinations(String digits) {
int len = digits.length(); //几层--循环宽度
if(len == 0){
return res;
}
map.put('2', "abc"); //找出对应关系
map.put('3', "def");
map.put('4', "ghi");
map.put('5', "jkl");
map.put('6', "mno");
map.put('7', "pqrs");
map.put('8', "tuv");
map.put('9', "wxyz");
fucLetter(digits, len, 0);
return res;
}
public void fucLetter(String digits, int len, int Index){
if(sb.length() == len){
res.add(sb.toString());
return;
}
char c = digits.charAt(Index); //找到每个数字
String s = map.get(c); // 取出对应字符串
for(int i = 0; i < s.length(); i++){ //循环字符串
sb.append(s.charAt(i));
fucLetter(digits, len, Index + 1);
sb.deleteCharAt(sb.length() - 1);
}
}
}
组合总和
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> sub = new ArrayList<>();
int target;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
this.target = target;
Arrays.sort(candidates); // 剪枝的话先进行排序
fucSum(candidates, 0, 0);
return res;
}
public void fucSum(int[] candidates, int sum, int startIndex){
if(sum > target){
return;
}
if(sum == target){
res.add(new ArrayList<>(sub));
return;
}
for(int i = startIndex; i < candidates.length; i++){ //从startIndex开始
if(sum + candidates[i] > target){
break; //这个不要return,直接break
} //这个剪枝注意一下
sub.add(candidates[i]);
sum += candidates[i];
fucSum(candidates, sum, i); //允许可以重复,从当前元素即可!
sub.remove(sub.size() - 1);
sum -= candidates[i];
}
}
}
组合总和II
- 思路1:去除重复的集合,但是if(i != 0 && candidates[i] == candidates[i - 1])单纯这样写是不够的,因为这样也把纵向重复去除了(纵向重复并不会导致集合重复)!!!区分纵向和横向的关系:纵向是递归,横向是回溯,只有return后才是回溯,所以还是需要一个标注代表是同一层还是同一纵,用boolean use[] = new boolean[candidates.length]来记录,正常纵向递归时标注为true,回溯时变为false。
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> sub = new ArrayList<>();
boolean use[];
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
use = new boolean[candidates.length]; //默认为false
Arrays.sort(candidates); // 剪枝前先进行排序
fucCombine(candidates, target, 0, 0);
return res;
}
public void fucCombine(int[] candidates, int target, int startIndex, int sum){
if(sum > target){
return;
}
if(sum == target){
res.add(new ArrayList<>(sub));
return;
}
for(int i = startIndex; i < candidates.length; i++){
if(i != 0 && candidates[i] == candidates[i - 1] && use[i - 1] == false){ //单纯这样写是不够的,因为这样也把纵向重复去除了!区分纵向和横向的关系:纵向是递归,横向是回溯,只有return后才是回溯
continue;
}
if(candidates[i] + sum > target){
break;
}
use[i] = true;
sub.add(candidates[i]);
sum += candidates[i];
fucCombine(candidates, target, i + 1, sum);
sub.remove(sub.size() - 1);
sum -= candidates[i];
use[i] = false;
}
}
}
- 思路2:使用if(i > startIndex && candidates[i] == candidates[i - 1])去重!!天才!!ifi > startIndex说明是同一层的for循环!!直接去重
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> sub = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates); // 剪枝前先进行排序
fucCombine(candidates, target, 0, 0);
return res;
}
public void fucCombine(int[] candidates, int target, int startIndex, int sum){
if(sum > target){
return;
}
if(sum == target){
res.add(new ArrayList<>(sub));
return;
}
for(int i = startIndex; i < candidates.length; i++){
if(i > startIndex && candidates[i] == candidates[i - 1]){
continue;
}
if(candidates[i] + sum > target){
break;
}
sub.add(candidates[i]);
sum += candidates[i];
fucCombine(candidates, target, i + 1, sum);
sub.remove(sub.size() - 1);
sum -= candidates[i];
}
}
}
回溯注意事项
- 何时定义startIndex,即循环从startIndex开始,例如: for(int i = startIndex; i < candidates.length; i++) ?当每一层要循环的区间有关联时,不允许跟之前重复,此时循环从startIndex开始。
- 递归时startIndex的值如何赋予?如果要求一个元素只能出现一次
,那么使用i+1,即:fucSum(candidates, sum, i+1); 否则使用i,即:fucSum(candidates, sum, i) - 剪枝优化
写博客的目的是每日督促并记录刷题,也欢迎大家批评指正~(day25)