39. 组合总和
该题目关键问题在于 candidates 里面的元素可以复用多次
体现代码关键为:
void backtrack(int[] candidates, int start, int target, int sum) {
// 回溯算法框架
for (int i = start; i < candidates.length; i++) {
// 选择 candidates[i]
backtrack(candidates, i, target, sum);
// 撤销选择 candidates[i]
}
}
而例如 77.组合 当中不能使用重复元素的标准组合代码:
void backtrack(int[] candidates, int start, int target, int sum) {
// 回溯算法框架
for (int i = start; i < candidates.length; i++) {
// 选择 candidates[i]
backtrack(candidates, i + 1, target, sum);
// 撤销选择 candidates[i]
}
}
最关键的地方便是递归调用时,不用 i + 1 便表示可以重复读取当前元素
借用卡哥本题的决策树:
所以完整代码如下:
class Solution {
List<List<Integer>> res = new LinkedList<>();
List<Integer> track = new LinkedList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
backtrack(candidates, target, 0, track);
return res;
}
int sum = 0;
public void backtrack(int[] candidates, int target, int startIndex, List<Integer> track){
if(sum > target){
return;
}
if(sum == target){
res.add(new LinkedList<>(track));
return;
}
for(int i = startIndex; i < candidates.length; i++){
sum += candidates[i];
track.add(candidates[i]);
// 该处没有返回 i + 1, 表示重复选取当前元素
backtrack(candidates, target, i, track);
sum -= candidates[i];
track.remove(track.size() - 1);
}
}
}
40.组合总和II
该题最关键的地方便是去重:集合(数组candidates)有重复元素,但不能有重复的组合。
借用卡哥的决策树:(可知,需要在树层去重)
主要问题便是:如何确定是同一树层上的元素
如果candidates[i] == candidates[i - 1]
并且 used[i - 1] == false
,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]。
如图:
// used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
// used[i - 1] == false,说明同一树层candidates[i - 1]使用过
// 要对同一树层使用过的元素进行跳过
if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
continue;
}
第二种便是利用 startIndex 来去重:(借用leetcode题解)
所以完整代码如下:
class Solution {
List<List<Integer>> res = new LinkedList<>();
List<Integer> track = new LinkedList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
backtrack(candidates, target, 0, 0, track);
return res;
}
public void backtrack(int[] candidates, int target, int startIndex, int sum, List<Integer> track){
if(sum == target){
res.add(new LinkedList<>(track));
return;
}
for(int i = startIndex; i < candidates.length; i++){
if(sum > target){
return;
}
// 去重
if(i > startIndex && candidates[i] == candidates[i - 1]){
continue;
}
sum += candidates[i];
track.add(candidates[i]);
backtrack(candidates, target, i + 1, sum, track);
sum -= candidates[i];
track.remove(track.size() - 1);
}
}
}
131.分割回文串
本题的难点:
- 切割问题如何抽象为组合问题
- 如何模拟那些切割线
- 切割问题中递归如何终止
- 在递归循环中如何截取子串
解决办法:从 s
的头部开始暴力穷举,如果发现 s[0..i]
是一个回文子串,那么我们就可以把 s
切分为 s[0..i]
和 s[i+1..]
,然后我们去尝试把 s[i+1..]
去暴力切分成多个回文子串即可。
决策树如下:
只有树枝上的子串是回文串时才能继续往下走,最后如果能够走到空串节点,就说明整个 s
完成了切分,也就是得到了一个合法的答案。
所以完整代码如下:
class Solution {
List<List<String>> res = new LinkedList<>();
List<String> track = new LinkedList<>();
public List<List<String>> partition(String s) {
backtrack(s, 0, track);
return res;
}
public void backtrack(String s, int startIndex, List<String> track){
if(startIndex == s.length()){
res.add(new LinkedList<String>(track));
return;
}
for(int i = startIndex; i < s.length(); i++){
// 判断是否为回文串
if(!isPalindrome(s, startIndex, i)){
continue;
}
// String.substring截取范围 "左闭右开"
track.add(s.substring(startIndex, i + 1));
backtrack(s, i + 1, track);
track.remove(track.size() - 1);
}
}
public boolean isPalindrome(String s,int startIndex, int end){
while(startIndex < end){
if(s.charAt(startIndex) != s.charAt(end)){
return false;
}
startIndex++;
end--;
}
return true;
}
}