算法通关村第十八关:青铜挑战-回溯是怎么回事

青铜挑战-回溯是怎么回事

回溯,最重要的算法之一
主要解决一些暴力枚举也搞不定的问题,例如组合、分割、子集、排列、棋盘等

从性能角度来看回溯算法的效率并不高,但对于这些暴力都搞不定的算法能出结果就很好了,效率低点没关系

回溯可视为递归的拓展,很多思想和解法都与递归密切相关,对比递归来分析其特征会理解的更深刻

举例说明递归和回溯的区别:
设想一个场景,某猛男想脱单,两种策略:

  1. 递归策略:先与意中人制造偶遇,然后了解人家的情况,然后约人家吃饭,有好感之后尝试拉人家的手,没有拒绝就表白
  2. 回溯策略:先统计周围所有单身女孩,然后一个一个表白,被拒绝就说”我喝醉了“,然后就当啥也没有发生,继续下一个

回溯最大的好处:有非常明确的模板
所有的回溯都是一个大框架,因此透彻理解回溯的框架是解决一切回溯问题的基础

回溯不是万能的,解决的问题也是非常明确的,例如组合、分割、子集、排列、棋盘等
不过这些问题具体处理时又有很多不同

回溯可视为递归的拓展,代码结构特别像深度遍历N叉树
难点:回溯在递归语句之后有个”撤销“的操作。
好比谈了个新女朋友,来你家之前,要将前任的东西赶紧藏起来。回溯也一样,有些信息是前任的,要处理掉才能重新开始。

回溯的模板如下

void backtracking(参数){if(终止条件){存放结果;return;}for(选择本层集合中元素(画成树,就是树节点孩子的大小)){处理节点;backtracking(参数);回溯,撤销处理结果}
}

1. 从N叉树说起

二叉树的前序遍历

class TreeNode:def __init__(self, val):self.val = valself.left = Noneself.right = Nonedef tree_dfs(root):if root is None:returnprint(root.val)tree_dfs(root.left)tree_dfs(root.right)

N叉树的前序遍历

class TreeNode:def __init__(self, val):self.val = valself.children = []def tree_dfs(root):# 递归终止条件if root is None:return# 节点处理print(root.val)# 通过循环,分别遍历N个子树for i in root.children:tree_dfs(i)

回溯模板与N叉树的遍历模板非常像!!!

2. 为什么有的问题暴力枚举也不行

什么问题暴力枚举也不行?

举个例子:

LeetCode77 组合
https://leetcode.cn/problems/combinations/

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。

n=4, k=2时,双层暴力枚举

def violent_enumeration():res = []for i in range(1, 5):for j in range(i + 1, 5):res.append((i, j))return resif __name__ == '__main__':print(violent_enumeration())  # [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]

n=10, k=3时,三层暴力枚举

def violent_enumeration():res = []for i in range(1, 11):for j in range(i + 1, 11):for k in range(j + 1, 11):res.append((i, j, k))return res

k未知时,循环次数未知,这时暴力枚举就失效了

这就是组合类型问题,除此之外,子集、排列、切割、棋盘等方面都有类似的问题,我们需要找到更好的方式

3. 回溯=递归+局部枚举+手动撤销(放下前任)

继续研究
LeetCode77 组合
https://leetcode.cn/problems/combinations/

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。

n=4, k=2时
在这里插入图片描述

n=5, k=3时
在这里插入图片描述

从图中我们可以发现,元素个数n相当于树的宽度(横向),每个结果的元素个数k相当于树的深度(纵向)
此外还有一下规律

  • 局部枚举:每次都是从类似 [1,2,3,4] 这样的序列进行枚举,越往后枚举范围越小
  • 递归:再看n=5,k=3时图中红色大框部分,执行过程与n=4,k=2处理过程一直,时可以递归的子结构
  • 手动撤销:观察图中可以看到,取3得到[1,2,3]之后,需要将3撤掉,再继续取4得到[1,2,4]
    • 对应的代码操作:
    • 将第一个结果放到 path中,path=[1]
    • 将第二个结果放到 path中,path=[1,2]
    • 将第三个结果放到 path中,path=[1,2,3]
    • 将结果输出,撤销3, path=[1,2]
    • 继续枚举,将第三个结果放到 path中,path=[1,2,4]

综上,可以得到 回溯=递归+枚举+手动撤销

这就是回溯的基本规律,掌握之后就可写出完整的回溯代码了

回溯代码实现

import copyclass Solution:def combine(self, n: int, k: int) -> List[List[int]]:def dfs(k, n, begin, path, res):# 递归终止条件是:path的长度等于kif len(path) == k:res.append(copy.deepcopy(path))return# 枚举:针对一个节点,遍历可能的搜索起点for i in range(begin, n+1):# 像路径变量里添加一个数,就是树枝的值path.append(i)# 搜索起点加1,缩小范围,为下一轮递归做准备,因为不允许出现重复的元素dfs(k, n, i + 1, path, res)# 手动撤销path.pop()res = []if k <= 0 or n < k:return respath = []begin = 1dfs(k, n, begin, path, res)return res

