【重点】【DFS】90.子集II

文章介绍了如何使用深度优先搜索(DFS)算法解决Java编程中的经典问题——给定一个整数数组,找出所有可能的子集,包括重复元素。通过递归实现并利用排序优化处理相等元素

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目
此题是元素存在重复的情况,可对比不存在元素重复情况的题目:78.子集

法1:DFS

经典方法,必须掌握!!!

Python

class Solution:
    def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
        nums.sort()
        res, path = [], []
        self.dfs(nums, 0, res, path)

        return res
    
    def dfs(self, nums, start, res, path):
        if start == len(nums):
            res.append(path.copy())
            return

        path.append(nums[start]) # 选择nums[start]
        self.dfs(nums, start+1, res, path)
        path.pop()
        while start + 1 < len(nums) and nums[start+1] == nums[start]:
            start += 1
        self.dfs(nums, start+1, res, path)

Java

class Solution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> tmp = new ArrayList<>();
        Arrays.sort(nums);
        dfs(nums, 0, res, tmp);

        return res;
    }

    public void dfs(int[] nums, int startIndex, List<List<Integer>> res, List<Integer> tmp) {
        if (startIndex == nums.length) {
            res.add(new ArrayList<>(tmp));
            return;
        }

        tmp.add(nums[startIndex]); // 选择startIndex
        dfs(nums, startIndex + 1, res, tmp);
        tmp.remove(tmp.size() - 1); // 不选startIndex
        while (startIndex + 1 < nums.length && nums[startIndex + 1] == nums[startIndex]) { // 如果去掉这个条件,则可解决【78.子集】
            ++startIndex;
        }
        dfs(nums, startIndex + 1, res, tmp);
    }
}
<think>嗯,用户问的是DFS和回溯法的区别。这个问题我得仔细想想,不能混淆了。首先,得明确两者的定义和应用场景。DFS,也就是深度优先搜索,是一种遍历或搜索树或图的算法,它沿着分支一直深入到底,再回溯到上一个节点继续探索。而回溯法则是一种通过试错来寻找问题解决方法的算法,当发现当前路径不能得到有效解时,会退回上一步重新选择。 那它们的区别到底在哪里呢?可能用户在学习算法时,对这两个概念容易混淆,尤其是在解决类似的问题,比如排列组合、迷宫问题时,两者都有应用。需要从不同的角度来区分,比如处理问题的类型、实现方式、剪枝的运用等方面。 首先,DFS主要是一种遍历方式,用于系统地访问所有节点,而回溯法则更侧重于通过递归试探和回退来寻找可行解或最优解。回溯法通常会利用DFS作为其遍历结构,但加入了剪枝等优化手段,提前排除不可能的情况,减少搜索空间。 举个例子,比如在解决八皇后问题时,使用回溯法,每次放置一个皇后后,检查是否冲突,如果冲突则不再继续往下搜索,而是回退到上一步。而DFS在这里作为遍历的可能路径的方法,但回溯法加入了条件判断,及时剪枝。 另外,DFS的应用范围更广,比如图的连通性检测、拓扑排序等,而回溯法则更多用于组合优化、决策问题,如数独、排列组合等。需要说明回溯法通常用递归实现,而DFS可以用递归或者栈来实现,但回溯法在递归返回时,会撤销之前的状态,也就是“回溯”操作。 可能用户在学习的时候,看到回溯法经常用递归和DFS的结构,所以会产生混淆。这时候需要指出回溯法是在DFS的基础上,增加了状态管理和剪枝,因此可以说回溯法是DFS的一种应用,但更强调试错和回退的过程。 需要确保回答结构清晰,先分别介绍两者的定义,然后对比不同点,最后举例说明。这样用户能逐步理解两者的区别,避免混淆。同时要注意用中文,使用正确的数学格式,比如变量用$...$包裹,独立公式单独成段用$$...$$,但用户的问题中没有涉及数学表达式,所以可能不需要用到这些。不过按照系统级指令,如果有的话要遵守。 总结下来,DFS是基础的搜索策略,而回溯法是基于DFS的一种优化算法,通过剪枝和状态回退提高效率。两者的核心区别在于目的和应用方式,回溯法更关注于解决决策问题中的试错过程,而DFS更关注于遍历或搜索整个结构。</think>DFS(深度优先搜索)和回溯法都是算法设计中常用的策略,两者有联系但也有本质区别。以下是逐步对比和分析: --- ### **1. 核心定义不同** - **DFS(深度优先搜索)** 一种**遍历或搜索树/图结构的算法**,从根节点出发,尽可能深地探索分支,直到无法继续(叶子节点或已访问节点),再回溯到上一个未探索的节点继续搜索。 **目标**:系统地访问所有节点或找到一条路径。 - **回溯法(Backtracking)** 一种**解决问题的算法思想**,通过递归尝试所有可能的候选解,若发现当前路径无法得到有效解,则撤销(回溯)上一步或几步的选择,重新尝试其他分支。 **目标**:寻找满足约束条件的可行解或最优解。 --- ### **2. 应用场景不同** - **DFS** 用于需**遍历或搜索完整结构**的问题,如: - 图的连通性检测 - 拓扑排序 - 二叉树的前/中/后序遍历 - **回溯法** 用于需**穷举可能性并剪枝**的组合优化问题,如: - 排列组合(全排列、子集) - 八皇后问题 - 数独求解 - 迷宫路径搜索 --- ### **3. 实现方式与关键区别** | **特性** | **DFS** | **回溯法** | |------------------|----------------------------------|--------------------------------| | **核心逻辑** | 单纯遍历,不主动回退 | 通过试错主动回退 | | **状态管理** | 通常不修改已访问状态 | 记录并修改路径状态(需撤销) | | **剪枝** | 不一定使用剪枝 | 必须剪枝以优化搜索空间 | | **递归终止条件** | 到达叶子节点或完成遍历 | 找到解或确认无解 | --- ### **4. 举例说明** #### **DFS示例:图的遍历** ```python def dfs(node): if node is visited: return visit(node) # 访问节点 for neighbor in node.neighbors: dfs(neighbor) # 递归深入 ``` **目标**:访问所有节点。 --- #### **回溯法示例:全排列问题** ```python def backtrack(path, choices): if 满足终止条件(如路径长度等于数组长度): 记录结果 return for choice in choices: if 选择无效(如元素已用): continue # 剪枝 做选择(将choice加入path) backtrack(path, 新choices) 撤销选择(从path移除choice) # 关键:回溯操作 ``` **目标**:生成所有排列,通过剪枝跳过无效路径。 --- ### **5. 总结区别** | **维度** | **DFS** | **回溯法** | |-------------|-----------------------------|------------------------------| | **本质** | 搜索算法 | 算法设计思想(基于DFS实现) | | **侧重点** | 遍历结构 | 通过试错和剪枝寻找解 | | **回退操作** | 自然回溯(递归栈回退) | 显式撤销选择(主动回溯) | | **优化手段** | 无剪枝 | 必须剪枝 | --- ### **关键结论** 回溯法可视为**DFS的增强版**: - 在DFS框架中增加了**状态管理**(记录和撤销选择)与**剪枝**(提前终止无效分支)。 - 回溯法更关注**问题的解空间**,而DFS更关注**结构的遍历**。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值