目录
491. 递增子序列
难度:medium
类型:回溯,类子集问题
思路:
因为不能排序,所以不能使用 40. 组合总和 II 的去重方式。使用hashset来对递归树的某一层去重。
代码:
class Solution {
private List<Integer> path = new ArrayList<>();
private List<List<Integer>> ans = new ArrayList<>();
public List<List<Integer>> findSubsequences(int[] nums) {
if (nums.length < 2) {
return ans;
}
backtracking(nums, 0);
return ans;
}
public void backtracking(int[] nums, int startIndex) {
if (path.size() >= 2) {
ans.add(new ArrayList<>(path));
}
// 因为不能排序,所以不能使用 40. 组合总和 II 的去重方式
// 使用hashset来对递归树的某一层去重
HashSet<Integer> set = new HashSet<>();
for (int i = startIndex; i < nums.length; i++) {
// 去重
if (set.contains(nums[i])) {
continue;
}
// 保证单调递增
if (!path.isEmpty() && nums[i] < path.get(path.size() - 1)) {
continue;
}
set.add(nums[i]);
path.add(nums[i]);
backtracking(nums, i + 1);
path.remove(path.size() - 1);
}
}
}
46. 全排列
难度:medium
类型:回溯,排列
思路:
本题的特点:nums不含重复数字,需要写出所有的排列,每个数字使用一次;
nums不含重复数字:不需要去重操作,下一题则需要去重(47全排列2);
需要写出所有的排列:没有startIndex参数,因为排列需要考虑不同的顺序,所以每一层递归都是从0开始遍历的。
每个数字使用一次:使用hashset或者used数组来保证每个数字只使用一次;
// 使用hashset进行全排列
class Solution {
private List<List<Integer>> ans = new ArrayList<>();
private List<Integer> path = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
backtracking(nums);
return ans;
}
public void backtracking(int[] nums) {
if (path.size() == nums.length) {
ans.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
if (!path.contains(nums[i])) {
path.add(nums[i]);
backtracking(nums);
path.remove(path.size() - 1);
}
}
}
}
// 使用used数组进行全排列
class Solution {
private List<List<Integer>> ans = new ArrayList<>();
private List<Integer> path = new ArrayList<>();
private boolean[] used;
public List<List<Integer>> permute(int[] nums) {
used = new boolean[nums.length];
backtracking(nums, used);
return ans;
}
public void backtracking(int[] nums, boolean[] used) {
if (path.size() == nums.length) {
ans.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
// 该元素已经使用过
if (used[i] == true) {
continue;
}
used[i] = true;
path.add(nums[i]);
backtracking(nums, used);
used[i] = false;
path.remove(path.size() - 1);
}
}
}
- 时间复杂度: O(n!)
- 空间复杂度: O(n)
47. 全排列 II
难度:medium
类型:回溯,排列
思路:
本题特点:排列问题,nums中包含重复数字,每个数字使用一次;
1.排列问题:没有startIndex参数,因为排列需要考虑不同的顺序,所以每一层递归都是从0开始遍历的。
2. nums中包含重复数字:
这道题使用used[i - 1] == false或者used[i - 1] == true来去重都可以,前者是在树层上去重,后者是在树枝上去重。但对used[i - 1]的判断不能省去。必须始终是true或者false的判断。
树层去重(效率更高):
// used[i - 1] == true,说明同⼀树⽀nums[i - 1]使⽤过
// used[i - 1] == false,说明同⼀树层nums[i - 1]使⽤过
// 同一树层nums[i-1]使用过,则跳过
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue;
}
3.每个数字使用一次:
// 同一树枝,nums[i]没处理,则处理
if (used[i] == false) {
used[i] = true;
path.add(nums[i]);
backtracking(nums, used);
used[i] = false;
path.remove(path.size() - 1);
}
代码:
class Solution {
private List<List<Integer>> list = new ArrayList<>();
private List<Integer> path = new ArrayList<>();
private boolean[] used;
public List<List<Integer>> permuteUnique(int[] nums) {
Arrays.sort(nums);
used = new boolean[nums.length];
backtracking(nums, used);
return list;
}
public void backtracking(int[] nums, boolean[] used) {
if (path.size() == nums.length) {
list.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]使⽤过
// 同一树层nums[i-1]使用过,则跳过
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue;
}
// 同一树枝,nums[i]没处理,则处理
if (used[i] == false) {
used[i] = true;
path.add(nums[i]);
backtracking(nums, used);
used[i] = false;
path.remove(path.size() - 1);
}
}
}
}
// 时间复杂度: 最差情况所有元素都是唯一的。复杂度和全排列1都是 O(n! * n) 对于 n 个元素一共有 n! 中排列方案。而对于每一个答案,我们需要 O(n) 去复制最终放到 result 数组
// 空间复杂度: O(n) 回溯树的深度取决于我们有多少个元素
- 时间复杂度: O(n! * n)
- 空间复杂度: O(n)