解法一:回溯
因为集合中的元素不用考虑顺序,我们通过保证元素之间的相对顺序不变来防止出现重复的子集
例如,[1,2,3]
中 2
后面只有 3
,如果你添加了前面的 1
,那么后面就只能选择2
和3
,若选择 了2
,后面就只能选择3
,否则,如果出现[2,1]
,就会和之前已经生成的子集 [1,2]
重复了。
得到下面这棵决策树
这棵树的特性是:如果把根节点作为第 0 层,那么第
n
层的所有节点就是大小为 n
的所有子集。
题意要求得到所有子集,那只要遍历这棵多叉树,把所有节点的值加入结果即可
go
func subsets(nums []int) [][]int {
path := make([]int, 0)
var res [][]int
backtrack(nums, 0, path, &res)
return res
}
func backtrack(nums []int, offset int, path []int, res *[][]int) {
if len(path) <= len(nums) { // 每个节点的值都是一个子集
tmp := make([]int, len(path))
copy(tmp, path)
*res = append(*res, tmp)
}
for i := offset; i < len(nums); i++ { // 递归退出条件: offset == len(nums)
path = append(path, nums[i]) // 做选择
backtrack(nums, i+1, path, res) // 通过offset参数控制树枝的遍历,每次只能往后选择元素,避免产生重复的子集
path = path[:len(path)-1] // 撤销选择
}
}
扩展
组合问题和子集问题其实是等价的 ,给定一个数组,大小为 k
的组合就是大小为 k
的子集。
来看这道题:leetcode.cn/problems/co...
回想刚才的决策树,其实这个题就是找出第k层的所有节点,前面的子集代码稍作改动即可
go
func combine(n int, k int) [][]int {
nums := make([]int, 0, n)
for i:=1; i<=n; i++{
nums = append(nums, i)
}
path := make([]int, 0)
var res [][]int
backtrack(nums, 0, path, k, &res)
return res
}
func backtrack(nums []int, offset int, path []int, k int, res *[][]int){
if len(path) == k{ // 收集长度为k的子集
tmp := make([]int, len(path))
copy(tmp, path)
*res = append(*res, tmp)
return
}
for i:= offset; i<len(nums); i++{ // 通过offset避免重复选择元素
path = append(path, nums[i]) // 做选择
backtrack(nums, i+1, path, k, res)
path = path[:len(path)-1] // 撤销选择
}
}
可以再优化一下空间复杂度,不需要生成一个额外的数组nums
go
func combine(n int, k int) [][]int {
path := make([]int, 0)
var res [][]int
backtrack(n, 1, path,k, &res) // 范围是【1,n】
return res
}
func backtrack(n int, offset int, path []int, k int, res *[][]int) {
if len(path) == k{
*res = append(*res, append([]int{}, path...))
return
}
for i:=offset; i<=n; i++{
path = append(path, i)
backtrack(n, i+1, path, k, res)
path = path[:len(path)-1]
}
}