一、算法题
39. 组合总和
class Solution {
List<List<Integer>> result=new ArrayList<>();
List<Integer> path=new LinkedList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
backtracking(candidates, target, 0,0);
return result;
}
public void backtracking(int[] candidates,int target,int startindex,int sum){
//回溯结束条件
if(sum>target){
return;
}
if(sum==target){
result.add(new ArrayList<>(path));
return;
}
for(int i=startindex;i<candidates.length;i++){
path.add(candidates[i]);
sum+=candidates[i];
backtracking(candidates, target, i, sum);
path.removeLast();
sum-=candidates[i];
}
}
}
- 如果是一个集合来求组合的话,就需要startIndex,例如:77.组合 (opens new window),216.组合总和III (opens new window)。
- startIndex为了去重
- 如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,例如:17.电话号码的字母组合
- 与之前不同的是,for循环中的backtracking递归传入的startindex不再是i-1,而是传入i,意味着下一层取的值可以重复
40.组合总和II
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new LinkedList<>();
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 result;
}
public void backtracking(int[] candidates, int target, int startindex, int sum) {
// 回溯结束条件
if (sum == target) {
result.add(new ArrayList<>(path));
return;
}
for (int i = startindex; i < candidates.length; i++) {
if(sum+candidates[i]>target){
break;
}
//去重
if(i>0&&candidates[i]==candidates[i-1]&&used[i-1]==false){
continue;
}
path.add(candidates[i]);
sum += candidates[i];
used[i]=true;
backtracking(candidates, target, i + 1, sum);
path.removeLast();
sum -= candidates[i];
used[i]=false;
}
}
}
- 本题的难点在于:集合(数组candidates)有重复元素,但还不能有重复的组合
- 元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。
- 比如candidates = [10,1,2,7,6,1,5],target=4,可以出现[1,1,2],但是不能[1,1,2],[1,2,1]同时出现,这相当于重复了
- 只能同一个树枝上数值重复,因为使用的数字虽然数值一样但不是同一个
- 同一层上不能重复
- 我们要去重的是同一树层上的“使用过”
- 同一树枝上的都是一个组合里的元素,不用去重
- 树层去重的话,需要对数组排序
- used数组
- used数组与candidate数组一样大,对应每个位置,表示该数字是否被使用
- 如果 i>0,且
candidates[i] == candidates[i - 1]
并且used[i - 1] == false
,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1],此时continue进入下一次循环- 这样做是因为前后数值相同,但是前一个数字没被使用,说明同一层的前一个树枝肯定使用了i-1这个数值,因为i从0开始,所以i=0时一定会先取第一个数,那么他对应的used[i]=true
- 因为前一个树枝取的数字与这个数值的数字相同,所以它肯定包含所有可能,同时会把该树枝的所有可能包含在内,这样如果把该树枝的回溯结果保存起来的话就会出现重复问题
- 比如
- target=3,第一层第二个树枝有[1],[1,2]这个结果;而第一层第一个树枝会有[1],[1,1],[1,2],[1,1,2];则前一个树枝已经包含了第二个树枝满足条件的组合
131.分割回文串
class Solution {
List<List<String>> result=new ArrayList<>();
List<String> path=new LinkedList<>();
public List<List<String>> partition(String s) {
backtracking(s, 0);
return result;
}
public void backtracking(String s,int startindex){
if(startindex>=s.length()){
result.add(new ArrayList<>(path));
return;
}
for(int i=startindex;i<s.length();i++){
if(huiwen(s, startindex, i)){
//取到end的前一个
//startindex用于去重,控制取得数组里的每个数作为开头
//i是在startindex取得开头后,遍历以那个数为开头的数组中的每一个数
//比如[1,2,3],startindex=2,i遍历[2,3]
String str=s.substring(startindex,i+1);
path.add(str);
}
else{
//不是回文串
continue;
}
backtracking(s,i+1);
path.removeLast();
}
}
public boolean huiwen(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;
}
}
return true;
}
}
- if(startindex>=s.length()){
result.add(new ArrayList<>(path));
return;
}
- 起始位置startindex=s.length的时候,说明已经遍历完了
- 比如s="abc",s.length=3,起始位置startindex从0开始,到2结束
- 当起始位置startindex=3时,说明已经遍历完三个字母了
- s.substring(startindex,i+1)
- 取到end的前一个
- startindex用于去重,控制取得数组里的每个数作为开头
- i是在startindex取得开头后,遍历以那个数为开头的数组中的每一个数
- 比如[1,2,3],startindex=2, i 遍历[2,3]
具体回溯过程不太懂