39. 组合总和
- 链接:代码随想录
这个题关键掌握去重的第一种情况,前后重复
- 解题思路:
①结束条件:sum >= target
②每一层遍历: candidates数组
③深度遍历:还是candidates数组
public class Solution {
private List<List<Integer>> res = new ArrayList<List<Integer>>();
private int sum = 0;
private List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
//不是必须排序的
Arrays.sort(candidates);
backtracking(candidates, target,0);
return res;
}
private void backtracking(int[] candidates,int target,int startIndex){
if(sum == target){
res.add(new ArrayList<>(path));//这里一定不能只添加path的引用,因为到最后path一定为0
return;
}
for (int i = startIndex; i < candidates.length; i++) {
if(sum + candidates[i] > target){
break;
}
sum += candidates[i];
path.add(candidates[i]);
backtracking(candidates, target,i);//这里不是startIndex + 1和startIndex要注意,因为每一层可以重复选取,但是要设置startIndex防止出现 3 2 2 和 2 2 3的情况
//这里一定为i,因为每层的i这个元素一定重复,但是不会出现集合重复的情况,如果传入startIndex,那么之后每一层的i都从0开始取
//回溯
sum -= candidates[i];
path.remove(path.size() - 1);
}
}
}
40.组合总和II⭐️
题目链接:代码随想录
这个给定数组与原先不同,这里面的元素有重复的
掌握去重思路
-
解题思路:
-
1.我把所有组合求出来,再用set或者map去重,这么做很容易超时!
- 2.取之前一定要排序,让有可能重复的元素挨在一块
两个“去重的过程”:
①是树枝之间的startIndex保证了一个数组中的每一个元素能够被只取一次
②是树层之间,通过used数组来标记树层之间,元素的使用情况。如[1,1,2]如果目标为3,则[1,2]和[1,2]是重复的,
用used来标识有没有被使用过来区分树层还是树枝之间的取法
- 2.取之前一定要排序,让有可能重复的元素挨在一块
-
-
图像理解
public class Solution1 {
private List<List<Integer>> res = new ArrayList<List<Integer>>();
private List<Integer> path = new ArrayList<>();
private int sum = 0;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
int[] used = new int[candidates.length];
//一定要先排序,让有可能重复的元素挨在一块
Arrays.sort(candidates);
backtracking(candidates, target, 0,used);
return res;
}
private void backtracking(int[] candidates, int target,int startIndex,int[] used){
if(sum > target) return;
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] == 0){
continue;//跳过这个分支
}
sum += candidates[i];
path.add(candidates[i]);
used[i] = 1;
// 每个节点仅能选择一次,所以从下一位开始
backtracking(candidates, target, i + 1, used);
//回溯,恢复原样
sum -= candidates[i];
path.remove(path.size() - 1);
used[i] = 0;
}
}
}
131.分割回文串⭐️
分割回文串,思路和组合问题一样,就是实现形式不同
题目链接:代码随想录
-
解题思路:
本题和组合问题如出一辙,难点在于如何切割字符串
1.本题递归函数参数还需要startIndex,因为切割过的地方,不能重复切割,和组合问题也是保持一致的。
2.切割完之后,判断切割的结果是回文数之后,立即回收结果
3.切割线 === startIndex
4.切割的子串长度为**[startIndex,i]**,因为i是这一层里面不断变化的值,startIndex是这一层的起始位置
5.注意切割过的位置,不能重复切割,所以,backtracking(s, i + 1); 传入下一层的起始位置为i + 1。
6.判断回收元素位置的最好办法是举个例子理解一下 -
图像理解:
public class Solution2 {
private List<List<String>> res = new ArrayList<List<String>>();
private Deque<String> path = new LinkedList<>();
public List<List<String>> partition(String s) {
backtracking(s, 0);
return res;
}
private void backtracking(String s,int startIndex){
//这里startIndex就是切割位置
if(startIndex == s.length()){
res.add(new ArrayList<String>(path));
return;
}
for (int i = startIndex; i < s.length(); i++) {
//判断是否为回文数
if(!isPalindrome(s, startIndex, i)){
continue;//跳过这一分支,进行剪枝
}else{
String str = s.substring(startIndex, i + 1);//[startIndex,i + 1)
path.add(str);
}
backtracking(s, i + 1);//这里始终是 i + 1
//回溯
path.removeLast();
}
}
//判断是否是回文数 [start,end]
private boolean isPalindrome(String s,int start,int end){
for(int i = start,j = end;i < j;i++,j--){
if(s.charAt(i) != s.charAt(j)){
return false;
}
if(i == j){
break;
}
}
return true;
}
}