回溯算法学习

一、基本理解

回溯算法的基本思想是:以深度优先搜索的方式,根据产生子节点的条件约束,搜索问题的解。当发现当前节点已不满足求解条件时,就「回溯」返回,尝试其他的路径。

二、三步走方法

res = []    # 存放所欲符合条件结果的集合
path = []   # 存放当前符合条件的结果
def backtracking(nums):             # nums 为选择元素列表
    if 遇到边界条件:                  # 说明找到了一组符合条件的结果
        res.append(path[:])         # 将当前符合条件的结果放入集合中
        return

    for i in range(len(nums)):      # 枚举可选元素列表
        path.append(nums[i])        # 选择元素
        backtracking(nums)          # 递归搜索
        path.pop()                  # 撤销选择

backtracking(nums)
     一、明确所有选择
  1. 画出决策树:决策树可以帮助理清搜索过程,确定搜索范围和路径。
  2. 确定搜索路径:根据决策树明确在不同阶段的所有可能选择。

     二、明确终止条件

  1. 确定终止位置:回溯算法的终止条件通常是决策树的底层,即达到无法再做选择的状态。
  2. 处理终止情况:明确在终止条件下的处理方法,如输出答案、将当前符合条件的结果放入集合等。

     三、将决策树和终止条件翻译成代码

  1. 定义回溯函数:
    • 明确函数意义:理解该函数在整个回溯过程中的作用。
    • 确定传入参数和全局变量:由递归搜索阶段的 “当前状态” 决定,最好能直接记录 “当前状态”,如全排列中,backtracking(nums)函数的参数nums表示当前可选元素,全局变量respath分别用于保存所有符合条件的结果和当前符合条件的结果。
    • 确定返回结果:一般是单个节点或数值,告知上一层函数当前搜索结果,也可使用全局变量保存 “当前状态” 而不返回结果。
  2. 书写回溯函数主体:
    • 枚举可选元素列表:通过循环遍历所有可能的选择。
    • 约束条件判断:根据给定的约束条件筛选合法的选择。
    • 选择元素:将满足条件的元素加入表示当前状态的变量中。
    • 递归搜索:调用自身继续探索下一层。
    • 撤销选择:在回溯时,将之前的选择撤销,尝试其他可能。
  3. 明确递归终止条件:
    • 将终止条件转换为代码中的条件语句。
    • 执行终止条件下的处理方法,如将结果存入集合等。

三、应用

1.全排列 

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

  1. 明确所有选择:
    • 遍历输入的数组 nums,对于每个元素,如果它不在当前路径 path 中,就可以作为一个选择。
    • 这确定了从数组中选择未被使用过的元素加入路径的所有可能性。
  2. 明确终止条件:
    • 当 path 的长度与输入数组 nums 的长度相等时,表明找到了一个全排列,将其添加到结果列表 res 中并返回。
  3. 将决策树和终止条件翻译成代码:
    • 定义回溯函数 backtracking,不需要传入结果集和当前路径,直接使用外部函数中的变量 res 和 path
    • 在回溯函数主体中,通过循环遍历所有选择,判断元素是否在路径中,满足条件则进行选择、递归搜索和撤销选择的操作。
    • 明确递归终止条件,当 path 长度满足要求时,将其加入结果集。
def permute(nums):
    res = []
    path = []
    def backtracking(nums):
        # 明确终止条件
        if len(path) == len(nums):
            res.append(path[:])
            return
        # 明确所有选择
        for num in nums:
            if num not in path:
                # 选择元素
                path.append(num)
                # 递归搜索
                backtracking(nums)
                # 撤销选择
                path.pop()
    backtracking(nums)
    return res

 2.全排列Ⅱ

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

def permuteUnique(nums):
    nums.sort()
    res = []
    path = []
    used = [False] * len(nums)

    def backtracking():
        if len(path) == len(nums):
            res.append(path[:])
            return
        for i in range(len(nums)):
            if used[i] or (i > 0 and nums[i] == nums[i - 1] and not used[i - 1]):
                continue
            path.append(nums[i])
            used[i] = True
            backtracking()
            used[i] = False
            path.pop()

    backtracking()
    return res

3.括号生成 

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。 

分析:

一、明确所有选择

  1. 有两种选择,添加左括号 “(” 或者添加右括号 “)”。
  2. 但添加括号时有约束条件,不能无限制地添加,要保证生成的括号组合是有效的。

二、明确终止条件

当左括号和右括号的数量都等于给定的对数 n 时,说明找到了一个有效的括号组合,将其加入结果列表 res

三、将决策树和终止条件翻译成代码

  1. 定义回溯函数 backtracking,传入当前已使用的左括号数量 left、右括号数量 right 和当前生成的括号组合 path
  2. 在回溯函数主体中,如果左括号数量小于 n,可以添加左括号,递归调用函数继续生成。如果右括号数量小于左括号数量,可以添加右括号,递归调用函数。
  3. 明确递归终止条件,当左右括号数量都等于 n 时,将当前括号组合加入结果列表。
def generateParenthesis(n):
    res = []
    def backtracking(left, right, path):
        if left == n and right == n:
            res.append(path)
            return
        if left < n:
            backtracking(left + 1, right, path + "(")
        if right < left:
            backtracking(left, right + 1, path + ")")
    backtracking(0, 0, "")
    return res

4.电话按键字母组合 

一、明确所有选择

  1. 对于输入的数字字符串中的每个数字,都有对应的一组字母可供选择。
  2. 遍历每个数字对应的字母集合,确定所有可能的选择。

二、明确终止条件

当处理完数字字符串的最后一个数字,即索引达到字符串的长度时,说明找到了一个字母组合,将其加入结果列表 res

三、将决策树和终止条件翻译成代码

  1. 定义回溯函数 backtracking,传入当前处理的数字字符串的索引。
  2. 在回溯函数主体中,如果当前索引等于数字字符串的长度,说明已经生成了一个完整的字母组合,将其加入结果列表。否则,获取当前数字对应的字母集合,遍历该集合,将每个字母加入当前路径,然后递归调用函数处理下一个数字。递归调用结束后,将当前加入的字母从路径中移除,进行回溯。
def letterCombinations(digits):
    if not digits:
        return []
    mapping = {
        '2': 'abc',
        '3': 'def',
        '4': 'ghi',
        '5': 'jkl',
        '6': 'mno',
        '7': 'pqrs',
        '8': 'tuv',
        '9': 'wxyz'
    }
    res = []
    path = ""

    def backtracking(index):
        if index == len(digits):
            res.append(path)
            return
        letters = mapping[digits[index]]
        for letter in letters:
            path += letter
            backtracking(index + 1)
            path = path[:-1]

    backtracking(0)
    return res

5.组合总和 

def combinationSum(candidates, target):
    res = []
    path = []

    def backtracking(target, start):
        if target == 0:
            res.append(path[:])
            return
        if target < 0:
            return
        for i in range(start, len(candidates)):
            path.append(candidates[i])
            backtracking(target - candidates[i], i)
            path.pop()

    backtracking(target, 0)
    return res

6.子集 

def subsets(nums):
    res = []
    path = []

    def backtracking(start):
        res.append(path[:])
        for i in range(start, len(nums)):
            path.append(nums[i])
            backtracking(i + 1)
            path.pop()

    backtracking(0)
    return res

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值