回溯算法二三事

本文详细介绍了回溯算法的设计步骤,强调了与深度优先遍历的相似性,并讨论了在传递参数时的注意事项。作者提倡做题时先画树形图,以帮助理解和剪枝,还提供了LeetCode上的例题及其解决方案,涉及全排列、子集和目标和回溯问题。
摘要由CSDN通过智能技术生成

回溯算法二三事

设计回溯的基本步骤

回溯和深度优先遍历有相通之处

回溯时,需要用一个栈保存状态变量path。在往下一步时添加选择path.append();在往上一步(“回头”)时,需要撤销上一次的选择path.pop()

设计状态变量:其他节点/叶子节点/根节点

需要注意:

  1. 回溯和DFS过程中,状态变量path所指向的列表在深度优先遍历的过程中只有一份,DFS完成后会返回根节点变为空。因此如果在传递参数的过程中不创建新的变量(比如直接传递path),就要善用import copy//res.append(copy.deepcopy(path))或者res.append(path[:])

  2. 在python中[1,2,3]+[4]会创建一个新的列表对象,path[:]也是。如果在DFS传递参数的过程中创建新的变量,则不用考虑上一条。但是会多用很多的额外空间。

为什么不是广度优先遍历?

  1. 深度优先遍历和广度优先遍历都可以遍历所以状态空间,在这一点上,用广度优先遍历是没问题的
  2. 深度优先遍历在不同状态之间的切换很容易,可以使用函数调用的系统栈。但是广度优先遍历中,从浅层转到深层,状态的变化很大。得用队列+节点类

做题先画树形图

做题时建议先画树形图,想清楚:

  1. 分支如何产生
  2. 题目需要的解在哪里?是在叶子节点、还是在非叶子节点、还是在从根节点到叶子结点的路径
  3. 那些搜索会产生不需要的解?比如:产生重复的原因是什么?可以怎么剪枝?

参考资料和例题:46. 全排列 - 力扣(LeetCode)

更进一步!DFS传递参数问题:

leetcode46.全排列,可以不需要传递参数

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
        res = []
        path = []
        used = [False] * len(nums)
        
        def dfs():  # dfs中没有显示的return也没有关系
            # 根本不需要局部变量?因为每个时刻只会有一种状态,所以只需要维护一份状态变量
            # 题目需要的解
            if len(path) == len(nums):
                res.append(copy.deepcopy(path))

            for i in range(len(nums)):
                if used[i] is False:
                    path.append(nums[i])
                    used[i] = True
                    dfs()
                    path.pop()
                    used[i] = False
        
        dfs()

        return res

leetcode78.子集,由于DFS中不能修改循环使用的变量,所以必须传递一个参数i

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        res = []
        path = []
        used = [False] * len(nums)
        index = 0
        
        def dfs(index):
            # global index  # 声明全局变量index
            # 题目需要的解
            if len(path) <= len(nums):
                res.append(copy.deepcopy(path))

            # print('path: {}'.format(path))
            # print('index: {}'.format(index))
            for i in range(index, len(nums)): 
                 # 2.局部变量在定义前引用。不能在使用中修改变量index,就是此处的DFS必须传递一个参数index的原因
                if used[i] is False:
                    path.append(nums[i])
                    used[i] = True
                    # index = index + 1  # 1.对于全局变量,只能引用而不能修改。如果要修改则会创建新的局部变量
                    dfs(index = i)
                    path.pop()
                    used[i] = False
                    # index = index - 1

        # print('index: {}'.format(index))
        dfs(index)

        return res

leetcode494.目标和 回溯超时,倒着回溯用@cache当dp用空间换时间 具体原理见 python中的cache

# 模仿version dp+cache
class Solution:
    def findTargetSumWays(self, nums: List[int], target: int) -> int:
        @ cache  
        # 当dfs(index, res)对每一种(idnex,res)组合只会出现一种解,加上@cache之后会自动把算过的解存下来,之后要用到的时候首先去查表,没查到才会计算。是空间换时间的艺术。
        def dfs(index, res):
            if index == -1:  # 全部的数都用完了,需要出循环
                if res == 0:  # 值刚好相等
                    return 1  # 提供一种可能性
                return 0  # 值不等,不提供可能性

            return dfs(index-1, res-nums[index]) + dfs(index-1, res+nums[index])  # 分别对应nums[index]前为加号或者减号的情况

        return dfs(len(nums)-1, target)

# version 0,会超时
# 还要用dp?
# 用完表达式
# 当到叶子节点满足要求时,返回路径数。树的深度是nums的长度,每个节点有两个分叉+和-
# 剪枝:如果目前的值+之后的数[全负-全正] 的区间范围内没有target,这条路线就不用看了
class Solution:
    res = 0
    def findTargetSumWays(self, nums: List[int], target: int) -> int:
        
        # 特殊情况处理
        path = 0  # 当前的值
        depth = 0

        def dfs(path, depth):
            # print('path={}, depth={}'.format(path, depth))
            # 返回条件
            if depth == len(nums):
                # print('depth == len(nums)-1')
                if path == target:
                    # print('path == target')
                    self.res += 1
                    # print('self.res:{}'.format(self.res))
                return

            number = nums[depth]

            # 剪枝
            possible_max = path + sum(nums[depth:])
            possible_min = path - sum(nums[depth:])
            if target > possible_max or target < possible_min:
                # print('cut off')
                return

            for i in [1, -1]:
                path += i*number
                depth += 1
                # print('number={}, path={}, depth={}'.format(number, path, depth))
                dfs(path, depth)
                path -= i*number
                depth -= 1

        # print('START: path={}, depth={}'.format(path, depth))
        dfs(path, depth)

        return self.res
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值