经典的回溯题 组合问题
startIndex 的使用
因为本地可以使用重复的元素 单层for循环依然是从startIndex开始,搜索candidates集合。
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
backtracking(candidates, target, 0, 0);
return res;
}
private void backtracking(int[] candidates, int target, int sum, int startIndex){
if(sum == target){
res.add(new ArrayList<>(path));
return;
}
if(sum > target){
return;
}
for(int i = startIndex; i < candidates.length; i++){
path.add(candidates[i]);
sum += candidates[i];
backtracking(candidates, target, sum, i);
path.remove(path.size() - 1);
sum -= candidates[i];
}
}
}
可以做剪枝优化 当sum > target 的时候 不需要进入递归
// 剪枝优化
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
Arrays.sort(candidates); // 先进行排序
backtracking(res, new ArrayList<>(), candidates, target, 0, 0);
return res;
}
public void backtracking(List<List<Integer>> res, List<Integer> path, int[] candidates, int target, int sum, int idx) {
// 找到了数字和为 target 的组合
if (sum == target) {
res.add(new ArrayList<>(path));
return;
}
for (int i = idx; i < candidates.length; i++) {
// 如果 sum + candidates[i] > target 就终止遍历
if (sum + candidates[i] > target) break;
path.add(candidates[i]);
backtracking(res, path, candidates, target, sum + candidates[i], i);
path.remove(path.size() - 1); // 回溯,移除路径 path 最后一个元素
}
}
}
在求和问题中,排序之后加剪枝是常见的套路!
没做出来
本题的难点在于区别2中:集合(数组candidates)有重复元素,但还不能有重复的组合。
去重的逻辑
组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过
所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重。
强调一下,树层去重的话,需要对数组排序!
此题还需要加一个bool型数组used,用来记录同一树枝上的元素是否使用过。
这个集合去重的重任就是used来完成的
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
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 startIndex){
if(sum == target){
res.add(new ArrayList<>(path));
return;
}
for(int i = startIndex; i < candidates.length; i++){
// 出现重复节点,同层的第一个节点已经被访问过,所以直接跳过
if(i > 0 && candidates[i] == candidates[i-1] && !used[i-1]){
continue;
}
if(sum + candidates[i] > target){
break;
}
used[i] = true;
sum += candidates[i];
path.add(candidates[i]);
backtracking(candidates, target, sum, i + 1);
used[i] = false;
sum -= candidates[i];
path.remove(path.size() - 1);
}
}
}
- used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
- used[i - 1] == false,说明同一树层candidates[i - 1]使用过
为什么 used[i - 1] == false 就是同一树层呢,因为同一树层,used[i - 1] == false 才能表示,当前取的 candidates[i] 是从 candidates[i - 1] 回溯而来的
注意:
去重逻辑可以不适用used数组
这里直接用startIndex来去重也是可以的, 就不用used数组了。
// 要对同一树层使用过的元素进行跳过
if (i > startIndex && candidates[i] == candidates[i - 1]) {
continue;
}
这道题目 主要是要分清楚去重逻辑是应用在 同一树枝 还是 同一树层
切割问题其实是一种组合问题!
分析出是组合问题就好办了
Java 切割子串的方法
s.substring(int start, int end);
class Solution {
List<List<String>> res = new ArrayList<>();
List<String> path = new ArrayList<>();
public List<List<String>> partition(String s) {
backtracking(s, 0);
return res;
}
private void backtracking(String s, int startIndex){
if(startIndex >= s.length()){
res.add(new ArrayList<>(path));
return;
}
for(int i = startIndex; i < s.length(); i++){
if(isValid(s.substring(startIndex, i+1))){
path.add(s.substring(startIndex, i+1));
backtracking(s, i+1);
path.remove(path.size() - 1);
}
}
}
private boolean isValid(String s){
int left = 0;
int right = s.length() - 1;
while(left <= right){
if(s.charAt(left) != s.charAt(right)){
return false;
}
left++;
right--;
}
return true;
}
}
判断是否为回文子串可以优化