算法训练营 day30 回溯算法 组合总和 组合总和II 分割回文串
组合总和
39. 组合总和 - 力扣(LeetCode)
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。
- 递归函数参数
这里依然是定义两个全局变量,二维数组result存放结果集,数组path存放符合条件的结果。(这两个变量可以作为函数参数传入)
首先是题目中给出的参数,集合candidates, 和目标值target。
- 递归终止条件
在如下树形结构中:
从叶子节点可以清晰看到,终止只有两种情况,sum大于target和sum等于target。
sum等于target的时候,需要收集结果
- 单层搜索的逻辑
单层for循环依然是从startIndex开始,搜索candidates集合。
代码如下:
class Solution {
List<List<Integer>> result = new ArrayList<List<Integer>>();
List<Integer> path = new ArrayList<Integer>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
backtracking(candidates, target, 0, 0);
return result;
}
private void backtracking(int[] candidates, int target, int sum, int index) {
if (sum > target) {
return;
}
if (sum == target) {
result.add(new ArrayList<>(path));
}
for (int i = index; i < candidates.length; i++) {
path.add(candidates[i]);
sum += candidates[i];
backtracking(candidates, target, sum, i);// 不用i+1了,表示可以重复读取当前的数
sum -= candidates[i];
path.remove(path.size() - 1);
}
}
}
剪枝操作
class Solution {
List<List<Integer>> result = new ArrayList<List<Integer>>();
List<Integer> path = new ArrayList<Integer>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);// 先进行排序
backtracking(candidates, target, 0, 0);
return result;
}
private void backtracking(int[] candidates, int target, int sum, int index) {
if (sum == target) {
result.add(new ArrayList<>(path));
}
// 如果 sum + candidates[i] > target 就终止遍历
for (int i = index; i < candidates.length&&sum+candidates[i]<=target; i++) {
path.add(candidates[i]);
sum += candidates[i];
backtracking(candidates, target, sum, i);
sum -= candidates[i];
path.remove(path.size() - 1);
}
}
}
组合总和II
40. 组合总和 II - 力扣(LeetCode)
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
本题的难点在于区别2中:集合(数组candidates)有重复元素,但还不能有重复的组合。
回看一下题目,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。
所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重。
为了理解去重我们来举一个例子,candidates = [1, 1, 2], target = 3,(方便起见candidates已经排序了)
强调一下,树层去重的话,需要对数组排序!
选择过程树形结构如图所示:
- 递归函数参数
与39.组合总和 (opens new window)套路相同,此题还需要加一个bool型数组used,用来记录同一树枝上的元素是否使用过。
这个集合去重的重任就是used来完成的。
- 递归终止条件
与39.组合总和 (opens new window)相同,终止条件为 sum > target
和 sum == target
。
- 单层搜索的逻辑
这里与39.组合总和 (opens new window)最大的不同就是要去重了。
前面我们提到:要去重的是“同一树层上的使用过”,如何判断同一树层上元素(相同的元素)是否使用过了呢。
如果candidates[i] == candidates[i - 1]
并且 used[i - 1] == false
,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]。
此时for循环里就应该做continue的操作。
这块比较抽象,如图:
代码如下:
List<List<Integer>> result = new ArrayList<List<Integer>>();
List<Integer> path = new ArrayList<Integer>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
boolean[] used = new boolean[candidates.length];
Arrays.fill(used,false);
Arrays.sort(candidates);
backtracking(candidates, target, 0, 0,used);
return result;
}
private void backtracking(int[] candidates, int target, int sum, int startIndex,boolean[] used) {
if (sum > target) {
return;
}
if (sum == target) {
result.add(new ArrayList<>(path));
}
for (int i = startIndex; i < candidates.length&&sum+candidates[i]<=target; i++) {
if (i>0&&candidates[i]==candidates[i-1]&&!used[i-1]){
continue;
}
used[i] = true;
path.add(candidates[i]);
sum += candidates[i];
backtracking(candidates, target, sum, i+1,used);
sum -= candidates[i];
path.remove(path.size() - 1);
used[i] = false;
}
}
分割回文串
131. 分割回文串 - 力扣(LeetCode)
给你一个字符串 s
,请你将 s
分割成一些子串,使每个子串都是 回文串 。返回 s
所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
两个主要难点
- 切割问题,有不同的切割方式
- 判断回文
切割问题,也可以抽象为一棵树形结构,如图:
- 递归函数参数
全局变量数组path存放切割后回文的子串,数组result存放结果集。 (这两个参数可以放到函数参数里)
本题递归函数参数还需要startIndex,因为切割过的地方,不能重复切割,和组合问题也是保持一致的。
- 递归函数终止条件
从树形结构的图中可以看出:切割线切到了字符串最后面,说明找到了一种切割方法,此时就是本层递归的终止条件。
- 单层搜索的逻辑
来看看在递归循环中如何截取子串呢?
在for (int i = startIndex; i < s.size(); i++)
循环中,我们 定义了起始位置startIndex,那么 [startIndex, i] 就是要截取的子串。
首先判断这个子串是不是回文,如果是回文,就加入在 path中,path用来记录切割过的回文子串。
代码如下:
class Solution {
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) {
if (startIndex>=s.length()){
result.add(new ArrayList(path));
}
for (int i = startIndex;i<s.length();i++){
if (isPalindrome(s,startIndex,i)){
String str = s.substring(startIndex, i + 1);
path.add(str);
}else {
continue;
}
backtracking(s,i+1);
path.remove(path.size()-1);
}
}
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;
}
}