4. 图解为什么有个撤销的操作

暂无,理解了上一小节的 手动撤销 即可

5. 回溯热身-再论二叉树的路径问题

5.1 输出二叉树的所有路径

LeetCode 257
https://leetcode.cn/problems/binary-tree-paths/

思路分析

方法1:深度优先搜索
深度优先搜索就是从根节点开始一直找到叶子结点,这里可以先判断当前节点是不是叶子结点,再决定是不是向下走,如果是叶子结点,我们就增加一条路径。这个之前学习,这里不再赘述

方法2:回溯
从回溯的角度分析,得到第一条路径ABD之后怎么找到第二条路径ABE,这里就是先将D撤销,然后再继续递归就可以了

难点,手动撤销

代码实现

方法1:

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:def search_path(node, path):if not node:return path += str(node.val)if not node.left and not node.right:paths.append(path)else:path += "->"search_path(node.left, path)search_path(node.right, path)paths = []if root:search_path(root, path="")return paths

方法2:回溯

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:def binaryTreePaths(self, root: Optional[TreeNode]) -> List[str]:def search_path(node, path, paths):if node is None:returnpath.append(str(node.val))if node.left is None and node.right is None:paths.append('->'.join(path))for i in [node.left, node.right]:search_path(i, path, paths)path.pop() # 返回上一层递归时,要让当前路径恢复原样paths = []path = []if root:search_path(root, path, paths)return paths

5.2 路径总和问题

LeetCode 113 路径总和 II
https://leetcode.cn/problems/path-sum-ii/

思路分析

目标:路径总和 targetSum=22

在这里插入图片描述

  • 根节点5
  • 需要左侧或右侧target_sum=22-5=17
  • 继续看左子树node(4),需要node(4)左子树或右子树满足 target_sum=17-4=13
  • 依次类推 … …

代码实现

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
import copyclass Solution:def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:def find(node, target_sum, path, paths):if node is None:returnpath.append(node.val)if node.left is None and node.right is None and node.val == target_sum:paths.append(copy.deepcopy(path))target_sum -= node.valfor i in [node.left, node.right]:find(i, target_sum, path, paths)path.pop()paths = []path = []if root:find(root, targetSum, path, paths)return paths

注:不想用copy,也可以用 path[:] 替代

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/72353.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c++类与对象

文章目录 前言一、1、类的引入2、类的定义3、类的访问限定符及封装4、类的实例化5、类对象模型6、this指针7、封装 前言 C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#xff0c;通过函数调用逐步解决问题。 C是基于面向对象的&#xff0c;关…

MySQL聚簇索引与非聚簇索引

分析&回答 当数据库一条记录里包含多个字段时&#xff0c;一棵B树就只能存储主键&#xff0c;如果检索的是非主键字段&#xff0c;则主键索引失去作用&#xff0c;变成顺序查找了。这时应该在第二个要检索的列上建立第二套索引。这个索引由独立的B树来组织。有两种常见的方…

2023国赛数学建模B题思路分析 - 多波束测线问题

# 1 赛题 B 题 多波束测线问题 单波束测深是利用声波在水中的传播特性来测量水体深度的技术。声波在均匀介质中作匀 速直线传播&#xff0c; 在不同界面上产生反射&#xff0c; 利用这一原理&#xff0c;从测量船换能器垂直向海底发射声波信 号&#xff0c;并记录从声波发射到…

vue基于Echarts、百度地图MapVGL实现可视化大屏数据展示

一、布局 常见的大屏数据展示布局&#xff0c;一般会将地图作为整个屏幕的背景&#xff0c;在地图上以九宫格布局展示各类数据图表。实现这一效果可以使地图的z-index1,在地图上的图表等z-index>1,下面会详细描述这种设计该如何实现&#xff1a; <div style"width…

嵌入式开发笔试面试

C语言部分&#xff1a; 1.gcc的四步编译过程 1.预处理 展开头文件&#xff0c;删除注释、空行等无用内容&#xff0c;替换宏定义。 gcc -E hello.c -o hello.i 2.编译 检查语法错误&#xff0c;如果有错则报错&#xff0c;没有错误则生成汇编文件。 gcc -S hello.i -o h…

C++ vector模拟实现

目录 使用insert时迭代器失效使用erase时迭代器失效使用memcpy浅拷贝的问题调用最匹配的函数可能出现的问题模拟实现vector 使用insert时迭代器失效 在模拟vector插入的时候会遇到扩容后pos失效的问题&#xff0c;需要更新pos vector():_start(nullptr), _finish(nullptr), _e…

解耦只是一个巧合?

本文分享一篇在IJCAI2023看到的文章&#xff1a;Overlooked Implications of the Reconstruction Loss for VAE Disentanglement 首先回顾下VAE&#xff0c;其loss函数有两项&#xff0c;一项是重构误差&#xff0c;另一项是正则项&#xff1a; L r e c ( x , x ^ ) E q ϕ (…

QT(9.1)对话框与事件处理

