Day23
39. 组合总和
力扣题目链接
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
思路
正常进行回溯
设置res和path两个全局变量,并使用sum记录数字
终止条件:因为这个题目不知道递归的层数,因此不能像之前那样size为k结束递归,递归结束就是要么sum > target,这时候直接返回,然后在循环中把这个元素减去继续递归;如果sum == target,就加入res
循环中,还需要设置startIndex,如果不设置的话,每次递归都从第一个元素开始,会出现2 2 3 3 2 2这样取重复的情况,因此我们通过startIndex保证第一个位置拿到3之后,第二个位置不会拿到比3小的数字
优化逻辑
对总集合排序之后,如果下一层的sum(就是本层的 sum + candidates[i])已经大于target,就可以结束本轮for循环的遍历。
代码
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) {
backtracking(candidates, target, 0);
return res;
}
private void backtracking(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.addFirst(candidates[i]);
sum += candidates[i];
backtracking(candidates, target, i);
path.removeFirst();
sum -= candidates[i];
}
}
}
class Solution1 {
List<List<Integer>> res = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
int sum = 0;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);//先把数组进行排序,得到递增数组
backtracking(target,candidates,0);
return res;
}
private void backtracking(int target, int[] candidates, int startIndex){
if (sum > target) return;
if (sum == target){
res.add(new ArrayList<>(path));
return;
}
for (int i = startIndex; i < candidates.length; i++){
if (sum + candidates[i] > target) break;//如果加上这个元素之后一定大了,后面的就不要看了,就算回溯加完也更大
int temp = candidates[i];
path.addFirst(temp);
sum += temp;
backtracking(target,candidates,i);
path.removeFirst();
sum -= temp;
}
}
}
40.组合总和II
力扣题目链接
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
思路
这道题目和39.组合总和 如下区别:
本题candidates 中的每个数字在每个组合中只能使用一次。
本题数组candidates的元素是有重复的,而39.组合总和是无重复元素的数组candidates
每个数字用一次好处理,递归的时候让startIndex + 1即可
关键是元素有重复的,比如1 1 6 7中拿8,我需要1 7 和1 1 6两个答案,不能有两个1 7
我们可以先把数组排序,得到定序的数组
先类似上一题进行剪枝处理,如果遍历到某个元素,把它加到sum里面之后发现已经大于target了,那就不用再循环了,这层递归可以结束了,因为后面的元素一定比这个元素大,一定不满足要求了
后面就有难度了
在第一层循环中,先拿到1,然后我进入递归,我想要在递归中能拿到第二个1,保证1 1 6这个答案,所以我不能简单的判断如果这个元素和上一个元素一样就continue,应该保证这个元素不能是每层递归的第一个元素,比如1 1 1 1中拿3也是一样,第一层递归拿到1,第二层递归还要再拿到1,因此只有当这层递归循环了至少一次,才能判断,因此需要是 i > startIndex
加入我第二层递归结束了,返回了第一层递归,继续循环,这时候1 1 6和1 7都拿到了,第一层递归走到了第二个1的位置,这时候应该continue,因为这个元素和上一个元素一样;同理1 1 1 1中拿3,第二层递归结束之后已经拿到了答案,返回第一层递归,走到第二个1,发现和上一个元素一样,那就continue
其实就是,同一数层上两个相同元素不能同时选取,但同一树枝上两个相同元素可以同时选取
代码
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);//先排序
backtracking(candidates,target,0);
return res;
}
private void backtracking(int[] candidates, int target, int startIndex) {
if (sum == target) {
res.add(new ArrayList<>(path));
}
for (int i = startIndex; i < candidates.length; i++) {
if (candidates[i] + sum > target) break;//类比之前的剪枝操作
if (i > startIndex && candidates[i] == candidates[i - 1]) continue;
//如果同层有相同的就返回;注意同枝可以有相同的
path.addFirst(candidates[i]);
sum += candidates[i];
backtracking(candidates,target,i + 1);
path.removeFirst();
sum -= candidates[i];
}
}
}
131.分割回文串
力扣题目链接
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
思路
最简单的是判断回文串,使用双指针即可,传入字符串和头尾指针
思考:需要把字符串进行切割,切割几段不知道,所以还是回溯问题
我们设置一个startIndex表示切割是否结束,如果startIndex == s.length()了,那切割就结束了,然后加入res,注意回文串的判断再前面判断过,所以终止条件这里我们直接加入到res即可
开始递归循环,每次先判断一下是不是回文串,从startIndex到i这段进行判断,如果是,就可以加入path中,如果不是,这次循环结束,让i++再循环
举个例子:
a a b的切割,首先把a传进去,第一层递归,startIndex = 0, i = 0; 判断a这个子字符串是回文串,加入path,然后进入第二层递归,startIndex = 1; i = 1; 判断a是回文串,加入path,进入第三层递归,startIndex = 2; i = 2; 判断b是回文串,加入path,继续递归。这时startIndex = 3,因此加入res,返回第三层递归;
第三层递归把a移除,然后继续循环,startIndex = 2,但是i = 3,已经不满足要求,结束循环,返回第二层递归
第二层递归把a移除,path里面还有一个a,这时startIndex = 1,i++变为2;继续循环,看aa是不是回文串,是因此加入path,然后进入第三层递归,发现b也是,进入第四层递归,然后加入res,结束第四层和第三层递归,移出a,然后回到第二层递归发现aab不是回文串,continue,然后循环结束
代码
class Solution {
List<List<String>> res = new ArrayList<>();
Deque<String> path = new LinkedList<>();
public List<List<String>> partition(String s) {
backtracking(s, 0);
return res;
}
private void backtracking(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)) {
path.addLast(s.substring(startIndex, i + 1));//subString是左闭右开的,想拿到i需要是i+1
} else {
continue;
}
backtracking(s, i + 1);
path.removeLast();
}
}
private boolean isPalindrome(String s, int head, int end) {//判断字符串是否是回文串
while (head <= end) {
if (s.charAt(head) != s.charAt(end)) return false;
head++;
end--;
}
return true;
}
}