LeetCode-Py回溯算法框架:子集/排列/组合问题的通用解法

LeetCode-Py回溯算法框架:子集/排列/组合问题的通用解法

【免费下载链接】LeetCode-Py ⛽️「算法通关手册」:超详细的「算法与数据结构」基础讲解教程,从零基础开始学习算法知识,800+ 道「LeetCode 题目」详细解析,200 道「大厂面试热门题目」。 【免费下载链接】LeetCode-Py 项目地址: https://gitcode.com/gh_mirrors/le/LeetCode-Py

回溯算法(Backtracking)是一种系统地搜索所有可能解的算法,通过递归和试错的方式逐步构建解的过程。当发现当前路径无法满足题目要求或无法得到有效解时,算法会撤销上一步的选择(即「回溯」),返回到上一个决策点,尝试其他可能的路径。回溯法的核心思想是「走不通就退回,换条路再试」,而每次需要回退的节点称为「回溯点」。详细理论基础可参考docs/07_algorithm/07_04_backtracking_algorithm.md

回溯算法通用框架

回溯算法的核心流程可概括为「选择-递归-回溯」三步,其通用模板如下:

res = []    # 存放所有符合条件结果的集合
path = []   # 存放当前递归路径下的结果

def backtracking(参数):
    if 满足结束条件:  # 例如:路径长度达到目标或遍历完所有元素
        res.append(path[:])  # 拷贝当前路径到结果集
        return
    
    for 选择 in 可选列表:
        if 剪枝条件:  # 例如:元素已使用或不符合约束
            continue
            
        path.append(选择)      # 做出选择
        backtracking(新参数)   # 递归探索下一层
        path.pop()            # 撤销选择,回溯到上一步

该框架在docs/07_algorithm/07_04_backtracking_algorithm.md中有完整实现,通过调整参数和剪枝条件可解决各类组合优化问题。

子集问题解法

子集问题要求找出数组中所有可能的不重复子集,如输入[1,2,3]需返回[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]。这类问题的关键是避免重复选择,可通过索引控制实现。

算法实现

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        res = []   # 存储所有子集结果
        path = []  # 存储当前子集路径
        
        def backtracking(index: int):
            res.append(path[:])  # 记录当前路径(包含空集)
            if index >= len(nums):
                return
                
            for i in range(index, len(nums)):
                path.append(nums[i])        # 选择当前元素
                backtracking(i + 1)         # 递归选择下一个元素
                path.pop()                  # 撤销选择
                
        backtracking(0)
        return res

核心要点

  1. 索引控制去重:通过index参数确保每次递归只选择后续元素,避免[1,2][2,1]这样的重复子集
  2. 结果收集时机:进入回溯函数即收集当前路径,确保空集和所有中间状态都被记录
  3. 剪枝优化:无需额外剪枝,索引机制已天然避免重复

完整解题思路可参考docs/07_algorithm/07_04_backtracking_algorithm.md中的子集问题分析。

排列问题解法

排列问题与子集问题的主要区别在于元素顺序,如[1,2][2,1]是不同排列。因此需要额外记录已使用元素,防止重复选择。

算法实现

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        res = []    # 存储所有排列结果
        path = []   # 存储当前排列路径
        
        def backtracking():
            if len(path) == len(nums):
                res.append(path[:])
                return
                
            for i in range(len(nums)):
                if nums[i] in path:  # 剪枝:跳过已使用元素
                    continue
                    
                path.append(nums[i])  # 选择当前元素
                backtracking()        # 递归选择下一个元素
                path.pop()            # 撤销选择
                
        backtracking()
        return res

核心要点

  1. 使用标记去重:通过nums[i] in path判断元素是否已使用,也可使用布尔数组提高效率
  2. 结果收集时机:仅当路径长度等于数组长度时才收集结果
  3. 决策树遍历:每个节点需要尝试所有未使用元素,生成全排列

全排列问题的决策树结构可参考docs/07_algorithm/07_04_backtracking_algorithm.md中的图示说明。

组合问题解法

组合问题通常要求从数组中选取特定数量的元素,如「从[1,2,3,4]中选2个元素的所有组合」。这类问题需要结合数量限制和索引控制。

算法实现

class Solution:
    def combine(self, n: int, k: int) -> List[List[int]]:
        res = []    # 存储所有组合结果
        path = []   # 存储当前组合路径
        
        def backtracking(start: int):
            if len(path) == k:
                res.append(path[:])
                return
                
            # 剪枝:剩余元素不足时停止
            for i in range(start, n - (k - len(path)) + 2):
                path.append(i)        # 选择当前数字
                backtracking(i + 1)   # 递归选择下一个数字
                path.pop()            # 撤销选择
                
        backtracking(1)
        return res

核心要点

  1. 双重剪枝优化

    • 索引控制:start参数确保元素递增选择
    • 数量控制:n - (k - len(path)) + 2确保剩余元素足够组成k个元素
  2. 终止条件:当路径长度等于k时收集结果

组合问题的更多变种可参考docs/solutions/0001-0099/combination-sum.md中的组合总和问题。

三类问题对比与优化

问题类型核心特点去重方式结果收集时机典型题目
子集元素无序,不重复选取索引控制进入回溯即收集78. 子集
排列元素有序,不重复选取标记数组/集合路径长度达标时46. 全排列
组合元素无序,固定数量索引控制+数量剪枝路径长度达标时39. 组合总和

通用优化技巧

  1. 剪枝策略

    • 提前终止:当剩余元素不足时停止递归
    • 排序去重:对数组排序后跳过相同元素(如含重复元素的子集问题)
    • 条件过滤:根据问题约束提前排除无效路径
  2. 空间优化

    • 使用全局变量代替参数传递
    • 结果集使用引用传递而非值传递
  3. 时间优化

    • 使用布尔数组代替in操作判断元素是否使用
    • 预计算边界条件减少循环次数

实战应用与练习

回溯算法在LeetCode中有大量应用,以下是推荐练习的典型题目:

  1. 基础入门

  2. 进阶挑战

  3. 面试高频

完整题目列表可参考docs/00_preface/00_06_categories_list.md#回溯算法题目

总结

回溯算法是解决排列、组合、子集等枚举类问题的通用方法,其核心在于通过「选择-递归-回溯」的流程遍历决策树。掌握以下关键点可有效提升解题能力:

  1. 问题建模:将实际问题转化为决策树结构
  2. 状态表示:设计合适的路径和结果集存储方式
  3. 剪枝优化:根据问题特点设计高效剪枝条件
  4. 去重策略:灵活运用索引控制、标记数组等方式避免重复解

通过本文介绍的通用框架和三类问题的具体实现,可应对大多数回溯算法题目。建议结合推荐练习题目深入理解不同场景下的变形应用,逐步掌握回溯算法的精髓。

【免费下载链接】LeetCode-Py ⛽️「算法通关手册」:超详细的「算法与数据结构」基础讲解教程,从零基础开始学习算法知识,800+ 道「LeetCode 题目」详细解析,200 道「大厂面试热门题目」。 【免费下载链接】LeetCode-Py 项目地址: https://gitcode.com/gh_mirrors/le/LeetCode-Py

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值