给定一个含有不同数字的集合,返回所有可能的全排列。
比如,[1,2,3]
具有如下排列:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
全排列首先考虑深度优先搜索,每个深度的遍历都从 0
到nums.length - 1
每次循环开始需要判断当前值是否已经使用过,即 if (!tempList.contains(nums[i]))
创建一个List
存放临时数据
当tempList.size() == nums.length
时res.add(new ArrayList
将tempList存入结果
此处不能直接add.(tempList)
,否则改变tempList也会导致结果的改变
特别注意每次递归结束前都需要将刚加入的值从tempList中去掉
tempList.remove(tempList.size() - 1)
class Solution {
List> res = new ArrayList>();
List tempList = new ArrayList();
public List> permute(int[] nums) {
dfs(nums);
return res;
}
public void dfs(int[] nums) {
if (tempList.size() == nums.length) {
res.add(new ArrayList(tempList));
return;
}
for (int i = 0; i < nums.length; i++) {
if (!tempList.contains(nums[i])) {
tempList.add(nums[i]);
dfs(nums);
tempList.remove(tempList.size() - 1);
}
}
}
}
给定一个可能包含重复数字的集合,返回所有可能的不同全排列。
例如,[1,1,2]
有以下不同全排列:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
此题跟上一题的区别在于给定数组可能包含重复数字,所以首先将数组排序 Arrays.sort(nums)
由于数组可能包含重复数字,所以不能用if (!tempList.contains(nums[i]))
判断当前值是否使用过
需要创建一个数组boolean[] used = new boolean[nums.length]
记录每个位置的值是否使用过(上题也可以使用这个方法)
但还存在一个问题,由于数组包含重复数字,tempList可能一模一样
即可能出现两个[1,1,2]
两个1分别来自两个位置
所以当每一层遍历到第二次或更多次,即i > 0
时
需要判断前一个值(num[i - 1])
是否被使用过,如果没使用过,继而判断当前值与前一个值是否相等
if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) continue;
class Solution {
List> res = new ArrayList>();
List tempList = new ArrayList();
public List> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
res.add(new ArrayList());
dfs(nums, 0);
return res;
}
public void dfs(int[] nums, int step) {
if (step == nums.length) return;
for (int i = step; i < nums.length; i++) {
if (i > step && nums[i] == nums[i - 1]) continue;
tempList.add(nums[i]);
res.add(new ArrayList(tempList));
dfs(nums, i + 1);
tempList.remove(tempList.size() - 1);
}
}
}
还有一种投机取巧的方,利用List
改写了equals()
方法
所以不需要排序,可以直接在加入结果前判断res
中是否已经包含了这个List
if (!res.contains(tempList))
但List
中的contains()
是线性搜索,即时间复杂度为O(n)
,所以不推荐用这种方法
class Solution {
List> res = new ArrayList>();
List tempList = new ArrayList();
public List> permuteUnique(int[] nums) {
boolean[] used = new boolean[nums.length];
Arrays.sort(nums);
dfs(nums, 0, used);
return res;
}
public void dfs(int[] nums, int step, boolean[] used) {
if (step == nums.length) {
if (!res.contains(tempList)) {
res.add(new ArrayList(tempList));
}
return;
}
for (int i = 0; i < nums.length; i++) {
// if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) continue;
if (!used[i]) {
used[i] = true;
tempList.add(nums[i]);
dfs(nums, step + 1, used);
used[i] = false;
tempList.remove(tempList.size() - 1);
}
}
}
}
给定一组不同的整数 nums,返回所有可能的子集(幂集)。
注意事项:该解决方案集不能包含重复的子集。
例如,如果 nums = [1,2,3]
,结果为以下答案:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
子集与全排列有两大区别
List
长度不等tepmList
加入res
tempList.size() == nums.length
List
中元素不能重复List
包含的元素都相同,只是顺序不一样[1,2,3]和[3,2,1]
List
中的元素都不相同,所以循环不能再从0
开始start
作为dfs()
的输入参数start
设为i + 1
即不会遍历之前访问过的元素class Solution {
List> res = new ArrayList>();
List tempList = new ArrayList();
public List> subsets(int[] nums) {
Arrays.sort(nums);
res.add(new ArrayList());
dfs(nums, 0);
return res;
}
public void dfs(int[] nums, int start) {
if (start == nums.length) return;
for (int i = start; i < nums.length; i++) {
tempList.add(nums[i]);
res.add(new ArrayList(tempList));
dfs(nums, i + 1);
tempList.remove(tempList.size() - 1);
}
}
}
给定一个可能包含重复整数的列表,返回所有可能的子集(幂集)。
注意事项:解决方案集不能包含重复的子集。
例如,如果 nums = [1,2,2]
,答案为:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]
此题是47
与78
题的结合
因为含有重复数字,所以首先需要先将给定数组排序
参考前两题,本题只需要在每次循环开始前判断当前值(nums[i])
与前一个值(nums[i-1)
是否相等
if (i > step && nums[i] == nums[i - 1]) continue;
由于start
变量,所以每次循环都不会遍历之前访问过的元素
因此不需要判断前一个值是否使用过(因为肯定没使用过)
class Solution {
List> res = new ArrayList>();
List tempList = new ArrayList();
public List> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
res.add(new ArrayList());
dfs(nums, 0);
return res;
}
public void dfs(int[] nums, int step) {
if (step == nums.length) return;
for (int i = step; i < nums.length; i++) {
if (i > step && nums[i] == nums[i - 1]) continue;
tempList.add(nums[i]);
res.add(new ArrayList(tempList));
dfs(nums, i + 1);
tempList.remove(tempList.size() - 1);
}
}
}
给一组候选数字(C)(没有重复)并给一个目标数字(T),找出 C 中所有唯一的组合使得它们的和为 T。
可以从 C 无限次数中选择相同的数字。
说明:
例如,给一个候选集合 [2, 3, 6, 7]
并且目标为 7
,
一个解的集合为:
[
[7],
[2, 2, 3]
]
本题与子集的区别在于数组中的数字可以无限使用
因此递归中start
参数传入的值应为i
而不是i + 1
本题还需定义一个remain
参数来记录动态目标值
class Solution {
List> res = new ArrayList>();
List tempList = new ArrayList();
public List> combinationSum(int[] nums, int target) {
dfs(nums, target, 0);
return res;
}
public void dfs(int[] nums, int remain, int step) {
if (remain < 0) return;
else if (remain == 0) {
res.add(new ArrayList(tempList));
return;
}
for (int i = step; i < nums.length; i++) {
tempList.add(nums[i]);
dfs(nums, remain - nums[i], i);
tempList.remove(tempList.size() - 1);
}
}
}
给定候选号码数组 (C) 和目标总和数 (T),找出 C 中候选号码总和为 T 的所有唯一组合。
C 中的每个数字只能在组合中使用一次。
注意:
例如,给定候选集合 [10, 1, 2, 7, 6, 1, 5]
和目标总和数 8
,
可行的集合是:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
首先排序!!!
判断前后值!!!
if (i > step && nums[i] == nums[i - 1]) continue;
class Solution {
List> res = new ArrayList>();
List tempList = new ArrayList();
public List> combinationSum2(int[] nums, int target) {
Arrays.sort(nums);
dfs(nums, target, 0);
return res;
}
public void dfs(int[] nums, int remain, int step) {
if (remain < 0) return;
else if (remain == 0) {
res.add(new ArrayList(tempList));
return;
}
for (int i = step; i < nums.length; i++) {
if (i > step && nums[i] == nums[i - 1]) continue;
tempList.add(nums[i]);
dfs(nums, remain - nums[i], i + 1);
tempList.remove(tempList.size() - 1);
}
}
}
List
中不能包含相同元素,需要引入变量start
来标记循环起始位置,使每次递归不再遍历之前访问过的元素(nums[i])
与前一个值(nums[i - 1])
是否相等res.add(new ArrayList(tempList))
的时机,是在每次循环中添加还是等达到某个条件是再添加List
,可以利用List
中的contains()
方法,但这是线性搜索,时间复杂度为O(n),不适合在递归中使用,所以不推荐