跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
编程语言算法

递归搜索与回溯算法详解及综合练习

通过多个 LeetCode 例题(子集异或和、全排列 II、电话号码组合、括号生成、组合、目标和、组合总和、字母大小写全排列),深入讲解递归、搜索与回溯算法的核心思想。重点阐述了决策树的构建、剪枝策略(如重复元素处理、合法分支判断)以及代码实现细节。文章对比了不同解题思路,分析了全局变量与参数传递的区别,并提供了完整的逻辑推导过程,帮助读者掌握回溯算法的综合应用。

Pythonist发布于 2026/3/27更新于 2026/7/2044 浏览
递归搜索与回溯算法详解及综合练习

文章配图

文章配图

找出所有子集的异或总和再求和

题目解析

文章配图

算法原理

解法

决策树

文章配图

这种决策使得每一次递归都是有效的递归,每一个节点都是最终的结果,所以这棵决策树是不用剪枝的,也没有递归出口。

注意

决策树执行添加元素的操作前,要先从子集末尾元素在 nums 的位置后面是否还有元素,如果有元素则可以添加,反之,则不可以添加。

文章配图

全局变量

开始时,子集是空集,所以异或的结果为 0,path 初始值刚好是 0,所以不用处理子集为空的情况。

文章配图

函数结构

在递归到决策树的某一层时,要知道从 nums 的哪个元素开始向后枚举,因此设计 dfs(nums, pos)。

文章配图

编写代码

文章配图

虽然没有写 return 来回溯,但是在每次向下递归新一层的 dfs 时,这层 dfs 执行完,就会自动返回上一层的 dfs。

全排列 II

题目解析

文章配图

算法原理

这道题其实就是全排列 I 的进阶版本,只是多了重复的数,大体框架和全排列 I 相同,只是剪枝操作需要更细致一点。

两种剪枝

  1. 在同一个节点(如图中的黑色节点)的所有分支中,相同的元素只能选择一次:

文章配图

  1. 同一个数只能使用一次,可以设置一个 check[] 数组来标记一下用过的数为 true。

文章配图

完善决策树

文章配图

文章配图

两种思考方式

本质相同,都是针对是否需要剪枝的情况作出相应的处理。

文章配图

文章配图

文章配图

只关心不合法的分支(被剪枝的分支)

红色剪枝条件 check[i] == true,表示同一个数只能使用一次。

粉色剪枝条件 nums[i] == nums[i - 1],表示同一个节点的所有分支中,相同数只能使用一次。

文章配图

问题一:如果 nums 中重复元素不是连在一起的,那么这个判断条件无法使用
问题解析

如果我们要全排列的数组是 [1, 2, 1, 3, 1],那么无法判断同一个节点,其中一条分支要递归的数,是否在前面分支中已经出现过了。

解决办法

我们可以在正式递归之前,先对 nums 数组先进行排序,方便后续的剪枝操作。在大多数情况下,排序数组的时间复杂度 O(N log N),对于递归 O(2^N) 来说可以忽略不计。

问题二:无法筛查掉红色剪枝的分支,和合法分支相邻的情况
问题解析

nums[i] == nums[i - 1] 这个对不合法分支的判断条件范围还是太广了,无法筛查掉红色剪枝的分支,和合法分支(不应该被剪枝的分支)相邻的情况。

因为这些分支所代表的数是相同的,所以 nums[i] 所在的分支会被识别为不合法分支,而被执行粉色剪枝。

但是实际上,被红色剪枝的 nums[i - 1] 所在分支,本身就是不合法分支。

只有当 nums[i-1] 和 nums[i] 两条分支都是合法的时候,才需要判断 nums[i] 是否等于 nums[i - 1]。

解决办法

我们要把下面这两种情况区分开:

文章配图

那么怎么区分开呢?其实非常简单;就是判断两个数是否在决策树的同一层。

(1) 对于不同层:

文章配图

两个数不是同一层,哪怕 nums[i] == nums[i - 1],nums[i - 1] 所在分支肯定也会被执行红色剪枝,因此 nums[i] 所在分支就是合法分支。

(2) 对于同一层:

文章配图

如果递归时发现 check[i-1] == false,则说明 nums[i-1] 和 nums[i] 所在位置为决策树同一层,如果是同一层,并且这两个数还相等,此时递归 nums[i] 的分支就是不合法的分支。

