代码随想录算法训练营Day23
组合总和
给你一个 无重复元素 的整数数组
candidates和一个目标整数target,找出candidates中可以使数字和为目标数target的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
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);//剪枝
backingtracking(candidates,target,0,0);
return result;
}
public void backingtracking(int[] candidates, int target,int startIndex,int sum){
if(sum>target){
return;
}
if(sum == target){
//result.add(path);这里是错误的!
result.add(new ArrayList<>(path));
return;
}
for(int i = startIndex;i<candidates.length;i++){
if(sum+candidates[i]>target) break;//剪枝
sum+=candidates[i];
path.add(candidates[i]);
backingtracking(candidates,target,i,sum);//这里不能传入i+1,因为可以重复使用!
sum-=candidates[i];
path.remove(path.size()-1);
}
}
}
具体区别
res.add(path);
这行代码是将path这个列表的引用直接添加到res中。也就是说,res中存储的是指向同一个path对象的引用。
因此,后续如果对path进行修改(比如添加或删除元素),res中对应的列表也会同步变化,因为它们指向的是同一个对象地址res.add(new ArrayList<>(path));
这行代码是创建了一个新的ArrayList,并用path的当前元素初始化它,然后将这个新列表添加到res中。
这样,res中存储的是一个新的、独立的列表对象,与原来的path不共享地址。
后续对path的修改不会影响到已经添加到res中的列表内容为什么通常用
res.add(new ArrayList<>(path));在回溯算法或者类似场景中,
path通常是一个动态变化的列表,随着递归的深入和回退不断添加和删除元素。如果直接用res.add(path);,那么res中存储的所有列表都会指向同一个path,最终所有结果都会变成相同的(即最后一次修改后的状态)。使用new ArrayList<>(path)可以保存当时的快照,保证结果的正确性
为什么剪枝操作先对数组进行排序
例如[2,3,5],target = 4;此时,2+2=4,后面的肯定大于4,可以剪枝
组合总和2
修改上面代码,超出时间限制,因为contains很慢!所以要在for循环中做剪枝操作,去重
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);//剪枝
boolean[] used = new boolean[candidates.length];
backingtracking(candidates,target,0,0,used);
return result;
}
public void backingtracking(int[] candidates, int target,int startIndex,int sum,boolean[] used){
if(sum>target){
return;
}
if(sum == target){
//result.add(path);这里是错误的!
/*if(!result.contains(path)){ result.add(new ArrayList<>(path)); }*/ result.add(new ArrayList<>(path));
return;
}
for(int i = startIndex;i<candidates.length;i++){
if(i>0&&candidates[i]==candidates[i-1]&&used[i-1]==false) continue;//相邻元素相同,只判断前一个元素,后面的会相同,实现了去重,这里的used数组只进行树层去重,没进行树枝去重,符合题意
if(sum+candidates[i]>target) break;//剪枝
sum+=candidates[i];
path.add(candidates[i]);
used[i] = true;
backingtracking(candidates,target,i+1,sum,used);
sum-=candidates[i];
path.remove(path.size()-1);
used[i] = false;
}
}
}
这里注意是树枝去重还是数层去重!!!
分割回文串
不太懂
class Solution {
List<List<String>> result = new ArrayList<>();
List<String> path = new ArrayList<>();
public List<List<String>> partition(String s) {
backtracking(s, 0, new StringBuilder());
return result;
}
private void backtracking(String s, int start, StringBuilder sb) {
//因为是起始位置一个一个加的,所以结束时start一定等于s.length,因为进入backtracking时一定末尾也是回文,所以path是满足条件的
if (start == s.length()) {
result.add(new ArrayList<>(path));
return;
}
//像前两题一样从前往后搜索,如果发现回文,进入backtracking,起始位置后移一位,循环结束照例移除path的末位
for (int i = start; i < s.length(); i++) {
sb.append(s.charAt(i));
//sb.toString().equals(sb.reverse().toString())不用这种方法,因为reverse会改变sb的内容
if (check(sb)) {
path.add(sb.toString());
backtracking(s, i + 1, new StringBuilder());
path.remove(path.size() - 1);
}
}
}
private boolean check(StringBuilder sb){
for (int i = 0; i < sb.length()/ 2; i++){
if (sb.charAt(i) != sb.charAt(sb.length() - 1 - i)){return false;}
}
return true;
}
}

浙公网安备 33010602011771号