随想录训练营Day27 | 回溯 39.组合总和, 40.组合总和II, 131.分割回文串
标签: LeetCode闯关记
39. 组合总和
- 本题和我们之前讲过的77.组合、216.组合总和III 有两点不同:
- 组合没有数量要求
- 元素可无限重复选取
个人思路: 把216.组合总和III的代码稍稍改改就好
- 个人AC代码
class Solution {
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
int sum = 0;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
find(candidates, target, 0);
return res;
}
public void find(int[] candidates, int target, int startIndex){
if(sum > target){
return;
}
if(sum == target){
res.add(new ArrayList<>(path));
return;
}
for (int i = startIndex; i < candidates.length ; i++) {
path.add(candidates[i]);
sum += candidates[i];
find(candidates,target,i);
sum -= candidates[i];
path.removeLast();
}
}
}
优化(没有想到):
- 对总集合排序之后,如果下一层的sum(就是本层的 sum + candidates[i])已经大于target,就可以结束本轮for循环的遍历。
- 先排序 : Arrays.sort(candidates); // 先进行排序
- 再判断(如下)
for (int i = startIndex; i < candidates.length; i++) {
// 如果 sum + candidates[i] > target 就终止遍历
if (sum + candidates[i] > target) break;
path.add(candidates[i]);
find(res, path, candidates, target, sum, i);
path.remove(path.size() - 1); // 回溯,移除路径 path 最后一个元素
sum -= candidates[i];
}
40.组合总和II
和39.组合总和 (opens new window)如下区别:
- 本题candidates仅能使用一次。
- 数组candidates的元素是有重复 难点: 去重
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);
boolean[] used = new boolean[candidates.length];
Arrays.fill(used, false);
backTracing(candidates,target,0,used);
return res;
}
public void backTracing(int[] candidates, int target,int startIndex, boolean[] used){
if(sum == target){
res.add(new ArrayList<Integer>(path));
return;
}
for (int i = startIndex; i < candidates.length; i++) {
//借助used数组去重,以此区分树层重复or树枝重复,牛逼! key
if(i >= 1 && candidates[i] == candidates[i-1] && used[i -1] == false){
continue;//犯错,写成了break;
}
//39. 组合总和学到的剪枝
if(sum + candidates[i] > target){
break;
}
sum += candidates[i];
path.add(candidates[i]);
used[i] = true;
backTracing(candidates,target, i + 1,used);
sum -= candidates[i];
used[i] = false;
path.removeLast();
}
}
}
实现中出现问题:
- 去重操作:只写了"if(i >= 1 && candidates[i] == candidates[i-1])",导致把所有正确结果去掉了,output为空, 没有进行数层去重;
- 把两个if判断写在一起,并写成了"break",错因,没有正确区分,①剪枝的时候,一旦发现"sum + candidates[i] > target",可以退出循环,即break;②发现数层去重的时候,应跳过当前元素,往后继续循环;
- chatGPT解释
- 使用 continue 语句可以跳过当前循环中的迭代,继而进行下一轮循环。而使用 break 语句会直接结束当前循环,不再执行后续的迭代。
- 如果将 continue 改为 break,则可能会导致错误的结果。因为在这个算法中,当发现一个数与前一个数相等且前一个数没有使用时,需要跳过当前的循环,但不需要结束循环。如果使用 break 来代替 continue,则会直接结束当前循环,无法完成后续的判断。
用时: 1.2h
131.分割回文串
难点: 找分割的分界线(完全没有想到 starIndex可以来当作终止条件的判断) 这里有点意思的是分割, 也就是把子串放在一个结果集里面.
class Solution {
List<List<String>> res = new ArrayList<>();
LinkedList<String> path = new LinkedList<>();
public List<List<String>> partition(String s) {
findPartition(s, 0);
return res;
}
private void findPartition(String s, int startIndex){
if(startIndex == s.length()){
res.add(new ArrayList<>(path));
return;
}
for (int i = startIndex; i < s.length(); i++) {
//判断回文串
if(isPalindrome(s,startIndex,i)){
String substr = s.substring(startIndex, i + 1);
path.add(substr);
findPartition(s,i + 1);//,在找到一个回文子串后,将其加入到 path 中,并直接调用 findPartition() 方法,然后再从 path 中移除最后一个元素。这种实现方式避免了重复操作路径 path 的开销,可以提高算法的效率。
path.removeLast();
}else{
continue;
}
}
}
private boolean isPalindrome(String s, int start, int end){
while(start <= end){
if(s.charAt(start) != s.charAt(end)){
return false;
}
start++;
end--;
}
return true;
}
}
出现问题: 在判断回文串时,双指针终止条件写错, while(start >= end);(无语) 难点: 理解如何分割,分割方式和终止条件