代码随想录 day27
题39 组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
思考
1,弄清楚了回溯算法不难写出来大致思路,但自己第一遍写的时候并没有完全写对,问题在于startindex没有写对,造成了结果集中的重复,需要注意for循环的时候应该从startindex开始循环,而不是从0开始,另外本题中要求可以重复使用某元素,因此递归的时候仍然从当前元素开始而不是从下一个元素开始。
class Solution {
LinkedList<Integer> path = new LinkedList<>();
List<List<Integer>> result = new ArrayList<>();
int sum = 0;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
backTracking(candidates, target, 0);
return result;
}
public void backTracking(int[] candidates, int target, int startIndex) {
if(sum > target){
return;
}
if(sum == target){
result.add(new ArrayList<>(path));
return;
}
for(int i = startIndex; i < candidates.length; i++){
sum += candidates[i];
path.add(candidates[i]);
backTracking(candidates, target, i);//从i 开始而不是从i+1开始
sum -= candidates[i];
path.removeLast();
}
}
}
题40 组合总和二
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明: 所有数字(包括目标数)都是正整数。 解集不能包含重复的组合。
示例 1: 输入: candidates = [10,1,2,7,6,1,5], target = 8, 所求解集为: [ [1, 7], [1, 2, 5], [2, 6], [1, 1, 6] ]
示例 2: 输入: candidates = [2,5,2,1,2], target = 5, 所求解集为: [ [1,2,2], [5] ]
思考:
1,自己尝试写了一遍,最后的结果集里有重复,对于如何去重没有搞清楚,不知道应该在哪里做去重的操作。
启发:
1,本题的难点在于给定数组中有重复元素,但要求结果集中不能有重复的组合。这就要求要先对原数组进行排序,并且在合适的时候要判断元素是否使用过,那么合适的时候是什么时候呢,应该是每一层的时候,比如[1,1,2,5,6] 和为8,则第一层时会得到[1,2,5], 那么第二层取到1的时候应该直接跳过,否则又会产生[1,2,5]。但还需要注意在每条路径上也就是遍历深度上不能去重,比如[1,1,6]这个结果是合法的。
2,那么应该如何实现层级上的去重呢,题解中提供了一种很巧妙的方式,引入一个布尔数组used来标记使用过的元素,使用过为true,未使用过为false。那么通过used又如何实现层级去重呢,当第i个元素和第i-1个元素相等并且used[i - 1] == false的时候代表层级遍历到了重复元素,因为此时可以理解为”第一次“(即使之前有相同的也已经收集完包含其的组合结果)遍历到该元素,此时如果和前面的元素相同就直接跳出本层循环,相当于此次不需要收集(因为相同的结果已经被收集过,再收集一次会重复)。
3, 那么如果used[i - 1] 为true呢?其实这种情况代表的是某条路径上的重复元素,这种情况是不需要去重的,比如[1,1,6]。
class Solution {
List<List<Integer>> result = new ArrayList<>();
LinkedList<Integer> path = new LinkedList<>();
int[] used;//标记元素是否被使用过。
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
used = new int[candidates.length];
Arrays.sort(candidates);//先将原数组进行排序
backTracking(candidates, target, 0);
return result;
}
public void backTracking(int[] candidates, int target, int startIndex){
if(target < 0) return;
if(target == 0){
result.add(new ArrayList<>(path));
return;
}
for(int i = startIndex; i < candidates.length ; i++){
//判断层级重复元素
if(i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == 0){
continue;
}
path.add(candidates[i]);
target -= candidates[i];
used[i] = 1;
backTracking(candidates, target, i + 1);
//回溯过程
target += candidates[i];
path.removeLast();
used[i] = 0;
}
}
}
题131 分割成回文子串
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。
示例: 输入: “aab” 输出: [ [“aa”,“b”], [“a”,“a”,“b”] ]
启发
1,分割的问题也很适合采用回溯法。
2, 本题其实还是挺难的,看了题解以后其实还没有理解的特别透彻
class Solution {
LinkedList<String> path = new LinkedList<>();
List<List<String>> result = new ArrayList<>();
public List<List<String>> partition(String s) {
backTracking(s, 0);
return result;
}
//startIndex可以代表切割线
public void backTracking(String s, int startIndex){
//当切割线切到字符串末尾的时候是终止条件
if(startIndex >= s.length()){
result.add(new ArrayList<>(path));
return;
}
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.removeLast();
}
}
//判断回文串
public boolean isPalindrome(String s, int startIndex, int endIndex){
for(int i = startIndex, j = endIndex; i < j; i++, j--){
if(s.charAt(i) != s.charAt(j)) return false;
}
return true;
}
}