最后一个问题比较难,前两个利用模板可以解决。
1.力扣39(组合问题)
经典的回溯问题,讲本题想象成上题这种树型结构,本题的特点是可以重复利用一个节点,也就是startIndex的的值就为i,但是我们必须要多加一个结束条件防止无限递归下去。回溯三部曲:
- 递归函数参数:这里依然是定义两个全局变量,集合res存放结果集,集合path存放符合条件的结果;集合candidates, 和目标值target,此外我还定义了int型的sum变量来统计单一结果path里的总和,其实这个sum也可以不用,用target做相应的减法就可以了,最后如何target==0就说明找到符合的结果了,但为了代码逻辑清晰,我依然用了sum,还需要startIndex来控制for循环的起始位置。
public void combination(int[] candidates, int target,int sum, int startIndex)
- 递归终止条件:终止只有两种情况,sum大于target和sum等于target
if(sum>target){
return;
}
if(sum==target){
res.add(new ArrayList(path));
return;
}
- 单层递归逻辑:单层for循环依然是从startIndex开始,搜索candidates集合。
for(int i=startIndex;i<candidates.length;i++){
path.add(candidates[i]);
combination(candidates,target,sum+candidates[i],i);
path.remove(path.size()-1);
}
整体代码:
List<Integer> path = new ArrayList();
List<List<Integer>> res = new ArrayList();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
combination(candidates,target,0,0);
return res;
}
public void combination(int[] candidates, int target,int sum, 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]);
combination(candidates,target,sum+candidates[i],i);
path.remove(path.size()-1);
}
}
2.力扣40(组合总和II)
本题最大的特点就是元素含有重复的,所以本题的关键就是进行去重的操作,在第一遍写的时候利用模板写出来的结果出现了重复,虽然值相同,但是确是索引不同,我们需要特殊的进行去重操作,此时我们需要理解树枝重复和树层重复概念,也就是元素之前出现过的位置,同层出现就为树层重复,而树枝重复同理,在出现树层重复我们就跳过,就可以达到去重。回溯三部曲:
- 递归函数参数:此题还需要加一个bool型数组used,用来记录同一树枝上的元素是否使用过。这个集合去重的重任就是used来完成的。
public void combination(int[] candidates, int target,int sum,int startIndex)
- 递归终止条件:终止条件为
sum > target
和sum == target
。
if(sum==target){
res.add(new ArrayList(path));
return;
}
- 单层搜索的逻辑:前面我们提到:要去重的是“同一树层上的使用过”,如果判断同一树层上元素(相同的元素)是否使用过了呢。如果
candidates[i] == candidates[i - 1]
并且used[i - 1] == false
,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]。此时for循环里就应该做continue的操作。
for(int i=startIndex;i<candidates.length&&sum+candidates[i]<=target;i++){
if (i > 0 && candidates[i] == candidates[i - 1] && !used[i - 1]) {
continue;
}
path.add(candidates[i]);
used[i] = true;
combination(candidates,target,sum+candidates[i],i+1);
path.remove(path.size()-1);
used[i] = false;
}
整体代码:
List<Integer> path = new ArrayList();
List<List<Integer>> res = new ArrayList();
boolean[] used;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
used = new boolean[candidates.length];
Arrays.fill(used,false);
Arrays.sort(candidates);
combination(candidates,target,0,0);
return res;
}
public void combination(int[] candidates, int target,int sum,int startIndex) {
if(sum==target){
res.add(new ArrayList(path));
return;
}
for(int i=startIndex;i<candidates.length&&sum+candidates[i]<=target;i++){
if (i > 0 && candidates[i] == candidates[i - 1] && !used[i - 1]) {
continue;
}
path.add(candidates[i]);
used[i] = true;
combination(candidates,target,sum+candidates[i],i+1);
path.remove(path.size()-1);
used[i] = false;
}
}
3.力扣131(分割回文串)
本题属于回溯算法中的切割问题,需要好好理解,我们需要重新定义一个方法来判断是否为回文子串,并且在遍历的过程中进行剪枝操作,把不是回文子串的直接进行返回进行剪枝,而这个竖线我们利用竖线前的下标来解决。回溯三部曲:
- 递归函数参数:全局变量数组path存放切割后回文的子串,二维数组result存放结果集。 (这两个参数可以放到函数参数里)本题递归函数参数还需要startIndex,因为切割过的地方,不能重复切割,和组合问题也是保持一致的。
public void part(String s,int startIndex)
- 递归函数终止条件:从树形结构的图中可以看出:切割线切到了字符串最后面,说明找到了一种切割方法,此时就是本层递归的终止终止条件;递归参数需要传入startIndex,表示下一轮递归遍历的起始位置,这个startIndex就是切割线。
if(startIndex==s.length()){
res.add(new ArrayList(path));
return;
}
- 单层搜索的逻辑:在
for (int i = startIndex; i < s.length()
; i++)
循环中,我们 定义了起始位置startIndex,那么 [startIndex, i] 就是要截取的子串。首先判断这个子串是不是回文,如果是回文,就加入在path中,path用来记录切割过的回文子串。
for(int i=startIndex;i<s.length();i++){
if(isisPalindrome(s,startIndex,i)){//是回文子串
String str = s.substring(startIndex,i+1);
path.add(str);
}else{//不是回文子串
continue;
}
part(s,i+1);
path.remove(path.size()-1);
}
整体代码:
List<List<String>> res = new ArrayList();
List<String> path = new ArrayList();
public List<List<String>> partition(String s) {
part(s,0);
return res;
}
public void part(String s,int startIndex) {
if(startIndex==s.length()){
res.add(new ArrayList(path));
return;
}
for(int i=startIndex;i<s.length();i++){
if(isisPalindrome(s,startIndex,i)){//是回文子串
String str = s.substring(startIndex,i+1);
path.add(str);
}else{//不是回文子串
continue;
}
part(s,i+1);
path.remove(path.size()-1);
}
}
//判断是否是回文子串
public boolean isisPalindrome(String s,int left,int right) {
for(int i=left,j=right;i<=j;i++,j--){
if(s.charAt(i)!=s.charAt(j)){
return false;
}
}
return true;
}