78. 子集
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
1 | 输入: nums = [1,2,3] |
话不多说,show you the code。
1 | class Solution: |
90. 子集 II
给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
1 | 输入: [1,2,2] |
- 本题和上一题的唯一区别就在于 nums会出现重复元素。
- 因此其实做法上只需要增加判断每次迭代或者递归,新的子集元素是否存在于原子集中即可
- 但是由于解集不能包含重复的子集(相同元素的不同序列,如[1,2,2],[2,1,2]),因此在迭代之前必须对nums进行 排序 操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38class Solution:
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
# 方法一、递归解法
if not nums:
return []
n = len(nums)
res = []
nums.sort()
def help(idx,tmp_list):
# 判断tmp_list不在原子集才加入
if tmp_list not in res:
res.append(tmp_list)
for i in range(idx,n):
help(i+1,tmp_list+[nums[i]])
help(0,[])
return res
# 方法二、列表推导式法
res=[[]]
for i in sorted(nums):
# 判断[i]+j不在原子集才加入
res+=[[i]+j for j in res if [i]+j not in res]
return res
# 方法三、迭代法
res=[]
nums.sort()
for i in nums:
# 每遇到一个元素
for j in range(len(res)):
# 判断res[j]+[i]不在原子集才加入
if res[j]+[i] not in res:
# 就在现有子集元素的基础上加上该元素,组成新的子集
res.append(res[j]+[i])
if [i] not in res:
res.append([i])
# 最后补上一个空集
res.append([])
return res
39. 组合总和
给定一个无重复元素的数组candidates 和一个目标数 target,找出candidates中所有可以使数字和为target的组合。
candidates 中的数字可以无限制重复被选取。
说明:
- 所有数字(包括 target )都是正整数。
- 解集不能包含重复的组合。
示例 1:
1 | 输入: candidates = [2,3,6,7], target = 7, |
示例 2:
1 | 输入: candidates = [2,3,5], target = 8, |
本题的总体思路是回溯+剪枝
- 以 target = 7 为根结点,每一个分支做减法。
- 减到 0 或者负数的时候,剪枝。其中,减到 0 的时候结算,这里 “结算” 的意思是添加到结果集。
上图说明:
一个蓝色正方形表示的是 “尝试将这个数到数组 candidates 中找组合”,那么怎么找呢?挨个减掉那些数就可以了。
在减的过程中,会得到 0 和负数,也就是被我标红色和粉色的结点:
- 得到 0 是我们喜欢的,从 0 这一点向根结点走的路径(很可能只走过一条边,也算一个路径),就是一个组合,在这一点要做一次结算(把根结点到 0 所经过的路径,加入结果集)。
- 得到负数就说明这条路走不通,没有必要再走下去了。
总结一下:在减的过程中,得到 0 或者负数,就没有必要再走下去,所以这两种情况就分别表示成为叶子结点。此时递归结束,然后要发生回溯。
1 | class Solution: |
40. 组合总和 II
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。
示例 1:
1 | 输入: candidates = [10,1,2,7,6,1,5], target = 8, |
示例 2:
1 | 输入: candidates = [2,5,2,1,2], target = 5, |
思路
- 本题和上题的区别在于candidates中的元素不可重复使用
- 因此只需要在递归的时候每层dfs从下一个元素开始即可
- 因为本身candidates中有重复元素,因此path在加入res之前需要查重
话不多说,show you the code
1 | class Solution: |
216. 组合总和 III
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
- 所有数字都是正整数。
- 解集不能包含重复的组合。
示例 1:1
2输入: k = 3, n = 7
输出: [[1,2,4]]
示例 2:
1 | 输入: k = 3, n = 9 |
思路
- 本题和前两题的区别在于candidates变成了[1,2,3,4,5,6,7,8,9],且path长度只能是k,candidates不可重复使用
- 因此只需要在递归的时候每层dfs从下一个元素开始即可,并且在加入path之前,确认path的长度是否为k
话不多说,show you the code
1 | class Solution: |
46. 全排列
给定一个没有重复数字的序列,返回其所有可能的全排列。
示例:
1 | 输入: [1,2,3] |
思路
- 回溯,详情见代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22# 方法一
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
if not nums: return []
res=[]
n=len(nums)
# 使用vis数组保存访问过的元素
vis=[0]*n
def backtrack(tmp_list):
if len(tmp_list)==n:
res.append(tmp_list)
for i in range(n):
# 访问过的直接跳过
if vis[i]:
continue
# 状态置为已访问,并将tmp_list加上当前元素继续递归
vis[i]=1
backtrack(tmp_list+[nums[i]])
# 将访问状态初始化,才不会影响别的路径
vis[i]=0
backtrack([])
return res
1 | # 方法二 |
47. 全排列 II
给定一个可包含重复数字的序列,返回所有不重复的全排列。
示例:
1 | 输入: [1,1,2] |
思路:
- 本题和上题的区别在于nums会出现重复元素,这个导致全排列子集需要去重
- 因此本题的难点在于如何去重剪枝
1 | # 方法一、朴素思想 |
1 | # 方法二、记录已访问元素 |
- 本文作者: Jason
- 本文链接: https://caicaijason.github.io/2019/11/20/回溯系列详解/
- 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!