(3) 改进条件

我们在准备对 nums[i] 进行递归时,先判断 check[i] == false,如果 check[i - 1] == false,那么就可以判断 nums[i-1] 和 nums[i] 在同一层。

对同一层进行进一步判断,如果 nums[i-1] == nums[i],说明 nums[i] 所在分支不合法。

问题三:i = 0 时,访问 nums[i - 1] 会越界

只关心不合法分支的最终判断条件:i != 0 && check[i] == false && nums[i] == nums[i-1]

总结不合法分支

文章配图

只关心合法的分支(不被剪枝的分支)

先判断该节点是否已经被使用(是否会被执行红色剪枝)

如果我们的思考链路是只关心分支合法,那么第一步就是检查该分支是否被红色剪枝:

文章配图

所以第一步就是判断 check[i] == false。

合法分支是一条不被执行红色剪枝 && 不被执行粉色剪枝 的分支,所以在判断该分支不被执行红色剪枝后,我们就需判断该分支是否被执行粉色剪枝。

节点未被使用的情况下,是否被执行粉色剪枝
节点在决策树的深度相同,但是代表的数不同

我们先来分析下面这条分支,在检查完 check[i] == false 而不被执行红色剪枝之后,我们来判断是否被粉色剪枝:

文章配图

显然,nums[i] != nums[i - 1],因此肯定不会执行粉色剪枝(nums 已经提取排好序了)。

此时的判断条件为 check[i] == false && (nums[i] != nums[i-1]...)

节点代表的数相同,但是该节点前一条分支已经被使用

文章配图

如果 nums[i] == nums[i - 1],但是 nums[i - 1] 已经被使用过了,此时 check[i - 1] == true,那么此时的分支也是合法的。所以此时的判断条件:

文章配图

当 i = 0 的情况

如果只是考虑当前分支是否合法,那么 i 是可以等于 0 的。

i = 0 表示的是数组第一个元素,只要确保 check[0] == false,就一定是可以大胆枚举的;因为这是对 nums 的第一个元素进行枚举的分支,这条分支必定是合法分支(一定是从考虑合法分支的角度出发)。

文章配图

此时的判断条件:

文章配图

两种思考方式判断条件的区别

如果考虑当前分支不合法,就需要根据前一条分支的具体情况,来对当前分支的合法与否作出判断,如果当前分支的 i=0,则会出现数组越界。

文章配图

处理细节问题

文章配图

编写代码

只关心不合法分支

文章配图

只关心合法分支

文章配图

电话号码的字母组合

题目解析

文章配图

算法原理

解法一:暴力枚举

定义两层 for 循环,对字符映射的数字的所有组合进行暴力枚举;但是如果一个字符映射的数字过多,使用暴力枚举是不好操作的。

解法二:深度优先遍历

决策树

文章配图

对决策树进行一次深度优先遍历,在叶子节点收集结果即可。

解决数字和字符串的映射关系

我们可以使用字符串数组,让字符串数组前两个位置空着,让下标为 2 的数组元素存 "abc" 这个字符串,往后依此类推。

文章配图

我们在遍历原始字符串的时候,拿到字符 '2' 之后,减去字符 '0' 对应的 ASCII 码值,就可以对应字符串数组的下标元素。

全局变量

文章配图

设计函数

文章配图

编写代码

文章配图

括号生成

题目解析

文章配图

算法原理

文章配图

给一个括号子串,必须从头到尾遍历子串的每一个括号字符,如果在遍历的过程中,出现左括号的数量大于右括号的数量,那么这个子串就一定不是有效括号子串。

文章配图

解法:暴搜

决策树

文章配图

