39.组合总和
思路:可以重复选取,这个要怎么回溯?而且是无限制的重复。每次回溯,都还是遍历当前的数组,记住数组中的最小值,一旦发现path的值加上最小值大于目标值,直接return,
尝试
class Solution {
List<List<Integer>> result= new ArrayList<>();
List<Integer> path = new ArrayList<>();
int min = Integer.MAX_VALUE;
int sum = 0;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
for(int i : candidates){
if(i < min) min = i;
}
backTracking(candidates,target,sum);
return result;
}
public void backTracking(int[] candidates, int target,int sum){
if(sum == target ){
// result.add(path);
result.add(new ArrayList<>(path));
return;
}
if(sum > target){
return;
}
for(int i = 0;i < candidates.length;i++){
sum += candidates[i];
path.add(candidates[i]);
backTracking(candidates,target,sum);
path.remove(path.size()-1);
sum -=candidates[i];
}
}
}
🍉返回结果时,要用【 result.add(new ArrayList<>(path));】
🍉我尝试中出现的问题是,会有【2,2,3】【2,3,2】这样的重复组合
答案
// 剪枝优化
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates); // 先进行排序
backtracking(candidates, target, 0, 0);
return res;
}
public void backtracking(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(candidates, target, sum + candidates[i], i);
path.remove(path.size() - 1); // 回溯,移除路径 path 最后一个元素
}
}
}
复习
离谱,就是错了一个参数!传入的应该是【i】,而不是startIndex,传【startIndex】就定死了
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);
backTracking(candidates,target,0,0);
return result;
}
public void backTracking(int[] candidates,int target,int sum,int startIndex){
if(sum == target){
result.add(new ArrayList<>(path));
return;
}
for(int i = startIndex; i < candidates.length;i++){
if(sum + candidates[i] > target) break;
path.add(candidates[i]);
backTracking(candidates,target,sum + candidates[i],startIndex);
path.remove(path.size() - 1);
}
}
}
小结
🍉对于组合问题,如果是一个集合来求组合的话,就需要startIndex,多个集合取组合,各个集合之间相互不影响,那么就不用startIndex
🍉终止遍历,用break
if (sum + candidates[i] > target) break;
🍉递归函数的参数【idx】,作用就是防止组合重复,如下图,取【2】之后,可以在【2,3,5】中接着取,但是取【5】之后只能在【5,3】中接着取
40.组合总和||
思路:跟组合总和应该是类似的,只能使用一次,注意candidate里面有重复元素,搞一个指针,不断移动地去加,这样就能保证一个元素只被用了一次,怎么实现在同一个path下移动指针,感觉现在需要两个指针
尝试
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
backTracking(candidates,target,0,0);
return result;
}
public void backTracking(int[] candidates, int target,int sum,int idx){
if(sum == target){
result.add(new ArrayList<>(path));
}
for(int i = idx; i < candidates.length; i++){
if(sum +candidates[i] > target) break;
path.add(candidates[i]);
backTracking(candidates,target,sum + candidates[i],i+1);
path.remove(path.size() - 1);
}
}
}
出现了重复组合,关键还是不知道怎么移动指针
修改(我靠,加一个if函数就AC了)
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
backTracking(candidates,target,0,0);
return result;
}
public void backTracking(int[] candidates, int target,int sum,int idx){
if(sum == target){
result.add(new ArrayList<>(path));
}
for(int i = idx; i < candidates.length; i++){
if(sum +candidates[i] > target) break;
if (i > idx && candidates[i] == candidates[i - 1]) {
continue;
}
path.add(candidates[i]);
backTracking(candidates,target,sum + candidates[i],i+1);
path.remove(path.size() - 1);
}
}
}
复习
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
backTracking(candidates,target,0,0);
return result;
}
public void backTracking(int[] candidates, int target,int sum,int startIndex){
if(sum == target){
result.add(new ArrayList<>(path));
return;
}
for(int i = startIndex; i < candidates.length ;i++){
if(i>startIndex && candidates[i] == candidates[i-1]) break;
if(sum + candidates[i] > target) return;
path.add(candidates[i]);
backTracking(candidates,target,sum+candidates[i],i+1);
path.remove(path.size() - 1);
}
}
}
答案
class Solution {
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
int sum = 0;
public List<List<Integer>> combinationSum2( int[] candidates, int target ) {
//为了将重复的数字都放到一起,所以先进行排序
Arrays.sort( candidates );
backTracking( candidates, target, 0 );
return res;
}
private void backTracking( int[] candidates, int target, 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;
}
sum += candidates[i];
path.add( candidates[i] );
// i+1 代表当前组内元素只选取一次
backTracking( candidates, target, i + 1 );
int temp = path.getLast();
sum -= temp;
path.removeLast();
}
}
}
小结
🍉现在明白了,【for循环】是横向遍历,【递归】是纵向遍历
🍉【for循环】时,每个数字是不能重复的,比如【1,1,2,3】,target为【3】,树层不去重,就会出现两个【1,2】
🍉如果总和大于目标值,应该是【break】,for循环跳过集合的重复数字,应该用【continue】
131.分割回文串
思路:印象中回文串要用双指针,为啥感觉遍历就行,回溯干啥啊,难在回溯时,还要判断截取的内容是否回文
复习
答案
class Solution {
List<List<String>> lists = new ArrayList<>();
Deque<String> deque = new LinkedList<>();
public List<List<String>> partition(String s) {
backTracking(s, 0);
return lists;
}
private void backTracking(String s, int startIndex) {
//如果起始位置大于s的大小,说明找到了一组分割方案
if (startIndex >= s.length()) {
lists.add(new ArrayList(deque));
return;
}
for (int i = startIndex; i < s.length(); i++) {
//如果是回文子串,则记录
if (isPalindrome(s, startIndex, i)) {
String str = s.substring(startIndex, i + 1);
deque.addLast(str);
} else {
continue;
}
//起始位置后移,保证不重复
backTracking(s, i + 1);
deque.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;
}
}
小结
🍉因为对于一个集合,存在多种分割方案,所以要用回溯
🍉【subString】要传入的是(起始位置指针),以及(结尾指针+1)