39.组合总和
给你一个 无重复元素 的整数数组 candidates
和一个目标整数 target
,找出 candidates
中可以使数字和为目标数 target
的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates
中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target
的不同组合数少于 150
个。
思路
先将该问题的解抽象为如下的树型结构,从中可以看出,当数组有序的情况下(小->大),如果当前的元素加上sum已经大于目标值target了,说明后续的分支已经没有符合的组合了,应该及时终止后续的分支展开,也就是所谓的剪枝处理。
另外,特别需要留意每个元素可以重复选用,那么在迭代时,下次遍历的开始元素下标仍然与上次相同。
代码实现(带剪枝处理)-java
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
// 结果集
List<List<Integer>> res = new ArrayList<>();
// 先对数组排序,方便后续做剪枝处理
Arrays.sort(candidates);
backtracking(res,new ArrayList<>(),candidates,target,0,0);
return res;
}
/**
* 回溯函数
*
*/
private void backtracking(List<List<Integer>> res,List<Integer> path,int[] candidates,
int target , int sum,int startIdx ){
// 终止条件
if(sum==target){
res.add(new ArrayList(path));
return;
}
// 递归加循环处理
for(int i=startIdx;i<candidates.length;i++){
// 剪枝处理:当sum加当前元素大于目标和,则直接终止遍历
if(sum+candidates[i]>target) break;
// 正常处理,注意startIdx仍是从i开始,这样意味着当前元素能被重复选中
path.add(candidates[i]);
backtracking(res,path,candidates,target,sum+candidates[i],i);
// 回溯:移除path中最后一个元素
path.remove(path.size()-1);
}
}
}
40.组合总和II
给定一个候选人编号的集合 candidates
和一个目标数 target
,找出 candidates
中所有可以使数字和为 target
的组合。
candidates
中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
思路
本题与上题差别有两个:
1.每个元素不能重复选用
2.数组中本身包含相同的元素但是要求组合不能重复
以上两个差别决定了本题与上题处理上有所不同:
1.同一个元素不能重复使用,意味着下次迭代的数组下标位置应该加1
2.组合不能重复,意味着在同一树层中(排序后)相邻的相同元素要去重
代码实现-java
class Solution {
// 结果集
List<List<Integer>> res = new ArrayList<>();
// 组合路径
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
// 数组排序
Arrays.sort(candidates);
backtracking(candidates,target,0,0);
return res;
}
/**
* 回溯函数
*/
private void backtracking(int[] candidates,int target,int sum,int startIdx){
// 终止条件
if(sum==target){
res.add(new ArrayList<>(path));
return;
}
// 遍历+递归
for(int i=startIdx;i<candidates.length;i++){
// 剪枝
if(sum+candidates[i]>target) break;
// 同一树层去重
if(i>startIdx && candidates[i]==candidates[i-1]) continue;
// 正常处理
path.add(candidates[i]);
backtracking(candidates,target,sum+candidates[i],i+1);
// 回溯
path.remove(path.size()-1);
}
}
}
131.分割回文串
给你一个字符串 s
,请你将 s
分割成一些子串,使每个子串都是 回文串 。返回 s
所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
思路
切割问题本质上与组合问题相同,所以仍然可以抽象为一颗树的查找。
代码实现
class Solution {
List<List<String>> res = new ArrayList<>();
List<String> path = new ArrayList<>();
public List<List<String>> partition(String s) {
backtracking(s,0);
return res;
}
private void backtracking(String s,int startIdx){
// 终止条件:当切割线到达字符串末尾时,说明找到了回文子串
if(startIdx >= s.length()){
res.add(new ArrayList(path));
return;
}
// 遍历
for(int i=startIdx;i<s.length();i++){
//如果是回文子串,则记录
if (isPalindrome(s, startIdx, i)) {
String str = s.substring(startIdx, i + 1);
path.add(str);
} else {
continue;
}
//起始位置后移,保证不重复
backtracking(s, i + 1);
// 回溯
path.remove(path.size()-1);
}
}
/**
* 判断是否回文子串(双指针法)
*/
private boolean isPalindrome(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;
}
}