一、39. 组合总和
这道题没有想到剪枝优化。这里在剪枝之前会先将数组进行排序,如果当前元素加入sum之后超过target值,则后边的元素也一定会超过,直接break跳出循环。
以下是代码部分:
public class 组合总和39 {
//这道题虽然做出来了,但是感觉理解的不是很透彻,
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
//剪枝优化
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
if(candidates.length == 0)
return result;
//首先要对给定的数组按升序排序
Arrays.sort(candidates);
backtracking2(candidates, target, 0);
return result;
}
private void backtracking2(int[] candidates, int target, int start){
if(target == 0){
result.add(new ArrayList<>(path));
return;
}
for (int i = start; i < candidates.length; i++) {
/*
剪枝操作
*/
if(target - candidates[i] < 0)
break;
path.add(candidates[i]);
backtracking2(candidates, target-candidates[i], i);
path.remove(path.size()-1);
}
}
}
二、40.组合总和II
这道题不会,没有想出来如何在搜索的过程中去重。
题解中,要深刻理解是在同一树层上去重,还是在同一树枝上去重。本题按题意,是在同一树层上去重。
额外定义一个used数组,用于标识当前数组中哪些元素已经使用过。如果是在同一树枝上,前边的元素使用过,used标识为true,说明正在同一树枝上进行操作,不需要去重。如果是在同一树层中,由于前边的元素会进行回溯,重新标识为false,所以如果当前元素与前一个元素相同,则需要去重。
在同一树层中,如果前边相同的元素已经考虑过情况,那么就不需要再考虑当前的元素了,直接return。
以下是代码部分:
public class 组合总和Ⅱ40 {
//想不出来,直接看的题解
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
boolean[] used;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
used = new boolean[candidates.length];
//需要先进行一个排序,将相同的元素放在一块
Arrays.sort(candidates);
backtracking(candidates, target, 0);
return result;
}
private void backtracking(int[] candidates, int target, int startIndex){
if(target == 0){
result.add(new ArrayList<>(path));
return;
}
for(int i = startIndex;i < candidates.length; i++){
//剪枝操作
if(target - candidates[i] < 0)
break;
//对同一层的元素进行去重
if( i >0 && candidates[i-1] == candidates[i] && used[i-1] == false) //踩坑,需要从i=1开始判断
continue;
path.add(candidates[i]);
used[i] = true;
backtracking(candidates, target - candidates[i], i+1);
path.remove(path.size()-1);
used[i] = false;
}
}
}
三、131.分割回文串
分割问题其实是和组合问题非常像的。
可以把切割线看作是组合问题里的数,也就是说选择某几个符合题意的切割线,每次都是选择i作为切割线。[startIndex,i]是一个选择了右边界之后的区间。
以下是代码部分:
public class 分割回文串131 {
//分割问题其实是和组合问题非常像的
//可以把切割线看作是组合问题里的数,也就是说选择某几个符合题意的切割线
List<List<String>> result = new ArrayList<>();
List<String> path = new ArrayList<>();
public List<List<String>> partition(String s) {
backtracking(s, 0);
return result;
}
private void backtracking(String s, int startIndex){
//收集结果
//startIndex是切割的起始位置(即当前还可以选择的切割点,如果等于了字符串的长度,证明没有办法再切割了)
if(startIndex == s.length()){
result.add(new ArrayList<>(path));
return;
}
for(int i = startIndex; i < s.length(); i++){
//这里其实就是跟组合问题类似,每次都是选择i作为分割点。[startIndex,i]是一个选择了右边界之后的区间。
String ss = s.substring(startIndex, i+1); //这里要+1,左闭右开
if(isHui(ss))
path.add(ss);
else
continue;
backtracking(s, i+1);
path.remove(path.size()-1);
}
}
private boolean isHui(String s){
for (int i = 0,j = s.length()-1; i < j ; i++,j--) {
if(s.charAt(i)!=s.charAt(j))
return false;
}
return true;
}
}