啥时候用
递归的后部分,实际上是纯暴力算法。
解决:组合,切割(回文数),子集,排列,棋盘(N皇后,解数独)问题。
如何理解
抽象为一个树形结构,宽度用for循环,深度用递归。
例题
1.机器人走棋盘
地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,
每一次只能向左,右,上,下四个方向移动一格,
但是不能进入行坐标和列坐标的数位之和大于k的格子。
例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
分析
看着像二位数组
机器人坐标是(x,y) and sum( int( str(x)))+sum(int(str(y)))<=k
条件是 行坐标各个数位和加列坐标各个数位和小等于K
且机器人前行,与八皇后相似,需要回溯算法
代码
class Solution:
def movingCount(self, threshold, rows, cols):
#棋盘问题,先产生0矩阵
board=[[0 for i in range(cols)] for j in range(rows)]
#注意这里二维矩阵的表示方法
global acc
acc = 0
#计数器 记录步数
#判断条件
def block(r,c):
s=sum(map(int,str(r)+str(c)))
#注意这里的计算各个数位之和的方式
return s>threshold
#只返回不满足条件的方便判断
#回溯遍历
def traverse(r,c):
global acc
if not (0<=r<rows and 0<=c<cols): # 超出范围跳出
return
if board[r][c]!=0: # 不等于0说明点不可使用 跳出
return
if board[r][c]==-1 or block(r,c):
board[r][c]=-1 #不符合条件的点记录-1
return
board[r][c]=1 #符合规定的点记录1,并计数加一
acc+=1
traverse(r+1,c)
traverse(r-1,c)
traverse(r,c+1)
traverse(r,c-1)
traverse(0,0)
return acc o = Solution()
print(o.movingCount(4 ,3 ,3)) # 输出结果:9
总结
模板
def 函数(参数):
global 计数器
if 不符合的条件:
return
符合的条件计数加一/按照题意计数
函数(所有可能的路线)
函数(初始点)
碎碎念
本质上来讲,回溯更像是高级的遍历,在不停的试错过程中找到最优解。
除了题中所给条件外,还要注意隐藏条件,比如超出棋盘的范围。
在棋盘行走的问题中,大多都是通过在棋盘上标记0或1来达到记录路径排除干扰路径的目的。
2.电话号码的数字组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
![](https://i-blog.csdnimg.cn/blog_migrate/1233af2130e5f3435a8d03368ffe4d2a.png)
分析
首先理解题意,所有能表示的字母组合,也就是说,把24个字母分成了8堆(其中除了7和9有四个字母,其余全是3个字母),从每个堆中任选其一进行组合,问有多少种组合方式。
回溯算法的子集问题。
思路很简单,就是按照顺序枚举每一个数字对应的字母组合在一起。使用额外的函数 combination()将已产生的字母序列和剩下数字对应的字母结合起来。
![](https://i-blog.csdnimg.cn/blog_migrate/1f9f6048260704db50744aca6156940e.gif)
代码
from typing import List
class Solution:
def letterCombinations(self, digits: str) -> List[str]:
if len(digits) == 0:
return []
#定义数字和字母的对应关系
num_to_letter = {
"2":"abc",
"3":"def",
"4":"ghi",
"5":"jkl",
"6":"mno",
"7":"pqrs",
"8":"tuv",
"9":"wxyz"
}
#用来保存最终的返回结果
final_result = []
#用来保存临时的组合结果
le_com = []
#利用回溯法来穷举
def backtrack(index):
#当递归完最后一个字符之后就拼接字符
if index == len(digits):
final_result.append("".join(le_com))
else:
#获取当前应该遍历的数字
digit_s = digits[index]
#遍历数字对应的字符串
for s in num_to_letter[digit_s]:
le_com.append(s)
backtrack(index+1)
le_com.pop()
#开始进行递归
backtrack(0)
return final_result
solution = Solution()
print(solution.letterCombinations("234"))
总结:回溯法三部曲
递归函数的返回值以及参数
在这里要定义两个全局变量,一个用来存放符合条件单一结果,一个用来存放符合条件结果的集合。
回溯函数终止条件
什么时候到达所谓的叶子节点
单层搜索的过程
回溯法的搜索过程就是一个树型结构的遍历过程,在如下图中,可以看出for循环用来横向遍历,递归的过程是纵向遍历。
3.组合问题
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。
分析
很明显的回溯算法组合问题,跟上一个差不多。
代码
def combine(n, k):
res = []
#记录总体
path = []
#记录满足条件的单一元素
def backtrack(n, k, StartIndex):
if len(path) == k:
res.append(path[:])
return
#满足条件,加入数组,单个终止条件
#开始回溯
for i in range(StartIndex, n-(k-len(path)) + 2):
#for循环遍历横向
path.append(i)
#把满足条件的加入到path里
backtrack(n, k, i+1)
#在第二个函数不停加入
path.pop()
#已经加入到res的pop出来,进行下次回溯
backtrack(n, k, 1)
#初始条件
return res
n = eval(input())
k = eval(input())
res = combine(n,k)
print(res)
总结
回溯三部曲:
1,两个累加器,一个记录单独元素,一个记录总体元素。
2,单个元素终止条件
3,for循环遍历横向,递归法遍历纵向
4.全排列
输入: [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
代码
class Solution:
def permute(self,nums):
res=[]
size=len(nums)
def backtrack(combination,nums):
if len(combination)==size:
res.append(combination)
return
for i in range(len(nums)):
backtrack(combination+[nums[i]],nums[:i]+nums[i+1:])
backtrack([],nums)
return res
if __name__=='__main__':
nums=[1,2,3]
solution=Solution()
print(solution.permute(nums))
总结
主要是回溯三部曲,注意迭代的两个函数和初始条件。
总结到这里,借鉴了各位大佬的代码和总结,作为菜鸡应该会经常更新的hh,有不对或者不好的地方欢迎各位大佬指正。