题目:
给你一个 无重复元素 的整数数组
candidates
和一个目标整数target
,找出candidates
中可以使数字和为目标数target
的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates
中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。对于给定的输入,保证和为
target
的不同组合数少于150
个。示例 1:
输入:candidates = [2,3,6,7], target = 7 输出:[[2,2,3],[7]] 解释: 2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。 7 也是一个候选, 7 = 7 。 仅有这两种组合。示例 2:
输入: candidates = [2,3,5], target = 8 输出: [[2,2,2,2],[2,3,3],[3,5]]示例 3:
输入: candidates = [2], target = 1 输出: []提示:
1 <= candidates.length <= 30
2 <= candidates[i] <= 40
candidates
的所有元素 互不相同1 <= target <= 40
[LeetCode] 39. 组合总和
自己看到题目的第一想法
通过回溯的方式, 当递归到某个路径下, 总和与目标值相同时, 则将当前路径添加到结果集中, 同时返回.
如果做一下排序的话, 只要遇到一个值满足或者路径节点总和和大于目标值, 即可终止当前树层的递归, 返回上一树层继续.
看完代码随想录之后的想法
基本上差不多, 但是我一开始写复杂了, 给 backTracking 加了一个返回值... 实际上完全没有必要.
当时想的是, 如果在 for 语句内进行递归的话, 如果 index 永远传的是 startIndex, 那如何才能避免死循环呢. 然而实际上在某一层一定会遇到所有节点的 sum > target , 因此递归就终止了. 同时在某一层中, 因为 startIndex 往下移动, 因此可以选取起他的路径. 不会死循环. 然而因为脑子里无法形成遍历的脑图, 所以整个都是懵的. 最好的方式可能是想几个案例, 根据案例画一下树形结构, 然后模拟一下递归的过程, 这样会清晰很多.
class Solution {
private List<List<Integer>> result = new ArrayList<>();
private List<Integer> path = new ArrayList<>();
private int sum = 0;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
if (null == candidates) {
return result;
}
Arrays.sort(candidates);
backTracking(candidates, target, 0);
return result;
}
private 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++) {
if (sum + candidates[i] > target) {
return;
}
path.add(candidates[i]);
sum += candidates[i];
backTracking(candidates, target, i);
path.remove(path.size() - 1);
sum -= candidates[i];
}
}
}
自己实现过程中遇到哪些困难
一开始没想明白递归的结束条件, 把代码写的有点复杂了.
看了视频之后才知道, 本来是挺简单的.
题目:
给定一个候选人编号的集合
candidates
和一个目标数target
,找出candidates
中所有可以使数字和为target
的组合。
candidates
中的每个数字在每个组合中只能使用 一次 。注意:解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,输出: [ [1,1,6], [1,2,5], [1,7], [2,6] ]示例 2:
输入: candidates = [2,5,2,1,2], target = 5, 输出: [ [1,2,2], [5] ]提示:
1 <= candidates.length <= 100
1 <= candidates[i] <= 50
1 <= target <= 30
[LeetCode] 40. 组合总和 II
自己看到题目的第一想法
啊哈, 求总和, 那就用模板来回溯吧. 想必不会那么简单的...
哦! 去重来了! 排序吧! 如果前一个元素和当前元素是相同的, 说明当前元素与之后的元素的组合已经遍历过了, 因此需要忽略当前元素.
看完代码随想录之后的想法
原来去重还可以这么写. int[] used 数组大法妙妙妙!!!
class Solution {
private List<List<Integer>> result = new ArrayList<>();
private List<Integer> path = new ArrayList<>();
private int sum = 0;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
if (candidates == null) {
return result;
}
Arrays.sort(candidates);
backTracking(candidates, target, 0, new int[candidates.length]);
return result;
}
private void backTracking(int[] candidates, int target, int startIndex, int[] used) {
if (sum > target) {
return;
}
if (sum == target) {
result.add(new ArrayList<>(path));
return;
}
for (int i = startIndex; i < candidates.length && sum + candidates[i] <= target; i++) {
if (i > 0 && candidates[i - 1] == candidates[i] && used[i - 1] == 0) {
// used[i - 1] == 0 说明当前递归过程中, 前一个元素是用不到的
// 这种情况只会出现于当前元素的递归层级和前一个元素是相同的情况
// 而这时候说明当前元素与当前元素之后的元素的组合条件,
// 在前一个元素的遍历过程中已经处理过了, 因此需要忽略当前元素
continue;// 后面的元素还是需要继续处理的.
}
path.add(candidates[i]);
sum += candidates[i];
used[i] = 1;
backTracking(candidates, target, i + 1, used);
path.remove(path.size() - 1);
sum -= candidates[i];
used[i] = 0;
}
}
}
class Solution {
private List<List<Integer>> result = new ArrayList<>();
private List<Integer> path = new ArrayList<>();
private int sum = 0;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
if (candidates == null) {
return result;
}
Arrays.sort(candidates);
backTracking(candidates, target, 0);
return result;
}
private 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 && sum + candidates[i] <= target; i++) {
if (i > startIndex && candidates[i - 1] == candidates[i]) {
// used[i - 1] == 0 说明当前递归过程中, 前一个元素是用不到的
// 这种情况只会出现于当前元素的递归层级和前一个元素是相同的情况
// 而这时候说明当前元素与当前元素之后的元素的组合条件,
// 在前一个元素的遍历过程中已经处理过了, 因此需要忽略当前元素
continue;// 后面的元素还是需要继续处理的.
}
path.add(candidates[i]);
sum += candidates[i];
backTracking(candidates, target, i + 1);
path.remove(path.size() - 1);
sum -= candidates[i];
}
}
}
自己实现过程中遇到哪些困难
还是对于递归结束的逻辑写的复杂了...
题目:
给你一个字符串
s
,请你将s
分割成一些子串,使每个子串都是回文串
。返回
s
所有可能的分割方案。示例 1:
输入:s = "aab" 输出:[["a","a","b"],["aa","b"]]示例 2:
输入:s = "a" 输出:[["a"]]提示:
1 <= s.length <= 16
s
仅由小写英文字母组成
[LeetCode] 131. 分割回文串
自己看到题目的第一想法
这种几乎是无限切割, 乱如麻...
用 Map 来保存切割过的索引吗?
没想明白.
看完代码随想录之后的想法
答案竟然如此简单!
关键的想法是: 对于每一个元素, 都要和后一位、后两位、后 n 位做一个组合, 看看是否满足回文串. 同时当找到当前元素的满足回文串的组合时, 需要继续迭代, 寻找下一个元素开始的所有满足条件的回文串. 同时因为回溯的原因, 我们可以暴力穷举所有的元素.
这里我觉得可以给自己一个提醒: 回溯就是暴力穷举, 减枝操作可以优化时间复杂度.
class Solution {
private List<List<String>> result = new ArrayList<>();
private List<String> path = new ArrayList<>();
public List<List<String>> partition(String s) {
backTracking(s.toCharArray(), 0);
return result;
}
private void backTracking(char[] chars, int startIndex) {
if (startIndex == chars.length) {
result.add(new ArrayList<>(path));
return;
}
for (int i = startIndex; i < chars.length; i++) {
if (!checkPath(chars, startIndex, i)) {
continue;
}
path.add(new String(chars, startIndex, i - startIndex + 1));
backTracking(chars, i + 1);
path.remove(path.size() - 1);
}
}
private boolean checkPath(char[] chars, int startIndex, int endIndex) {
for (int i = startIndex; i <= startIndex + ((endIndex - startIndex) >> 1); i++) {
if (chars[i] != chars[endIndex - (i - startIndex)]) {
return false;
}
}
return true;
}
}
自己实现过程中遇到哪些困难
毕竟一开始没做出来, 看过视频后, 代码本身是没什么难度的.