- 组合总和 II
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
class Solution {
// 定义一个链表用于保存当前遍历路径中的元素
LinkedList<Integer> path = new LinkedList<>();
// 定义一个列表用于保存符合条件的组合
List<List<Integer>> ans = new ArrayList<>();
// 定义一个布尔类型数组,用于记录数组中的元素是否被访问过
boolean[] used;
// 定义一个整型变量,用于记录当前路径中元素之和
int sum = 0;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
// 初始化 used 数组
used = new boolean[candidates.length];
// 将 used 数组中的元素初始化为 false
Arrays.fill(used, false);
// 对 candidates 数组进行排序,以便处理重复元素
Arrays.sort(candidates);
// 调用回溯方法
backTracking(candidates, target, 0);
// 返回符合条件的组合列表
return ans;
}
private void backTracking(int[] candidates, int target, int startIndex) {
// 如果当前路径的元素之和等于目标值
if (sum == target) {
// 将当前路径添加到结果列表中
ans.add(new ArrayList(path));
}
// 遍历 candidates 数组中的元素,从 startIndex 开始
for (int i = startIndex; i < candidates.length; i++) {
// 如果当前元素加上当前路径的和大于目标值
if (sum + candidates[i] > target) {
// 结束当前循环
break;
}
// 判断当前元素是否与前一个元素相等,且前一个元素没有被访问过
if (i > 0 && candidates[i] == candidates[i - 1] && !used[i - 1]) {
// 跳过当前元素,避免重复组合
continue;
}
// 标记当前元素为已访问
used[i] = true;
// 更新当前路径的和
sum += candidates[i];
// 将当前元素添加到路径中
path.add(candidates[i]);
// 递归调用 backTracking 方法,从当前元素的下一个元素开始遍历
backTracking(candidates, target, i + 1);
// 回溯,恢复访问标记、路径和和路径
used[i] = false;
sum -= candidates[i];
path.removeLast();
}
}
}
1 初始化 used 数组:
在 Java 中,布尔类型数组默认值为 false。used = new boolean[candidates.length]; 这行代码创建了一个长度等于 candidates 数组长度的布尔类型数组。这是因为我们需要跟踪 candidates 数组中每个元素的使用情况。这里不需要再进行 Arrays.fill(used, false);,因为布尔数组已经默认初始化为 false。
2 回溯过程分析:
假设我们有以下输入:candidates = [1, 1, 2, 5, 6, 7, 10], target = 8。
排序后的 candidates 为:[1, 1, 2, 5, 6, 7, 10]。
1. 从第一个元素开始(1),sum = 0:
1.1. 选择 1,sum = 1 -> [1]
1.1.1. 因为下一个元素与当前元素相同,跳过
1.1.2. 选择 2,sum = 3 -> [1, 2]
1.1.2.1. 选择 5,sum = 8 -> [1, 2, 5] (找到一个解)
1.1.2.2. 跳过 6,因为选择它会超过目标值
1.1.2.3. 跳过剩余元素,因为选择它们会超过目标值
1.1.3. 回溯,移除 2,sum = 1 -> [1]
1.2. 移除 1,sum = 0 -> []
2. 从第二个元素开始(1),sum = 0:
2.1. 选择 1,sum = 1 -> [1]
2.1.1. 选择 2,sum = 3 -> [1, 2]
2.1.1.1. 选择 5,sum = 8 -> [1, 2, 5] (找到一个解)
2.1.1.2. 跳过 6,因为选择它会超过目标值
2.1.1.3. 跳过剩余元素,因为选择它们会超过目标值
2.1.2. 回溯,移除 2,sum = 1 -> [1]
2.2. 移除 1,sum = 0 -> []
3. 从第三个元素开始(2),sum = 0:
3.1. 选择 2,sum = 2 -> [2]
3.1.1. 选择 5,sum = 7 -> [2, 5]
3.1.1.1. 选择 6,sum = 13 (超过目标值),回溯
3.1.1.2. 选择 7,sum = 14 (超过目标值),回溯
3.1.1.3. 跳过 10,因为选择它会超过目标值
3.1.2. 回溯,移除 5,sum = 2 -> [2]
3.2. 移除 2,sum = 0 -> []
4. 从第四个元素开始(5),sum = 0:
4.1. 选择 5,sum = 5 -> [5]
4.1.1. 选择 6,sum = 11 (超过目标值),回溯
4.1.2. 选择 7,sum = 12 (超过目标值),回溯
4.1.3. 跳过 10,因为选择它会超过目标值
4.2. 回溯,移除 5,sum = 0 -> []
5. 从第五个元素开始(6),sum = 0:
5.1. 选择 6,sum = 6 -> [6]
5.1.1. 选择 7,sum = 13 (超过目标值),回溯
5.1.2. 跳过 10,因为选择它会超过目标值
5.2. 回溯,移除 6,sum = 0 -> []
6. 从第六个元素开始(7),sum = 0:
6.1. 选择 7,sum = 7 -> [7]
6.1.1. 跳过 10,因为选择它会超过目标值
6.2. 回溯,移除 7,sum = 0 -> []
7. 从第七个元素开始(10),sum = 0:
7.1. 跳过 10,因为选择它会超过目标值
在整个回溯过程中,找到了两个符合条件的组合:[1, 2, 5]。这些组合会被添加到结果列表中。在回溯的过程中,我们通过判断当前元素与前一个元素是否相等以及前一个元素是否被访问过,来跳过重复元素。回溯过程中,我们不断地选择元素并更新路径和,直到路径和等于目标值或超过目标值。当路径和等于目标值时,将当前路径添加到结果列表中;当路径和超过目标值时,回溯并移除最后一个添加的元素。通过这个过程,我们遍历了所有可能的组合。
在整个回溯过程中,我们在每一层递归中尝试添加一个新元素,然后检查其与目标值之间的关系。如果当前路径中元素之和超过了目标值,我们停止在当前路径中添加更多元素。如果元素之和等于目标值,我们将当前路径添加到结果列表中。无论哪种情况,都会回溯到上一层递归,撤销之前添加的元素,继续尝试其他可能的组合。
这样的回溯过程可以确保我们找到所有符合条件的组合,避免重复和无效组合。最终,结果列表将包含所有符合条件的组合,即所有元素之和等于目标值的组合。
总结回溯过程:
- 从第一个元素开始尝试组合
- 在每一层递归中添加一个新元素,检查其与目标值之间的关系
- 如果当前路径中元素之和超过了目标值,回溯并尝试其他可能的组合
- 如果当前路径中元素之和等于目标值,将当前路径添加到结果列表中
- 回溯到上一层递归,撤销之前添加的元素,继续尝试其他可能的组合
- 遍历所有可能的组合,直到找到所有符合条件的组合