LeetCode - 39. Combination Sum 组合总和
解题目标:给定一组数组和target,找出所有组合总和为target的集合,元素可以重复。
解题思路:这题和之前的组合问题有些不一样的地方在于之前我们所要找的的组合长度是固定的,但是这道题没有固定的长度,因此深度我们也是没办法确定的,而是需要判断和的情况来判断深度。这道题的主要思路还是根据回溯算法的模板,如果当前的sum已经大于了target,就可以直接跳过这条分枝的查找或直接return。
注意事项:题目的candidate在回溯前需要重新从小到大排序,因为需要从小加到大才能方便剪枝。并且下一层回溯中传入的startIdex应该是i而不是i+1,因为元素是可重复的,自身也应该被下一层考虑。startIndex的作用就是在于让下一层的递归知道我们的起始位置是哪一个元素。
class Solution {
private List<List<Integer>> res = new ArrayList<>();
private List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates); //sort candidates array
backtracking(candidates, target, 0, 0);
return res;
}
public void backtracking(int[] candidates, int target, int curSum, int startIdx) {
if (curSum == target) {
res.add(new ArrayList<>(path));
return;
}
for (int i=startIdx; i < candidates.length; i++) {
if (curSum + candidates[i] > target) break;
path.add(candidates[i]);
backtracking(candidates, target, curSum + candidates[i], i); //这里的startIdx不用+1因为元素是可重复使用的
path.remove(path.size() - 1);
}
}
}
LeetCode - 40. Combination Sum II 组合总和II
解题目标:给定一组数组和一个target,找出所有可能的总和为target的组合,元素不可复用。
解题思路:这题和组合总和有一些略微的不同,这次的组合是不可复用同一个位置元素的(数值相同依然可以)。按照之前的思路做这题,我们会发现当题目给定的candidate中有元素重复的时候,我们所得到的结果也会有重复的组合。因此在同一层中遇到了重复的元素应该需要跳过。并且数组排序的意义在于我们可以把数值相同的元素放在一起。因此这里去重指的是树层去重。去重逻辑需要满足两个条件,一个是nums[i] == nums[i-1],这个目的是判断当前是否用到了重复的元素,但是逛有这个条件还是不够,因为我们不能排除树枝上用到的相同元素。因此这个时候需要一个used数组来对每一个节点的使用情况进行跟踪,第二个条件就是used数组对应前一个元素used[i-1]的下标是0,因为回溯之后我们用的是第二个新的‘1’,因此我们可以判断这个是同树层的重复元素,应当跳过。
class Solution {
private List<List<Integer>> res = new ArrayList<>();
private List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
boolean[] used = new boolean[candidates.length];
backtracking(candidates, target, 0, 0, used);
return res;
}
public void backtracking(int[] candidates, int target, int startIdx, int curSum, boolean[] used) {
if (curSum > target) return;
if (curSum == target) {
res.add(new ArrayList<>(path));
return;
}
for (int i = startIdx; i < candidates.length; i++) {
if (i >=1 && candidates[i] == candidates[i-1] && !used[i-1]) continue;
path.add(candidates[i]);
used[i] = true;
backtracking(candidates, target, i+1, curSum + candidates[i], used);
used[i] = false;
path.removeLast();
}
}
}
LeetCode - 131. Palindrome Partitioning 分割回文串
解题目标:给定一个字符串,列出所有回文子串
解题思路:这题本质上是求解字符串的不同切割方式。startIdx其实就可以当作切割线,s[startIdx, i]其实就是切割后的子串。终止条件就是判断是否当前的startIdx(切割线)以及到了最后一个元素之后,如果是那就直接返回。
class Solution {
private List<List<String>> res = new ArrayList<>();
private List<String> partitionRes = new ArrayList<>();
public List<List<String>> partition(String s) {
backtracking(s, 0, new StringBuilder());
return res;
}
private void backtracking(String s, int startIdx, StringBuilder sb) {
//切割到字符串末尾终止
if (startIdx == s.length()) {
res.add(new ArrayList<>(partitionRes));
return;
}
for(int i=startIdx; i < s.length(); i++) {
sb.append(s.charAt(i));
//检查是否是回文
if (isPalindrome(sb)) {
partitionRes.add(sb.toString());
} else continue;
backtracking(s, i + 1, new StringBuilder()); //这里的sb要清空
partitionRes.removeLast();
}
}
private boolean isPalindrome(StringBuilder sb) {
//检查对称性
for (int i = 0; i < sb.length() / 2; i++) {
if (sb.charAt(i) !=sb.charAt(sb.length() - 1 - i)) return false;
}
return true;
}
}