回溯法
回朔法的思想: 通过枚举法,对所有可能性进行遍历。 但是和枚举法不同的是回溯法不是一直遍历下去,而是在不满足条件是回退一步,去尝试其余的路,而回退的这一步就是回溯算法的关键。
因此回朔法可以简单的理解为: 走不通就退一步的枚举法。而这里回退点也叫做回朔点。
利用for循环和递归配合可以实现回朔: 当递归从递归出口出来之后,上一层的for循环就会继续执行,而for循环的继续执行就会给出当前节点下的下一条可行路径。然后再递归调用,就可以顺着这条从未走过的路径又向下走一步。
回溯模板 python
def dfs():
if 回朔点:# 递归出口,这条路走到底了
保存当前结果
return
for route in all_route_set : # 逐步选择当前节点下的所有可能route
if 剪枝条件:
剪枝前的操作
return # 不继续往下走了,退回上层,换个路走
# 当前路径可能是条可行路径
保存当前数据 # 向下走之前要记住已经走过这个节点了。例如push当前节点
dfs() # 递归调用,继续向下走一步
回朔清理 # 该节点下的所有路径都走完了,清理堆栈,准备下一个递归。例如pop当前节点
例题1 (力扣 39. 组合总和):
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
if len(candidates) == 0: return []
candidates = sorted(candidates) # 排序
# 回溯法
# candisates: 候选点列表, target:目标,begin:起始位置,path:当前走过的路径,res:结果列表
def dfs(candidates, target, begin, path, res):
path = path.copy() # python 传的参数是引用,所以重新复制一份
if target == 0: # 回溯点, 到达目标,该退出了
res.append(path) # 保存当前结果
return # 返回
for i in range(begin, len(candidates)): # 从当前点的候选点中按顺序依次选取一个点进行回溯
if target - candidates[i] < 0: # 如果此时已不可能在满足题意,则剪枝,返回
return
path.append(candidates[i]) # 回溯之前,记录当前点
dfs(candidates, target-candidates[i], i, path, res) # 回溯
# 注:此题数据可重复,所以回溯起点是i,否则为i+1
path.pop() # 回溯清理,弹出当前点
res = []
dfs(candidates, target, 0, [], res) # 回溯
return res
20.9.18 补充一道题目 (全排列问题)
[力扣] 47. 全排列 II 。给定一个可包含重复数字的序列,返回所有不重复的全排列。
示例:
输入: [1,1,2]
输出:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
这是一道很经典的回溯题,采用回溯加上剪枝,可以达到很高的效率。
执行用时:40 ms, 在所有 Python3 提交中击败了98.20%的用户
内存消耗:13.3 MB, 在所有 Python3 提交中击败了97.76%的用户
思路: 从给定列表中每次取一个值,添加到path中,然后再从剩下的值中取一个,直到达到回溯条件(path长度等于nums长度),然后回溯。
在这道题中剪枝那一步是必要的,可以排除重复结果。
class Solution:
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
n = len(nums)
if n==0: return [] # 空列表直接返回
nums.sort() # 排序
def dfs(nums, path, res):
if len(path) == n: # 回溯点
res.append(path[:]) # 添加到结果中,注意path和path[:]的群IE
return
for i in range(0, len(nums)):
if i>0 and nums[i]==nums[i-1]: # 剪枝,和上一次取值一样会造成重复,所以舍弃,重新取值
continue
path.append(nums[i]) # 添加点
dfs(nums[:i]+nums[i+1:], path, res) # 下一次选取值得nums是这一次旋球之后剩下的,也就是从nums中排除掉nums[i]
path.pop() # 回溯一步,弹出一个点
res = []
dfs(nums, [], res)
return res