蓝色剪枝表示添加 ( 数量大于 n 剪枝;紫色剪枝表示添加 ) 数量大于 ( 的数量。

全局变量

文章配图

这些变量可以设置成全局变量,也可以作为参数传给 dfs,区别在于恢复现场时采取的措施不同。

设置函数

文章配图

编写代码

文章配图

组合

题目解析

文章配图

算法原理

解法:暴搜

决策树

根据题目示例可以知道,得到的 path,元素没有顺序可言,path 的区别只在元素的种类,所以可以画出决策树:

文章配图

  1. 两层节点代表的数相同,执行紫色剪枝;2. 前面的分支已经枚举过这种可能,执行蓝色剪枝,path 只看元素种类,不看元素顺序

发现规律

文章配图

所以我们不需要定义全局变量来剪枝,只需要在向下递归时,从当前节点元素的下一个元素开始递归即可。

设置函数

文章配图

编写代码

文章配图

目标和

题目解析

文章配图

算法原理

解法

决策树

这棵决策树是要深度优先遍历的,每一个节点每次递归只能遍历一条分支,而不是在一个节点递归时,同时记录所有分支;通过递归回溯相结合的方式,遍历整棵决策树。

文章配图

编写代码

path 是全局变量时候的代码(手动回溯)

文章配图

path 作为参数的代码(自动回溯)

文章配图

报错原因:

如果我们提前让 path+= nums[i],是真正修改了 path 的状态并且记录,那么编译器在帮我们恢复现场的时候,只会恢复 pos 的值,而因为 path 的值无法恢复到上次递归前的值。

文章配图

组合总和

题目解析

文章配图

算法原理

解法一:暴搜

决策树

红色剪枝:剪去超过 target 的分支

文章配图

紫色剪枝:剪去重复出现的组合

文章配图

最终结果

文章配图

发现规律

文章配图

从最终的有效递归图,我们可以发现,有效递归都是从当前节点开始,向后枚举的。

所以我们在进行下一轮递归时,从当前节点开始枚举即可。

编写代码

文章配图

文章配图

报错原因:没有考虑以 pos 越界的情况作为递归出口,并且忽略了本题是可以选择重复元素的:

文章配图

恢复现场的操作是去掉最后一个元素,并且 remove() 的 API 也用不对。

文章配图

解法二

决策树

文章配图

这棵决策树是的每一层,是在节点和 <= target 时,枚举重复元素相加的个数,直到枚举的节点和大于 target。

处理细节问题

文章配图

恢复现场的时机

这个解法有一个特别容易被忽略的地方,就是回溯现场的时机,如左下角的两次递归回溯:

文章配图

第一次回溯,是不能恢复现场的,因为第二次递归,是在第一次递归了 0 个 5 的基础上,再多递归 1 个 5;也就是说,对于同一层回溯,只有递归完一个元素能枚举的所有使用次数,才能恢复现场。

并且,恢复现场时,sum 会自动恢复,但是 path 需要我们手动恢复。

文章配图

在解法一的基础上修改代码

文章配图

对于解法二,只是枚举的策略不同,其他的递归,剪枝操作和解法一是相同的;因此,我们只需要删除红色框的代码,在此基础上重新编写即可。

用于枚举的循环的终止条件

文章配图

这个循环的小细节是,枚举 nums[pos] 的个数是从 0 个开始的,当 k * nums[pos] > aim,枚举完这个数的所有可能的使用个数:

文章配图

在哪里恢复现场?

不要在上面 for 循环恢复现场,出了 for 循环,表示 nums[pos] 的使用个数枚举完毕,此时再恢复现场:

文章配图

恢复现场的循环起点

注意:恢复现场的循环从 k=1 开始

因为我们在递归枚举时,会枚举 nums[pos] 使用 0 次的情况(上层 for 循环从 k=0 开始循环):

但是 path 真正添加 nums[pos] 时,nums[pos] 的使用次数是不为 0 的;所以我们恢复现场要从 k=1 开始;

所以我们手动恢复现场,本质上恢复的的是执行 path.remove(size()-1) 操作 k-1 次,sum 则是因为通过参数传递,编译器会自动帮助我们恢复 sum;

编写代码

文章配图

小优化

文章配图

字母大小写全排列

题目解析

文章配图

算法原理

解法:暴搜

决策树

文章配图

我们可以在原字符串 s 上操作,当 s 递归到叶子节点时,把叶子节点的 s 添加到 ret 中;也可以定义一个全局变量 path,遍历到 s 字符串的字母字符时,就把变或不变的字符(每次只能选一个)添加到 path 上,如果遍历到的是数字,直接添加到 path 后即可。

全局变量

文章配图

函数设计

文章配图

编写代码

文章配图

文章配图

目录

  1. 找出所有子集的异或总和再求和
  2. 题目解析
  3. 算法原理
  4. 解法
  5. 决策树
  6. 注意
  7. 全局变量
  8. 函数结构
  9. 编写代码
  10. 全排列 II
  11. 题目解析
  12. 算法原理
  13. 两种剪枝
  14. 完善决策树
  15. 两种思考方式
  16. 只关心不合法的分支(被剪枝的分支)
  17. 问题一:如果 nums 中重复元素不是连在一起的,那么这个判断条件无法使用
  18. 问题解析
  19. 解决办法
  20. 问题二:无法筛查掉红色剪枝的分支,和合法分支相邻的情况
  21. 问题解析
  22. 解决办法
  23. 问题三:i = 0 时,访问 nums[i - 1] 会越界
  24. 总结不合法分支
  25. 只关心合法的分支(不被剪枝的分支)
  26. 先判断该节点是否已经被使用(是否会被执行红色剪枝)
  27. 节点未被使用的情况下,是否被执行粉色剪枝
  28. 节点在决策树的深度相同,但是代表的数不同
  29. 节点代表的数相同,但是该节点前一条分支已经被使用
  30. 当 i = 0 的情况
  31. 两种思考方式判断条件的区别
  32. 处理细节问题
  33. 编写代码
  34. 只关心不合法分支
  35. 只关心合法分支
  36. 电话号码的字母组合
  37. 题目解析
  38. 算法原理
  39. 解法一:暴力枚举
  40. 解法二:深度优先遍历
  41. 决策树
  42. 解决数字和字符串的映射关系
  43. 全局变量
  44. 设计函数
  45. 编写代码
  46. 括号生成
  47. 题目解析
  48. 算法原理
  49. 解法:暴搜
  50. 决策树
  51. 全局变量
  52. 设置函数
  53. 编写代码
  54. 组合
  55. 题目解析
  56. 算法原理
  57. 解法:暴搜
  58. 决策树
  59. 发现规律
  60. 设置函数
  61. 编写代码
  62. 目标和
  63. 题目解析
  64. 算法原理
  65. 解法
  66. 决策树
  67. 编写代码
  68. path 是全局变量时候的代码(手动回溯)
  69. path 作为参数的代码(自动回溯)
  70. 组合总和
  71. 题目解析
  72. 算法原理
  73. 解法一:暴搜
  74. 决策树
  75. 最终结果
  76. 发现规律
  77. 编写代码
  78. 解法二
  79. 决策树
  80. 处理细节问题
  81. 恢复现场的时机
  82. 在解法一的基础上修改代码
  83. 用于枚举的循环的终止条件
  84. 在哪里恢复现场?
  85. 恢复现场的循环起点
  86. 编写代码
  87. 小优化
  88. 字母大小写全排列
  89. 题目解析
  90. 算法原理
  91. 解法:暴搜
  92. 决策树
  93. 全局变量
  94. 函数设计
  95. 编写代码
  • 免费图片AI生成工具免费生成了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • Apache IoTDB 时序数据库介绍与单机版安装部署指南
  • 大模型 RAG 技术详解:架构、优势与实战案例
  • SCI 及核心期刊投稿 AI 生成率检测与降低指南
  • MySQL 8.0 Windows 本地安装与配置实战指南
  • Rust WebAssembly 与 Three.js 结合的 3D 数据可视化实战:高性能粒子系统
  • AC-MPC:微分 MPC 赋能强化学习,实现高速无人机竞速
  • OpenCode 开源 AI 编程代理技术与行业分析
  • C++ STL 常用算法详解:查找、排序与数值处理
  • AI Agent 新范式:FastGPT 集成 MCP 协议构建工具增强智能体
  • AI 绘画工具背后的视觉技术:Stable Diffusion 解析
  • 数据结构:哈希表原理与 C 语言实现
  • Python 环境管理对比:uv 与 conda 的核心差异与选型指南
  • Spring Boot 2.0 整合 Spring Security OAuth2
  • Spring Boot 数据导入导出与报表生成实战
  • 2024 国内最新完整 AI 大模型清单及介绍
  • Kali Linux 虚拟机安装指南
  • Linux 系统安装 Docker Engine 指南
  • 数字 IC 前端设计:前仿篇 (VCS, DVE, Verdi)
  • Windows 部署 OpenAkita 并接入飞书,打造本地 AI 助手
  • Java 工程项目管理系统功能模块与技术架构说明

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online