491.递增子序列
思路
整体是通过回溯来解决子集问题
- 本题数组的顺序不能改变,否则无法通过递归返回原顺序
- 同层需要去重,纵向不用去重 – > 完全相同结果要去重,数组内相同元素不去重
- 因为数组需要保持原顺序,同层去重无法通过前后对比实现,所以要借助哈希表
解题方法
注意:
- 数值范围有限,可以使用数组来替代 set 实现哈希表的作用
- 终止条件注意有两个:叶子节点和不符合条件的节点
复杂度
- 时间复杂度:
添加时间复杂度, 示例: O ( n ) O(n) O(n)
- 空间复杂度:
添加空间复杂度, 示例: O ( n ) O(n) O(n)
Code
class Solution {
private List<List<Integer>> result = new ArrayList<>();
private LinkedList<Integer> path = new LinkedList<>();
// 整体是通过回溯来解决子集问题
// 如果使用回溯来解决,则数组的顺序不能改变,否则无法通过递归返回原顺序
// 同层需要去重,纵向不用去重 -- > 也即,完全相同结果要去重,数组内相同元素不去重
// 因为数组需要保持原顺序,同层去重无法通过前后对比实现,所以要借助 set
public List<List<Integer>> findSubsequences(int[] nums) {
backTracking(nums, 0);
return result;
}
private void backTracking(int[] nums, int startIndex) {
// 终止条件
// 1. 到了叶子节点
// 2. 纵向上已经到了不符合条件的节点,也即当前节点值小于父节点值
if (nums.length < startIndex) {
return;
}
if (path.size() >= 2 && path.get(path.size() - 2) > path.get(path.size() - 1)) {
return;
}
// 收集子集
if (path.size() >= 2) {
result.add(new ArrayList<>(path));
}
// 横向遍历
// 记录当前层级已经用过的数值
// HashSet<Integer> used = new HashSet<>();
// 已知数值范围,可以直接用数组来模拟哈希表,提高效率
int[] used = new int[201];
for (int i = startIndex; i < nums.length; i++) {
if (/*used.contains(nums[i])*/used[nums[i] + 100] == 1) {
continue;
}
path.add(nums[i]);
// used.add(nums[i]);
used[nums[i] + 100] = 1;
backTracking(nums, i + 1);
path.removeLast();
}
}
}
46.全排列
思路
排列不同于组合,[1, 2] 和 [2, 1] 对组合是同一个答案,在处理时要排除;但对于排列就是不同的两个答案
- 组合通过在纵向遍历中传递 startIndex 来控制下一层级从上一层级的后面开始横向遍历,以此保证组合层面的唯一性
- 排列通过 used 数组,保证在纵向方向上能完整遍历整个数组,而在横向方向上,除了各自父及祖先节点不要,其他的都要遍历
解题方法
回溯模板配合 used 数组使用
复杂度
- 时间复杂度:
添加时间复杂度, 示例: O ( n ) O(n) O(n)
- 空间复杂度:
添加空间复杂度, 示例: O ( n ) O(n) O(n)
Code
class Solution {
private List<List<Integer>> result = new ArrayList<>();
private LinkedList<Integer> path = new LinkedList<>();
boolean[] used;
// 排列不同于组合,[1, 2] 和 [2, 1] 对组合是同一个答案,在处理时要排除;但对于排列就是不同的两个答案
// 组合通过在纵向遍历中传递 startIndex 来控制下一层级从上一层级的后面开始横向遍历,以此保证组合层面的唯一性
// 排列通过 used 数组
public List<List<Integer>> permute(int[] nums) {
used = new boolean[nums.length];
backTracking(nums);
return result;
}
private void backTracking(int[] nums) {
// 终止条件--叶子节点
if (path.size() == nums.length) {
result.add(new ArrayList<>(path));
return;
}
// 横向遍历
// 父节点及祖先节点用过的索引不用,其余的都要
for (int i = 0; i < nums.length; i++) {
if (used[i]) {
continue;
}
used[i] = true;
path.add(nums[i]);
backTracking(nums);
used[i] = false;
path.removeLast();
}
}
}
47.全排列 II
思路
同一层级的去重,这里不在意顺序,可以先排序
注意:
和组合的同一层级去重一样,去重只针对当前层级可用的数值,不能带入之前层级已经用过(本层级不可用)的数值
解题方法
描述你的解题方法
复杂度
- 时间复杂度:
添加时间复杂度, 示例: O ( n ) O(n) O(n)
- 空间复杂度:
添加空间复杂度, 示例: O ( n ) O(n) O(n)
Code
class Solution {
private List<List<Integer>> result = new ArrayList<>();
private LinkedList<Integer> path = new LinkedList<>();
private boolean[] used;
// 同一层级的去重,这里不在意顺序,可以先排序
// 和组合的同一层级去重一样,当前节点去重判断要特别注意,一定要忽略之前层级的数值,否则会过滤掉应有的结果
public List<List<Integer>> permuteUnique(int[] nums) {
Arrays.sort(nums);
used = new boolean[nums.length];
backTracking(nums);
return result;
}
private void backTracking(int[] nums) {
if (nums.length == path.size()) {
result.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i]) {
continue;
}
// 统一层级去重
// 注意:去重只针对当前层级可用的数值,不能带入之前层级已经用过(本层级不可用)的数值
if (i > 0 && !used[i - 1] && nums[i] == nums[i - 1]) {
continue;
}
path.add(nums[i]);
used[i] = true;
backTracking(nums);
used[i] = false;
path.removeLast();
}
}
}