回溯算法精解:排列、子集与组合

好的,我们来总结一下使用深度优先搜索(DFS)解决排列问题、子集问题和组合问题的核心思路与代码模板。这三类问题都是回溯算法的经典应用场景,本质都是通过递归遍历所有可能的解空间,但在具体实现和问题特性上有所区别。


一、排列问题(Permutations)

问题特征 :求一个集合中所有元素的全排列 ,顺序不同视为不同解。
核心思路 :每次递归选择一个未被使用的元素加入当前路径,直到路径长度等于原集合长度。
关键点 :需要标记元素是否已被使用(如 visited 数组)。

代码模板
python 复制代码
def permute(nums):
    res = []
    path = []
    visited = [False] * len(nums)  # 标记元素是否使用
    
    def dfs():
        if len(path) == len(nums):  # 终止条件:路径长度等于集合大小
            res.append(path.copy())
            return
        for i in range(len(nums)):
            if visited[i]: continue  # 跳过已使用的元素
            visited[i] = True
            path.append(nums[i])
            dfs()  # 递归下一层
            path.pop()  # 回溯
            visited[i] = False
    
    dfs()
    return res

二、子集问题(Subsets)

问题特征 :求一个集合的所有子集 (包括空集),顺序无关。
核心思路 :每个元素有"选"或"不选"两种状态,递归遍历所有选择分支。
关键点:无需考虑顺序,通常按索引顺序递归避免重复。

代码模板
python 复制代码
def subsets(nums):
    res = []
    path = []
    
    def dfs(start):  # start: 当前可选的起始索引
        res.append(path.copy())  # 所有路径都是子集
        for i in range(start, len(nums)):
            path.append(nums[i])
            dfs(i + 1)  # 下一层从 i+1 开始选
            path.pop()  # 回溯
    
    dfs(0)
    return res

三、组合问题(Combinations)

问题特征 :从集合中选取 k 个元素的所有组合 (顺序无关)。
核心思路 :在子集问题基础上增加长度限制(len(path) == k)。
关键点:可通过剪枝(如剩余元素不足时提前终止)优化效率。

代码模板
python 复制代码
def combine(n, k):
    res = []
    path = []
    
    def dfs(start):
        if len(path) == k:  # 终止条件:路径长度等于 k
            res.append(path.copy())
            return
        for i in range(start, n + 1):
            # 剪枝:剩余元素不足时跳过
            if n - i + 1 < k - len(path): 
                break
            path.append(i)
            dfs(i + 1)  # 下一层从 i+1 开始选
            path.pop()  # 回溯
    
    dfs(1)
    return res

四、问题对比与总结

问题类型 核心区别 终止条件 去重方式
排列 顺序敏感 len(path) == len(nums) visited 数组
子集 顺序无关 无固定终止(每一步都记录) 按索引顺序递归(start 参数)
组合 顺序无关 len(path) == k 按索引顺序递归(start 参数)

五、补充说明

  1. 剪枝优化 :在组合问题中,可通过判断剩余元素数量提前终止循环(如 n - i + 1 < k - len(path))。
  2. 去重处理 :若原集合包含重复元素(如 [1,1,2]),需在循环中跳过重复值(如 if i > start and nums[i] == nums[i-1]: continue)。
  3. 时间复杂度:排列(O(n!))、子集(O(2\^n))、组合(O(C_n\^k)),剪枝可显著减少实际递归次数。

通过灵活调整终止条件、元素选择范围和去重逻辑,可解决大多数变体问题(如重复元素、求和约束等)。

相关推荐
GuWenyue8 小时前
传统Agent工具两大痛点!300行代码落地MCP跨语言工具,彻底解耦LLM与工具
前端·人工智能·算法
想会飞的蒲公英9 小时前
计算机怎样读取中文文本:编码、清洗与标准化
人工智能·python·自然语言处理
我叫洋洋9 小时前
C ++ [ hello world ]
c语言·c++·算法
SeaTunnel10 小时前
从 Python Script 地狱到标准化数据集成框架
大数据·开发语言·python·程序员·代码·seatunnel
深度研习笔记10 小时前
OpenCV工业视觉实战09|项目EXE打包+工控机无环境部署+后台常驻运行,彻底脱离Python环境,完成项目最终交付
python·opencv·webpack
CCPC不拿奖不改名10 小时前
大模型推理架构与开源生态知识整理
数据库·windows·python·架构·langchain·开源·github
奋发向前wcx10 小时前
y1,y2总复习笔记5 2026.7.19
数据结构·笔记·算法
Marst Code11 小时前
(python)2026Plotly 库评估:交互式可视化到底值不值得引入?
开发语言·python
databook11 小时前
当散点图不够用时:用 t-SNE 可视化多维数据
python·数据分析·数据可视化
我的xiaodoujiao13 小时前
快速学习Python基础知识详细图文教程9--函数进阶
开发语言·python·学习·测试工具