本题是 集合里元素可以用无数次,那么和组合问题的差别 其实仅在于 startIndex上的控制
题目链接: 39. 组合总和
文章讲解: 39. 组合总和
视频讲解: 39. 组合总和
本题没有组合数量要求,仅仅是总和的限制,所以递归没有层数的限制,只要选取的元素总和超过target,就返回!
而在77.组合
和216.组合总和III
中都可以知道要递归K层,因为要取k个元素的组合。
对于sum已经大于target的情况,其实是依然进入了下一层递归,只是下一层递归结束判断的时候,会判断sum > target的话就返回。其实如果已经知道下一层的sum会大于target,就没有必要进入下一层递归了。那么可以在for循环的搜索范围上进行剪枝。
对总集合排序之后,如果下一层的sum(就是本层的 sum + candidates[i])已经大于target,就可以结束本轮for循环的遍历。
本题和我们之前讲过的77.组合
、216.组合总和III
有两点不同:
// 回溯 已剪枝
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
int sum = 0;
int startIndex = 0;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates); // 先进行排序
backtracking(candidates, target, sum, startIndex);
return result;
}
public void backtracking(int[] candidates, int target, int sum, int startIndex){
if(sum > target) return;
if(sum == target){
result.add(new ArrayList<>(path));
return;
}
for(int i = startIndex; i < candidates.length && sum + candidates[i] <= target; i++){ //剪枝 控制树的横向遍历
sum += candidates[i];
path.add(candidates[i]);
backtracking(candidates, target, sum, i); // 控制树的纵向遍历
sum -= candidates[i];
path.removeLast();
}
}
}
本题开始涉及到一个问题了:去重。
注意题目中给我们 集合是有重复元素的,那么求出来的 组合有可能重复,但题目要求不能有重复组合。
题目链接: 40.组合总和II
文章讲解:[ 40.组合总和II ](https://programmercarl.com/0040.%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8CII.html
视频讲解:https://www.bilibili.com/video/BV12V4y1V73A)
这道题目和39.组合总和
如下区别:
39.组合总和
是无重复元素的数组candidates我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重。我们要做的是数层方面的去重,used[]数组进行去重.树层去重的话,需要先对数组排序!
// 回溯 已剪枝
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
int sum = 0;
int startIndex = 0;
boolean used[];
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
used = new boolean[candidates.length];
Arrays.sort(candidates); // 先进行排序
backtracking(candidates, target, sum, startIndex, used);
return result;
}
public void backtracking(int[] candidates, int target, int sum, int startIndex, boolean[] used){
if(sum > target) return;
if(sum == target){
result.add(new ArrayList<>(path));
return;
}
for(int i = startIndex; i < candidates.length && sum + candidates[i] <= target; i++){ //剪枝 控制树的横向遍历
if(i >0 && candidates[i-1] == candidates[i] && used[i-1] == false) continue; // 去重
used[i] = true;
sum += candidates[i];
path.add(candidates[i]);
backtracking(candidates, target, sum, i+1, used); // 控制树的纵向遍历
used[i] = false;
sum -= candidates[i];
path.removeLast();
}
}
}
本题较难,大家先看视频来理解 分割问题,明天还会有一道分割问题,先打打基础。
题目链接: 131.分割回文串
文章讲解: 131.分割回文串
视频讲解: 131.分割回文串
如何更高效的计算一个子字符串是否是回文字串呢,我们这里是用双指针实现的,可以用动态规划来优化。后续学了动态规划再说吧
列出如下几个难点:
// 回溯
class Solution {
List<List<String>> result = new ArrayList<>();
LinkedList<String> path = new LinkedList<>();
public List<List<String>> partition(String s) {
backTracking(s, 0);
return result;
}
private void backTracking(String s, int startIndex) {
//如果起始位置大于s的大小,说明找到了一组分割方案
if (startIndex >= s.length()) {
result.add(new ArrayList(path));
return;
}
for (int i = startIndex; i < s.length(); i++) {
//如果是回文子串,则记录
if (isPalindrome(s, startIndex, i)) {
String str = s.substring(startIndex, i + 1);
path.add(str);
} else {
continue;
}
//起始位置后移,保证不重复
backTracking(s, i + 1);
path.removeLast();
}
}
//判断是否是回文串
private boolean isPalindrome(String s, int startIndex, int end) {
for (int i = startIndex, j = end; i < j; i++, j--) {
if (s.charAt(i) != s.charAt(j)) {
return false;
}
}
return true;
}
}