下面给出一篇详细解析该题 DFS 解法的博客文章,希望能帮助大家更好地理解这个算法的思路。
【算法解析】子集问题 —— DFS 递归解法详解
在本篇文章中,我们将讨论如何利用深度优先搜索(DFS)来生成一个数组的所有子集(即幂集)。题目要求给定一个互不相同的整数数组 nums
,返回该数组所有可能的子集。
问题描述
给定一个数组 nums
,其中的所有元素都互不相同。要求返回 nums
的所有可能子集,注意解集不能包含重复的子集,且可以按任意顺序返回。
例如:
- 输入:
nums = [1,2,3]
- 输出:[[], [1], [2], [1,2], [3], [1,3], [2,3], [1,2,3]]
算法思路
本题可以采用 DFS 的递归方法来遍历所有可能的子集。具体来说,我们可以使用回溯算法,在递归的过程中维护一个「路径」(即当前选取的子集元素),当遍历到数组末尾时,将当前路径加入答案集合中。每一层递归都有两种选择:选取当前数字或者跳过当前数字。
代码中对数组进行了排序,其实这一步主要在有重复数字的情况下有用(通过跳过重复数字可以避免重复解),但对于本题中“互不相同”的数组来说,排序并不是必需的。不过为了通用性以及代码的一致性,这里依然进行了排序。
代码实现
下面是代码实现(Python 版本):
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
# 排序(主要是为处理重复元素,如果已知元素互不相同,这步可以省略)
nums.sort()
path = [] # 当前子集
ans = [] # 存储所有子集
n = len(nums)
def dfs(i: int) -> None:
# 当索引越界,说明已经考虑完所有数字,将当前路径加入答案中
if i == n:
ans.append(path.copy())
return
# 分支1:选择当前数字
x = nums[i]
path.append(x)
dfs(i + 1)
path.pop()
# 分支2:不选择当前数字(跳过所有与当前数字相同的元素)
# 因为本题数字互不相同,这里的 while 循环其实不会跳过多个元素
i = i + 1
while i < n and nums[i] == x:
i += 1
dfs(i)
# 从索引0开始搜索所有子集
dfs(0)
return ans
代码解析
1. 排序与初始化
nums.sort()
path = [] # 用于记录当前选择的子集
ans = [] # 用于存放所有生成的子集
n = len(nums)
对数组进行排序可以方便后续跳过重复数字的操作。这里初始化了两个列表:
path
:用于存储当前递归路径上选取的数字,也就是当前生成的子集;ans
:最终存储所有满足条件的子集。
2. 递归函数 dfs
递归函数 dfs(i: int)
中的参数 i
表示当前处理到数组的第 i
个元素。
终止条件
if i == n:
ans.append(path.copy())
return
当索引 i
到达数组末尾时,说明当前递归路径已经处理完所有数字,将当前路径(子集)加入答案 ans
中。
分支1:选择当前数字
x = nums[i]
path.append(x)
dfs(i + 1)
path.pop()
首先选择当前数字 x
并将其加入路径中,然后递归调用 dfs(i + 1)
考虑后续数字。递归结束后通过 path.pop()
回溯,撤销之前的选择,保证路径状态正确。
分支2:不选择当前数字
i = i + 1
while i < n and nums[i] == x:
i += 1
dfs(i)
在回溯后,不选择当前数字 x
。这里先将 i
增加1,然后利用 while
循环跳过所有与 x
相同的数字。虽然题目保证数字互不相同,但这个逻辑可以应对有重复数字的情况,从而避免生成重复子集。最后,递归调用 dfs(i)
继续搜索后续数字。
3. 开始递归搜索
最后,通过调用 dfs(0)
从索引 0 开始搜索所有子集,并返回答案 ans
。
总结
本题利用 DFS 递归和回溯的方法,枚举了所有可能的子集。主要思路就是对每个数字都有「选」和「不选」两种选择,通过递归来构造出所有可能的组合。对于处理重复数字的情况,通过在「不选」分支中跳过相邻重复数字可以避免生成重复子集。尽管本题中数字互不相同,但这种写法具有更好的通用性。
希望这篇文章能够帮助你深入理解 DFS 在子集问题中的应用!如果你有任何疑问或建议,欢迎在评论区讨论。