78.Given a set of distinct integers, nums, return all possible subsets (the power set).
Note: The solution set must not contain duplicate subsets.
Example:
Input: nums = [1,2,3]
Output:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
- 暴力解
求子集,先加一个空集, 然后遍历数组,把每一个结果加上前一个结果。
比如说,先加入空集,然后加入{1}, 然后再添加{1,2}, 也就是把2加入到前一个结果{1}中。然后添加{1,2,3}(把3添加到{1,2}中)。这是大循环里的第一步,然后再遍历到2,依次。。。下面来实现
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
//加空集
res.add(new ArrayList<>());
//大循环,循环nums
for (int num : nums) {
//记录此时res的size,因为第二层循环只用到此时res已经存在的item,后面的size会变
int size = res.size();
//第二层循环
for (int i = 0; i < size; i++) {
//初始化一个新的list,用遍历到的num + 已经包含res中存在的结果,形成一个新的列表
List <Integer> temp = new ArrayList<>(res.get(i));
temp.add(num);
res.add(temp);
}
}
return res;
}
}
时间复杂度:O(N*2^N)
空间复杂度:O()
2. backtrack
回溯本质上也是暴力解,就是穷举。假设nums=[1,2,3]。先遍历1的子节点,把所有可能的解,加入集合,然后撤销这个选择。 然后选择2这条路径,再递归。
参考公众号labuladong的回溯算法框架, 我们直接开搞。(搞之前简单画一个树, 看一下结构,这里就不画了,太麻烦)
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
backtrack(res, nums, new ArrayList<>(), 0);
return res;
}
private void backtrack(List<List<Integer>> res, int[] nums, List<Integer> temp, int index) {
res.add(new ArrayList<>(temp));
for (int i = index; i < nums.length; i++) {
//chose the path
temp.add(nums[i]);
// cuz we can't chose the same item, so we need to traverse from the next number i + 1.
backtrack(res, nums, temp, i + 1);
// backtrack
temp.remove(temp.size() - 1);
}
}
}
Given a collection of integers that might contain duplicates, nums, return all possible subsets (the power set).
Note: The solution set must not contain duplicate subsets.
Example:
Example:
Input: [1,2,2]
Output:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]
这题临时写的,我会把我所有的思路直接写上来。
首先,有重复吗,那直接用set就是了,我们试试
class Solution {
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> res = new ArrayList<>();
backtrack(res, new ArrayList<>(), nums, 0);
Set<List<Integer>> ans = new HashSet<>(res);
List<List<Integer>> result = new ArrayList<>(ans);
return result;
}
private void backtrack(List<List<Integer>> res, List<Integer> temp, int[] nums, int index) {
res.add(new ArrayList<>(temp));
for (int i = index; i < nums.length; i++) {
temp.add(nums[i]);
backtrack(res, temp, nums, i + 1);
temp.remove(temp.size() - 1);
}
}
}
继续优化一下
class Solution {
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
Set<List<Integer>> res = new HashSet<>();
backtrack(res, new ArrayList<>(), nums, 0);
List<List<Integer>> result = new ArrayList<>(res);
return result;
}
private void backtrack(Set<List<Integer>> res, List<Integer> temp, int[] nums, int index) {
res.add(new ArrayList<>(temp));
for (int i = index; i < nums.length; i++) {
temp.add(nums[i]);
backtrack(res, temp, nums, i + 1);
temp.remove(temp.size() - 1);
}
}
}
搞定,如果不用set,那么在递归中设置条件,如果后面一个数和前面的相同,那么跳过即可
- LeetCode 46 全排列
Given a collection of distinct integers, return all possible
permutations.
Example:
Input: [1,2,3]
Output:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
穷举的形式,那么用回溯算法。具体可以画个树的图来清晰算法。
我们还是基于78题,与78题不同的是, 全排列递归的base case是集合长度达到nums.length的时候,先把框架写出来:
class Solution {
public List<List<Integer>> permute(int[] nums) {
Arrys.sort(nums);
List<List<Integer>> res = new ArrayList<>();
backtrack(res, nums, new ArrayList<>(), 0);
return res;
}
private void backtrack(List<List<Integer>> res, int[] nums, List<Integer> temp, int index) {
if (temp.size() == nums.length) {
res.add(new ArrayList<>(temp));}
for (int i = index; i < nums.length; i++) {
//chose the path
temp.add(nums[i]);
// cuz we can't chose the same item, so we need to traverse from the next number i + 1.
backtrack(res, nums, temp, i + 1);
// backtrack
temp.remove(temp.size() - 1);
}
}
}
这样运行出来只有一个解,来修改。首先sort可以去掉。然后,问题是出在index上,全排列并不需要额外的指针:
class Solution {
public List<List<Integer>> permute(int[] nums) {
Arrys.sort(nums);
List<List<Integer>> res = new ArrayList<>();
backtrack(res, nums, new ArrayList<>());
return res;
}
private void backtrack(List<List<Integer>> res, int[] nums, List<Integer> temp) {
if (temp.size() == nums.length) {
res.add(new ArrayList<>(temp));}
for (int i = 0; i < nums.length; i++) {
if (temp.contains(nums[i])) continue;
//chose the path
temp.add(nums[i]);
// cuz we can't chose the same item, so we need to traverse from the next number i + 1.
backtrack(res, nums, temp);
// backtrack
temp.remove(temp.size() - 1);
}
}
}
运行结果:Memory Limit Exceeded
继续修改,
递归中会不断地重复添加nums[i], 因此我们需要剔除已经存在的情况
事实上并没有选择性遍历,而是每一遍都完整的遍历nums中的数,所以会存在重复的现象,导致一致在递归。 下面尝试用额外的指针来限制选择,这里用一个可能不太好弄,可以尝试一下用两个指针。
太麻烦了,两个指针的话既要考虑后面的数字,也要考虑前面的数字,放弃。
那只要加个条件即可
if (temp.contains(nums[i])) continue;
class Solution {
public List<List<Integer>> permute(int[] nums) {
//Arrys.sort(nums);
List<List<Integer>> res = new ArrayList<>();
backtrack(res, nums, new ArrayList<>());
return res;
}
private void backtrack(List<List<Integer>> res, int[] nums, List<Integer> temp) {
if (temp.size() == nums.length) {
res.add(new ArrayList<>(temp));}
for (int i = 0; i < nums.length; i++) {
if (temp.contains(nums[i])) continue;
//chose the path
temp.add(nums[i]);
// cuz we can't chose the same item, so we need to traverse from the next number i + 1.
backtrack(res, nums, temp);
// backtrack
temp.remove(temp.size() - 1);
}
}
}