Day 26
去重:
所谓去重,其实就是使用过的元素不能重复选取。
组合问题可以抽象为树形结构,那么“使用过”在树形结构上有两个维度,一个维度是同一个树枝上使用过,一个维度是同一个数层上使用过。
树层去重:
如何判断同一树层上元素(相同的元素)是否使用过了呢?
如果candidates[i] == candidates[i - 1] 并且 used[i - 1] == false,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]。
此时for循环里就应该做continue的操作。
图解 40.组合总和,如图:
Day 27
这里主要还是去重的逻辑,跟上面的一样,主要是树层去重,树枝不需要去重
也就是,一个数组内元素可以重复,但是结果中不能有相同的数组。
直接给出代码:
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
backtracking(nums, 0);
return res;
}
private void backtracking(int[] nums, int start) {
res.add(new ArrayList<>(list));
int[] used = new int[nums.length]; // 只是树层去重
for (int i = start; i < nums.length; i++) {
// 去重的逻辑
if (i > start && nums[i] == nums[i-1] && used[i] == 0) continue;
list.add(nums[i]);
used[i] = 1;
backtracking(nums, i + 1);
used[i] = 0;
list.remove(list.size() - 1);
}
}
}
Day 28
递增子序列
这道题其实和上一道 求子集 思路很类似。
不同点在于,递增子序列元素默认不可以排序,子集去重的前提是数组排序后的。这样可以判断相邻元素是否使用过。
所以需要修改去重的逻辑。
同样是树层去重的逻辑,但是数组不一定有序,需要另外一个数据结构记录该层已经使用过的元素,可以使用Set,也可以使用数组。
如:使用Set记录本层中已经使用过的元素,
遍历时,如果本层数组末尾元素大于遍历元素,或者Set中存在该元素,for循环中都应该 continue 这次遍历。
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> list = new ArrayList<>();
public List<List<Integer>> findSubsequences(int[] nums) {
backtracking(nums, 0);
return res;
}
private void backtracking(int[] nums, int start) {
if (list.size() > 1) {
res.add(new ArrayList<>(list));
}
HashSet<Integer> set = new HashSet<>();
for (int i = start; i < nums.length; i++) {
if (!list.isEmpty() && nums[i] < list.get(list.size() - 1) || set.contains(nums[i]) ) {
continue;
}
set.add(nums[i]);
list.add(nums[i]);
backtracking(nums, i+1);
list.remove(list.size() - 1);
// 不需要再对set数组移除元素,应为set定义再回溯函数内,只记录本层的循环,下一层的就重新定义了。
}
}
}
全排列
排列问题,如果数组无重复元素,比较简单。
回溯时,每次从头遍历,只需要树枝去重即可。
树枝去重,即使用used数组记录元素是否使用过即可。
if (used[i] == 1) continue;
去重都需要排序数组
如果数组中存在重复元素的话,返回不重复的结果。
首选同层使用过的元素需要去重,同时应为排序的原因,每次从数组第一位开始遍历,树枝上就也需要去重操作。
继续使用used数组,
树层去重:
// ...&& used[i-1] == 0 同一层使用过nums[i-1]元素
// ...&& used[i-1] == 1 同一树枝使用过nums[i-1]元素
// 如果同一树层nums[i - 1]使用过则直接跳过
if (i > 0 && nums[i-1] == nums[i] && used[i-1] == 0) {
continue;
}
if (used[i] == 0) { // 表示 i 位置元素还未使用
}
完整代码:
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
int[] used = new int[nums.length];
Arrays.sort(nums);
backtracking(nums, used);
return res;
}
private void backtracking(int[] nums, int[] used) {
if (path.size() == nums.length) {
res.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
// used[i - 1] == true,说明同一树枝nums[i - 1]使用过
// used[i - 1] == false,说明同一树层nums[i - 1]使用过
if (i > 0 && nums[i-1] == nums[i] && used[i-1] == 0) continue;
if (used[i] == 0) {
used[i] = 1;
path.add(nums[i]);
backtracking(nums,used);
path.remove(path.size() - 1);
used[i] = 0;
}
}
}
}
使用set数组的话,无法做树枝的遍历去重。因为 set 数组只记录不同元素,相同元素无法被记录,所以从头遍历时无法判断哪个元素已经被使用过了。