这里先总结下对于回溯获得组合的startIndex设定:
如果是一个集合来求组合的话,就需要startIndex
如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex
组合总和
和III的区别就是,这个是可以重复从candidates中取数(当然他的数也是不会为0的),对数量k没有要求
版本一的代码大家可以看到,对于sum已经大于target的情况,其实是依然进入了下一层递归,只是下一层递归结束判断的时候,会判断sum > target的话就返回,那么可以在for循环的搜索范围上做做文章:
对总集合排序之后,如果下一层的sum(就是本层的 sum + candidates[i])已经大于target,就可以结束本轮for循环的遍历
(这里由于是从一个数组中取, 还是有很多细节的)
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
//需要排序才能保证后面的剪枝操作
Arrays.sort(candidates);
//这个start也是从0开始的,因为现在是从数组中取
backtracking(candidates, target, 0, 0);
return res;
}
public void backtracking(int[] candidates, int target, int sum, int start){
//下面还是老规矩
if(sum > target){
return;
}
if(sum == target){
res.add(new ArrayList<>(path));
return;
}
//注意剪枝操作:
for(int i = start; i < candidates.length && sum + candidates[i] <= target; i++){
//注意这里,由于他是从candidate数组中取数据,所以有所不同
path.add(candidates[i]);
sum += candidates[i];
//这里不用i+1,因为他是允许重复的
backtracking(candidates, target, sum, i);
path.remove(path.size() - 1);
sum -= candidates[i];
}
}
}
组合总和II
和上一道的区别就是这里不能使用同一个元素(但是如果两个元素相同的值还是可以的); 同时这道题的candidate元素是有重复的
先理解下这里的去重, 可以发现其实就是每一层上的元素要去重,而不是每个树枝上
小知识:Arrays.fill(arrayname, value); 用来填充数组
第一种解法需要设置一个boolean型used数组,判断是否用过元素
去重主要有两点:
candidates[i] == candidates[i - 1];
used[i - 1] == false;
这两个就说明是在同一树层使用过,需要跳过,说实话我没有完全理解, 涉及到很多use数据的内容
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
//定义used数据
boolean[] used;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
used = new boolean[candidates.length];
//加标志数组,用来辅助判断同层节点是否已经使用
Arrays.fill(used, false);
//一样要排序
Arrays.sort(candidates);
backtracking(candidates, target, 0, 0);
return res;
}
private void backtracking(int[] candidates, int target, int sum, int start){
//前面的终止条件都是一样的,可以忽略大于,因为单层遍历时会有剪枝
if(sum == target){
res.add(new ArrayList<>(path));
return;
}
//注意这里也有一个剪枝操作
for(int i = start; i < candidates.length && sum + candidates[i] <= target; i++){
//开始判断去重
if(i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false){
continue;
}
used[i] = true;
sum += candidates[i];
path.add(candidates[i]);
//这里任然要i+1,因为是不能重复的
backtracking(candidates, target, sum, i + 1);
//回溯初始化
used[i] = false;
sum -= candidates[i];
path.remove(path.size() - 1);
}
}
}
第二种解法很巧妙也更简单, 原理类似于 Three Sum, 直接判断i > start && 前后相等就continue
这个方法最重要的作用也是,可以让同一层级,不出现相同的元素, 去重原理如图:
直接拉代码:
class Solution {
List<Integer> path = new ArrayList<>();
List<List<Integer>> res = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
backtracking(candidates, target, 0, 0);
return res;
}
private void backtracking(int[] candidates, int target, int sum, int start){
if(sum == target){
res.add(new ArrayList<>(path));
return;
}
for(int i = start; i < candidates.length && sum + candidates[i] <= target; i++){
//开始剪枝
if(i > start && candidates[i] == candidates[i - 1]){
continue;
}
path.add(candidates[i]);
sum += candidates[i];
backtracking(candidates, target, sum, i + 1);
path.remove(path.size() - 1);
sum -= candidates[i];
}
}
}
分割回文串
和组合问题差不多
依然使用递归三部曲:
- 终止条件就是, 切割线切到了字符串最后面,说明找到了一种切割方法(startIndex ≥ s.length())
- 单层递归:需要判断是不是回文,如果是回文,就用substring获取在s中的字串,add
- 所以可以发现还需要一个判断回文的方法:双指针一前一后判读
注意点:
- 注意终止条件是分割到最后一个了
- 注意字符串不能直接分割,需要利用substring, 参数是(start, i+1)
- 判断回文双指针定义好
-
class Solution { List<String> path = new ArrayList<>(); List<List<String>> res = new ArrayList<>(); public List<List<String>> partition(String s) { backtracking(s, 0); return res; } //递归方法 private void backtracking(String s, int start){ //终止条件:说明找到了分割条件 if(start >= s.length()){ res.add(new ArrayList<>(path)); return; } //开始回溯, 然后这里开始判读 for(int i = start; i < s.length(); i++){ if(isPalindrome(s, start, i)){ //由于字符串不能直接分割,所以要调用substring方法 String str = s.substring(start, i + 1); path.add(str); } else { continue; } backtracking(s, i + 1); path.remove(path.size() - 1); } } //isPalindrome method, 注意这里的start和end会变化的 private boolean isPalindrome(String s, int start, int end){ while(start < end){ if(s.charAt(start) != s.charAt(end)){ return false; } start++; end--; } //注意:这里必须在条件内判断!=,才可以保证每个数都被遍历到了 return true; } }