本文是对 leetcode 回溯题的一些模板进行整理总结,很多关于回溯的 blog 都会引用对回溯算法的 official definition 和通用的解题步骤,如果是真的想研究这一算法思想,按照这样的方式来完全没有问题。不过个人觉得如果仅仅只是为了应试,那么掌握一些解题的模板会更直接的帮助理解回溯的算法思想。本文将举一些简单的例子来说明这些模板,不采用树来描述,使得对于数据结构不太了解的读者也相对友好。
文章目录
基本思想:
回溯问题是对多叉树的深度搜索,遇到不满足条件的节点则回退,递归的搜索答案。在递归调用前,尝试一种可能的方案,那么在递归调用的时候,函数的开始,有判断语句,如果这种方案可行,记录下这种方案,并且 return,否则,继续进行尝试,找到满足条件的解以后,回退到之前的选择。
常见模板:
1、无重复元素的全排列问题(或者有重复元素但是不需要去重)
一般在回溯的过程中,不断缩小原来数组的范围并添加至 t r a c k track track 中,直至枚举完所有的元素,满足条件的添加到 r e s u l t result result 数组中, 模板如下
def problem(nums):
res = []
def backtrack(nums, track):
if (判断满足题目所给的条件): # 如果不限制每个结果都需要用到所有元素,就不需要 if 判断,直接加入 res
res.append(track[:]) # 这里必须传入track的拷贝,track[:], 否则答案全是空
return
for i in range(len(nums)):
backtrack(nums[:i] + nums[i+1:], track + nums[i])
backtrack(nums, [])
return 题目需要的res相关的参数,输出本身,长度,或者其他的
以下题目为实战中套用框架解题
Leetcode 46 全排列
由于是全排列,只要没得选了,那就是我们所需的答案,加入 r e s u l t result result 并且 r e t u r n return return
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
res = []
def backtrack(nums, track):
if not nums:
res.append(track[:])
return
for i in range(len(nums)):
backtrack(nums[:i] + nums[i+1:], track + [nums[i]])
backtrack(nums, [])
return res
2、有重复元素的全排列问题
遇到有重复元素的问题,最好先进行排序,再采用剪枝的方法来进行去重,具体分析见 4。这里给出全排列有重复元素去重的框架:
def problem(nums):
res = []
nums.sort()
def backtrack(nums, track):
if (判断满足题目所给的条件): # 如果不限制每个结果都需要用到所有元素,就不需要 if 判断,直接加入 res
res.append(track[:]) # 这里必须传入track的拷贝,track[:], 否则答案全是空
return
for i in range(len(nums)):
if i > 0 and nums[i] == nums[i-1]: #剪枝去重
continue
backtrack(nums[:i] + nums[i+1:], track + nums[i])
backtrack(nums, [])
return 题目需要的res相关的参数,输出本身,长度,或者其他的
Leetcode 1079 活字印刷
先将字符串放在入列表中进行排序,后进行剪枝去重。
由于不需要求具体有哪些排列,因此只需要用一个变量来记录过程中的结果。类似的, N N N皇后与 N N N皇后Ⅱ 的差别也仅在于是否需要建立一个列表或者一个变量来保存结果。
初始 a n s ans ans 设为 -1,因为题目要求最后的结果非空,提前减去一个空字符串。
class Solution:
def numTilePossibilities(self, tiles: str) -> int:
self.ans = -1
tiles = list(tiles)
tiles.sort()
def backtrack(tiles):
self.ans += 1
for i in range(len(tiles))<