回溯
参考:https://zhuanlan.zhihu.com/p/302415065
关于回溯
回溯是递归的“副产品”,并非高效算法,通过剪枝可以跳过非必要的搜索。
回溯算法能解决如下问题:
- 组合问题:N个数里面按一定规则找出k个数的集合;
- 排列问题:N个数按一定规则全排列,有几种排列方式;
- 切割问题:一个字符串按一定规则有几种切割方式;
- 子集问题:一个N个数的集合里有多少符合条件的子集;
- 棋盘问题:N皇后,解数独等等。
注:「组合是不强调元素顺序的,排列是强调元素顺序」,即组合无序,排列有序。
回溯函数的伪代码模板
def backtracking(self, 参数列表):
# 定义递归出口
if 终止条件:
保存结果
return
for i in range(n):
处理
self.dfs(参数)
处理回退
以组合问题举例
一句话总结:for循环横向遍历,递归纵向遍历,回溯不断调整结果集。
例题:力扣77题:给定两个整数n和k,返回1...n中所有可能的k个数的组合。思路如下图:
代码如下:
class Solution(object):
def __init__(self):
self.res = []
def combine(self, n, k):
"""
:type n: int
:type k: int
:rtype: List[List[int]]
"""
nums = [i for i in range(1, n+1)]
sizeN = len(nums)
self.dfs(nums, sizeN, k, 0, [])
return self.res
def dfs(self, nums, sizeN, k, begin, temp):
# 定义递归出口:长度为k
if len(temp) == k:
self.res.append(temp)
for i in range(begin, sizeN):
if len(temp) > k:
break
# 递归
self.dfs(nums, sizeN, k, i+1, temp+[nums[i]])
上述例题是在一个集合中求组合,需要用到“begin”这个变量来控制for循环的起始位置。
那么对于组合问题,什么时候需要此变量呢?
如果是一个集合来求组合,就需要begin,例如:力扣39、40、77等题;
如果是多个集合取组合,各个集合之间相互不影响,那么就不需要begin,例如:力扣17题。
做题中总结的一个模板
class Solution():
# 返回值,定义为全局变量
def __init__(self):
self.res = []
def function(self, string):
n = len(string)
temp = []
visit = [0 for _ in range(n)]
self.dfs(string, temp, visit, n)
return self.res
def dfs(self, string, temp, visit, n):
# 定义递归出口
if len(temp) == len(string):
self.res.append("".join(temp))
else:
# for循环横向遍历
for i in range(n):
# 当且仅当当前字符未被访问
if not visit[i]:
# 用temp记录当前访问
temp.append(string[i])
visit[i] = 1
# 递归调用,纵向遍历
self.dfs(string, temp, visit, n)
# 回退
temp.pop(-1)
visit[i] = 0