作业&#xff1a; 1. 完善登录框 点击登录按钮后&#xff0c;判断账号&#xff08;admin&#xff09;和密码&#xff08;123456&#xff09;是否一致&#xff0c;如果匹配失败&#xff0c;则弹出错误对话框&#xff0c;文本内容“账号密码不匹配&#xff0c;是否重新登录”&…

23062C++QT day2

封装一个结构体&#xff0c;结构体中包含一个私有数组&#xff0c;用来存放学生的成绩&#xff0c;包含一个私有变量&#xff0c;用来记录学生个数&#xff0c; 提供一个公有成员函数&#xff0c;void setNum(int num)用于设置学生个数 提供一个公有成员函数&#xff1a;void…

FPGA实战小项目3

基于FPGA的波形发生器 基于FPGA的波形发生器 基于FPGA的beep音乐播放器设计 基于FPGA的beep音乐播放器设计 基于FPGA的cordic算法实现DDS sin和cosine波形的产生 基于FPGA的cordic算法实现DDS sin和cosine波形的产生

无需公网IP,实现远程访问群晖Drive并挂载为电脑磁盘的方法

文章目录 前言1.群晖Synology Drive套件的安装1.1 安装Synology Drive套件1.2 设置Synology Drive套件1.3 局域网内电脑测试和使用 2.使用cpolar远程访问内网Synology Drive2.1 Cpolar云端设置2.2 Cpolar本地设置2.3 测试和使用 3. 结语 前言 群晖作为专业的数据存储中心&…

利用MQ实现mysql与elasticsearch数据同步

流程 1.声明exchange、queue、RoutingKey 2. 在hotel-admin中进行增删改&#xff08;SQL&#xff09;&#xff0c;完成消息发送 3. 在hotel-demo中完成消息监听&#xff0c;并更新elasticsearch数据 4. 测试同步 1.引入依赖 <!--amqp--> <dependency><groupId&…

欧洲云巨头OVHcloud收购边缘计算专家 gridscale

边缘计算社区近日获悉&#xff0c;欧洲云巨头OVHcloud已进入全面收购德国公司 gridscale 的谈判&#xff0c;该公司是一家专门从事超融合基础设施的软件提供商。 此次战略收购将标志着 OVHcloud 的另一个重要里程碑&#xff0c;使该集团能够显着加速其地理部署&#xff0c;并进…

lv3 嵌入式开发-6 linux shell脚本编程(概念、变量、语句)

1 Shell脚本概述 1.1Shell脚本概述 Shell脚本是利用 shell 的功能所写的一个程序。这个程序是使用纯文本文件&#xff0c;将一些 shell 的语法与命令&#xff08;含外部命令&#xff09;写在里面&#xff0c;搭配正则表达式、管道命令与数据流重定向等功能 1.2Shell脚本编写流…

[JAVA] byte与int的类型转换案例剖析

总结&#xff1a; ①没有byte的字面值&#xff0c;赋值时需要强制转换类型 ②涉及运算&#xff0c;系统自动进行类型升级&#xff0c;由此用final修饰&#xff0c;代表这是一个不会更改值的常量&#xff0c;通过编译 感受&#xff1a;还是用int吧&#xff0c;自动类型转换太复…

Java程序员所需Javascript知识

它是一种脚本语言&#xff0c;可以用来更改页面内容&#xff0c;控制多媒体&#xff0c;制作图像、动画等等 js 代码位置 <script>// js 代码 </script>引入 js 脚本&#xff0c;在js脚本中写js代码 <script src"js脚本路径"></script>注…

滑动窗口实例7(串联所有单词的子串)

题目&#xff1a; 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。 s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。 例如&#xff0c;如果 words ["ab","cd","ef"]&#xff0c;…

OpenCV之形态学操作

形态学操作包含以下操作&#xff1a; 腐蚀 (Erosion)膨胀 (Dilation)开运算 (Opening)闭运算 (Closing)形态梯度 (Morphological Gradient)顶帽 (Top Hat)黑帽(Black Hat) 其中腐蚀和膨胀操作是最基本的操作&#xff0c;其他操作由这两个操作变换而来。 腐蚀 用一个结构元素…

简述视频智能分析EasyCVR视频汇聚平台如何通过“AI+视频融合”技术规避八大特殊作业风险

视频智能分析EasyCVR视频汇聚平台可以根据不同的场景需求&#xff0c;让平台在内网、专网、VPN、广域网、互联网等各种环境下进行音视频的采集、接入与多端分发。在视频能力上&#xff0c;视频云存储平台EasyCVR可实现视频实时直播、云端录像、视频云存储、视频存储磁盘阵列、录…

noetic 怎么下载robotiq_modbus_tcp 从而使用robotiq二指夹爪

1&#xff0c;会科学上网 2&#xff0c; # Modbus TCP rosdep install robotiq_modbus_tcp3&#xff0c;报错 4&#xff0c;去robotiq的功能包里面找两个大爷&#xff08;tmd&#xff09; 分别修改package.xml 本来是python &#xff0c; 然后分别修改成python3 然后参考其他…