回溯算法【笔记】

啥时候用

递归的后部分,实际上是纯暴力算法。

解决:组合,切割(回文数),子集,排列,棋盘(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 不对应任何字母。

分析

首先理解题意,所有能表示的字母组合,也就是说,把24个字母分成了8堆(其中除了7和9有四个字母,其余全是3个字母),从每个堆中任选其一进行组合,问有多少种组合方式。

回溯算法的子集问题。

思路很简单,就是按照顺序枚举每一个数字对应的字母组合在一起。使用额外的函数 combination()将已产生的字母序列和剩下数字对应的字母结合起来。

代码

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,有不对或者不好的地方欢迎各位大佬指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值