从 LeetCode 78 子集出发,真正理解回溯:当前操作、子问题与下一个子问题
大家好我是程序员无隅第一次写回溯时我们很容易把注意力放在append()、递归调用和pop()上最后记住了一段模板却不知道为什么递归参数是i也不知道下一层为什么传i 1或j 1。其实写回溯最重要的并不是先背模板而是先把递归函数的含义说清楚。只要能回答“当前做什么、当前还要解决什么、做完以后还剩什么”这三个问题代码通常只是把这条逻辑翻译出来。本文从 LeetCode 78「子集」出发分别使用“选或不选”和“枚举选哪个”两种写法理解回溯最后再把同样的分析方法迁移到 LeetCode 131「分割回文串」。一、为什么写回溯前要先回答“三问”假设我们正在逐位构造一个字符串path[i]表示答案的第i个位置。这时可以先回答当前操作是什么枚举一个合法字母填入path[i]。dfs(i)解决什么子问题在前i个位置已经确定的基础上从第i位开始继续构造字符串。做完选择后下一个子问题是什么第i位已经填好调用dfs(i 1)继续构造第i 1位及后面的部分。这三问分别对应回溯代码里的三件事forchinchoices:# 枚举当前选择path.append(ch)# 执行当前选择dfs(i1)# 解决下一个子问题path.pop()# 撤销当前选择这里真正决定递归写法的不是append()和pop()而是我们对dfs(i)的定义。如果把dfs(i)定义成“处理第i个输入元素”下一层通常是dfs(i 1)如果把它定义成“从下标i开始枚举下一个要选的元素”那么选择nums[j]后下一层就应该是dfs(j 1)。递归参数不是凭感觉传递的它必须与递归函数的语义保持一致。二、回溯算法的本质在决策树上构造答案回溯可以理解为在一棵决策树上进行深度优先搜索。树上的一个节点表示当前已经完成了一部分选择。从一个节点走向子节点表示做出一次新选择。到达满足条件的节点时把当前路径收集为答案。返回父节点前撤销刚才的选择再尝试其他分支。因此写回溯时需要先确定两个核心对象。1.path已经完成了哪些选择path保存从根节点走到当前节点的选择结果。例如在子集问题中path [1, 3]表示当前已经选择了数字1和3。它不是“接下来还要做什么”而是已经做完的选择所形成的局部答案。2.dfs(状态)接下来还要解决什么问题递归参数描述剩余问题。例如dfs(i)可以定义为在当前path的基础上从下标i开始继续构造后面的答案。于是一次标准回溯过程就是path.append(choice)# 做选择dfs(next_state)# 解决选择之后的剩余问题path.pop()# 恢复到选择之前的状态pop()并不是为了“删除错误答案”。它的作用是恢复现场让同一个path可以继续表示父节点的状态随后尝试另一种选择。因此回溯的核心链路可以概括为当前状态 → 枚举一个合法选择 → 修改 path → 进入下一个状态 → 恢复 path。三、LeetCode 78用两种视角生成所有子集给定一个不含重复元素的整数数组nums返回它的所有子集。以nums [1, 2, 3]为例答案包括[] [1] [2] [3] [1,2] [1,3] [2,3] [1,2,3]这道题有两种经典回溯写法。它们没有改变问题本身只是观察决策树的角度不同。3.1 方法一站在输入角度选或不选站在输入数组的角度我们依次询问每个元素nums[i]要不要进入当前子集每个元素只有两种状态不选nums[i]选择nums[i]回溯三问当前操作是什么决定nums[i]选还是不选。当前子问题是什么dfs(i)表示在前i个元素已经决定完的基础上继续决定下标i及后面的元素。下一个子问题是什么无论是否选择nums[i]它都已经被处理完所以下一层都是dfs(i 1)。代码defsubsets(nums):ans[]path[]nlen(nums)defdfs(i):ifin:ans.append(path.copy())return# 不选 nums[i]dfs(i1)# 选择 nums[i]path.append(nums[i])dfs(i1)path.pop()dfs(0)returnans这棵搜索树一共有n层每一层处理一个输入元素。只有到达i n时才说明所有元素的“选或不选”都已经决定完因此在叶子节点收集答案。这里的path.copy()不能省略。path在整个搜索过程中会不断修改如果直接保存path答案数组中的多个位置将引用同一个列表后续回溯会一起改变它们。3.2 方法二站在答案角度枚举下一个选谁换一个角度不再逐个询问输入元素而是直接考虑当前要往path里放哪个数如果当前允许从下标i开始选择那么可以枚举j i, i 1, ..., n - 1把nums[j]作为答案中的下一个元素。回溯三问当前操作是什么从当前允许选择的范围[i, n)中枚举一个下标j把nums[j]加入path。当前子问题是什么dfs(i)表示在当前已经选好若干数字的基础上从下标i开始继续枚举下一个要选择的数字。下一个子问题是什么如果选择了nums[j]下一次只能从j 1开始继续选择因此调用dfs(j 1)。代码defsubsets(nums):ans[]path[]nlen(nums)defdfs(i):# 当前 path 本身就是一个合法子集ans.append(path.copy())forjinrange(i,n):path.append(nums[j])dfs(j1)path.pop()dfs(0)returnans注意这里的下一层是dfs(j 1)不是dfs(i 1)。因为i只表示本层允许选择的起始位置真正被选中的是nums[j]。选择完成后需要越过下标j下一层才能保证下标严格递增。3.3 为什么不会遗漏也不会产生重复子集假设某个子集选中的下标是i₁ i₂ i₃第二种写法会依次选择i₁ → i₂ → i₃任何一个子集都能把元素按照原数组下标从小到大排列因此它一定对应搜索树中的一条路径。这说明不会遗漏。同时递归只允许从当前下标之后继续选择所以下标不能回头。[1, 3]只能通过“先选择1再选择3”得到不可能再通过“先选择3再选择1”生成一次。这说明不会重复。递增下标同时建立了完整性和唯一性每个子集都对应唯一的一条递增下标序列。3.4 两种方法到底有什么区别第一种方法站在输入角度当前元素选不选它的递归深度固定为n每个叶子节点对应一种完整的选或不选方案。第二种方法站在答案角度当前答案的下一个元素选谁它的答案长度不固定每进入一个递归节点当前path就已经代表一个合法子集因此可以立即收集。两种写法都会生成2^n个子集。复制每个子集还需要与其长度成正比因此时间复杂度O(n × 2^n)递归栈与路径空间O(n)如果计算返回结果本身占用的空间O(n × 2^n)四、从子集迁移到 LeetCode 131 分割回文串LeetCode 131 要求把字符串分割成若干个回文子串并返回所有合法分割方案。例如s aab合法答案为[a, a, b] [aa, b]这道题与子集很像字符串中的切割位置同样可以看成一系列选择。它也有两种观察角度。4.1 方法一判断当前位置切不切站在输入位置的角度依次判断每个字符后面是否切一刀。回溯三问当前操作是什么判断位置i后面是否切割。不切当前子串继续向后延长。切取出s[start:i 1]只有它是回文串才能加入path。当前子问题是什么dfs(i, start)表示当前正在检查位置i未完成子串从start开始继续决定后面的切割方式。下一个子问题是什么不切时当前子串起点不变进入dfs(i 1, start)。切割时下一段从i 1开始进入dfs(i 1, i 1)。defpartition(s):ans[]path[]nlen(s)defis_palindrome(left,right):whileleftright:ifs[left]!s[right]:returnFalseleft1right-1returnTruedefdfs(i,start):ifin:ifstartn:ans.append(path.copy())return# 当前位置后面不切继续延长当前子串ifin-1:dfs(i1,start)# 当前位置后面切一刀ifis_palindrome(start,i):path.append(s[start:i1])dfs(i1,i1)path.pop()dfs(0,0)returnans一句话记忆依次判断每个字符后面切不切切出来的部分必须是回文串。4.2 方法二枚举下一段在哪里结束站在答案的角度我们不再判断每个位置“切不切”而是直接枚举下一段回文串的结束位置。回溯三问当前操作是什么从尚未分割的第一个字符i开始枚举结束位置j。如果s[i:j 1]是回文串就把它加入path。当前子问题是什么dfs(i)表示前i个字符已经分割完成从下标i开始继续分割剩余字符串。下一个子问题是什么选择s[i:j 1]后这一段已经完成下一次从j 1开始因此调用dfs(j 1)。defpartition(s):ans[]path[]nlen(s)defis_palindrome(left,right):whileleftright:ifs[left]!s[right]:returnFalseleft1right-1returnTruedefdfs(i):ifin:ans.append(path.copy())returnforjinrange(i,n):ifnotis_palindrome(i,j):continuepath.append(s[i:j1])dfs(j1)path.pop()dfs(0)returnans一句话记忆每次枚举下一段回文串选多长选完以后继续分割剩余字符串。4.3 为什么子集可以立即收集回文分割却不行在子集的“枚举选哪个”写法中即使后面还有数字没有选择当前path也已经是一个完整、合法的子集。例如nums [1, 2, 3] path [1][1]本身就是答案不需要等到所有数字都处理完因此进入dfs时就可以记录。而在回文分割中s aab path [aa]此时字符b还没有被分割[aa]只是一个半成品。只有递归位置到达n说明整个字符串都被若干回文子串覆盖当前path才是完整答案。所以答案何时加入ans不能靠背模板判断。应该先问当前 path 是否已经满足题目对一个完整答案的全部要求五、一套可复用的回溯分析方法遇到新的回溯题可以按照下面的顺序分析。第一步确定path表示什么先问自己当前已经做了哪些选择在子集问题中path是已经选中的数字在分割回文串中path是已经确定的回文子串。第二步定义dfs(状态)不要只写一个模糊的“dfs用来回溯”。需要把剩余问题说完整。例如dfs(i)在当前 path 的基础上从下标 i 开始继续枚举后面的选择。定义清楚以后递归参数如何变化通常也会随之确定。第三步回答回溯三问当前操作是什么当前子问题是什么做完选择后下一个子问题是什么如果第三问无法回答就说明递归函数的定义还不够清楚。第四步确定什么时候得到完整答案结束条件不是统一的i n收集答案的位置也不一定总在叶子节点。子集的“选或不选”写法所有元素都决定完时收集。子集的“枚举选哪个”写法每个节点的path都是合法子集进入递归就收集。分割回文串只有整个字符串都被分割完时收集。结束条件取决于题目如何定义一个完整答案而不是取决于模板长什么样。第五步枚举选择递归再恢复现场最后才把前面的分析翻译成代码defdfs(state):if当前已经构造出完整答案:ans.append(path.copy())returnforchoicein当前所有合法选择:path.append(choice)dfs(next_state)path.pop()这段代码只是一个结构提示并不是所有回溯题都要机械套用。真正需要记住的是path描述已经完成的选择dfs(状态)描述尚未解决的问题每次递归只做一个当前选择然后把剩余问题交给下一层。回到子集问题“选或不选”是在遍历输入元素每层决定一个元素的状态。“枚举选哪个”是在构造答案每层决定答案中的下一个元素。当你能准确说出自己站在哪个角度、当前做什么、下一层还剩什么时回溯就不再是一段需要死记硬背的模板而是一条可以一步步推导出来的决策链。

相关新闻

开源项目wvp-gb28181-pro怎么设置生产环境的IP

开源项目wvp-gb28181-pro怎么设置生产环境的IP

对照开源 WVP-PRO 官方配置语义,生产环境最合理的原则是:按“谁访问谁”拆 IP,不要一个公网 IP 打天下。你们现在生产更接近“能用,但不完全按官方最优拆分”。 如果是公网摄像头 公网网页观看 WVP/ZLM 同机或同内网&#xff0c…

2026/7/29 2:16:09 阅读更多 →
【JAVA毕设源码分享】基于Java的旅游攻略分享平台的设计与实现(程序+文档+代码讲解+一条龙定制)

【JAVA毕设源码分享】基于Java的旅游攻略分享平台的设计与实现(程序+文档+代码讲解+一条龙定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/7/29 2:15:09 阅读更多 →
三电平 PCS 拓扑优势剖析与中点电位平衡控制技术详解

三电平 PCS 拓扑优势剖析与中点电位平衡控制技术详解

引言 随着新能源发电、储能系统以及高压大功率电能转换需求的快速增长,对功率变换器(PCS)的性能要求日益严苛。传统的两电平电压源型变换器在高电压、大功率应用场景下面临着开关损耗大、输出谐波含量高、电磁干扰(EMI)严重等挑战。在此背景下,三电平拓扑应运而生,并迅…

2026/7/29 2:15:09 阅读更多 →

最新新闻

MAI Gateway 技术解析:OpenAI 越狱一周才被发现,企业网关层该如何补位

MAI Gateway 技术解析:OpenAI 越狱一周才被发现,企业网关层该如何补位

2026 年 7 月 27 日,OpenAI 失控 AI 内幕曝光:基于 GPT-5.6 Sol 和一个更强大未发布模型的测试智能体,在内部测试中突破沙盒容器限制,连接互联网并入侵了 Hugging Face 服务器。从 7 月 9 日首次逃逸尝试,到 7 月 19 日…

2026/7/29 2:23:12 阅读更多 →
[特殊字符] “YOLO 模式” 首次曝光:AI 代理自主渗透泰国财政部,网络间谍进入全自动化时代

[特殊字符] “YOLO 模式” 首次曝光:AI 代理自主渗透泰国财政部,网络间谍进入全自动化时代

黑客给 AI 下达指令后就去睡觉了——AI 自己完成了侦察、提权、横向移动全部攻击链。 你好,我是老张。 如果说上个月 OpenAI 模型“越狱”入侵 Hugging Face 还是一次意外的测试失控,那这一次,攻击者已经主动把 AI 代理投入了真实的网络间谍…

2026/7/29 2:23:12 阅读更多 →
基于Mathematica与Arduino的人脸跟踪云台系统设计与实现

基于Mathematica与Arduino的人脸跟踪云台系统设计与实现

1. 项目缘起:当数学引擎遇见物理世界几年前,我在做一个智能交互装置的原型时,遇到了一个头疼的问题:我需要一个能实时处理摄像头画面、识别特定目标(比如人脸),并据此控制物理设备(比…

2026/7/29 2:23:12 阅读更多 →
MiniMax呼吁AI模型权重开放:技术实践与行业影响分析

MiniMax呼吁AI模型权重开放:技术实践与行业影响分析

MiniMax 呼吁开放权重与开源未来:AI 模型开源的技术实践与影响分析在人工智能快速发展的今天,模型权重的开放与开源生态建设成为行业热议话题。作为国内领先的 AI 初创企业,MiniMax(稀宇科技)近期公开呼吁开放模型权重…

2026/7/29 2:23:11 阅读更多 →
ESP32与开源大模型融合:打造智能微型桌面机器人全流程指南

ESP32与开源大模型融合:打造智能微型桌面机器人全流程指南

1. 项目概述:当创客周刊遇上开源大模型与微型机器人又到了每周的创客时间。这期DF创客周刊第35期,标题里两个关键词特别抓人:微型桌面机器人和开源大语言模型。乍一看,一个玩的是精巧的硬件机电控制,另一个是前沿的AI软…

2026/7/29 2:23:11 阅读更多 →
APDS-9960传感器实战:从I2C通信到色彩校准的嵌入式开发指南

APDS-9960传感器实战:从I2C通信到色彩校准的嵌入式开发指南

1. 项目缘起:从“免费试用”到“深度把玩”的契机最近在逛一些硬件开发社区时,发现了一个挺有意思的免费试用活动,主角是一款集成了红外手势识别、RGB颜色检测和环境光检测的传感器模块。说实话,这类“三合一”的传感器在创客圈里…

2026/7/29 2:22:11 阅读更多 →

日新闻

【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

一、本文介绍 🔥本文在RT-DETR多模态融合目标检测中引入RLAB残差线性注意力模块,可在不同模态特征交互阶段进行多次残差细化,使可见光、红外等特征在尺度、语义和空间位置上更好对齐;随后将细化特征与解码器输出拼接并生成Q、K、V,通过线性注意力自适应强化关键通道、目…

2026/7/29 0:00:23 阅读更多 →
AI编程系列02:合并知识功能,给 AI 问数和 RAG 场景打基础

AI编程系列02:合并知识功能,给 AI 问数和 RAG 场景打基础

AI编程系列02:合并知识功能,给 AI 问数和 RAG 场景打基础 在上一期「AI编程系列」中,我们学习了如何构建一个基础的 AI 问答系统,通过简单的输入输出让模型回应问题。但现实世界中的 AI 应用往往需要处理更复杂的场景:…

2026/7/29 0:00:23 阅读更多 →
AI智能体开发实战:从工具调用到企业级部署

AI智能体开发实战:从工具调用到企业级部署

1. 从被动问答到主动执行:AI Agent的范式转变过去两年,大语言模型最显著的应用形态是聊天机器人——用户提问,AI回答。但真正的生产力革命发生在2023年下半年:当AI学会主动调用工具完成任务时,生产力工具的历史被彻底改…

2026/7/29 0:00:23 阅读更多 →

周新闻

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 道路桥梁裂缝检测数据集 道路桥梁病害识别检测数据集

深度学习道路桥梁裂缝检测系统 数据集6000张 完整源码已标注数据集训练好的模型环境配置教程程序运行说明文档,可以直接使用!系统支持图片、视频、摄像头等多种方式检测裂缝,功能强大实用。 1数据集6000张 8各类别

2026/7/28 12:04:22 阅读更多 →
深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

深度学习YOLO模型如何训练 PUBG 绝地求生目标检测数据集

pubg数据集 精选原图1.42万数据 1.49万标签 无任何重复、算法增强或冗余图像! pubg绝地求生目标检测数据集 1分类:e_body,14905个标签,txt格式 共计14244张图,99%为640*640尺寸图像 适合yolo目标检测、AI训练关键词&am…

2026/7/28 8:29:16 阅读更多 →
Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex英雄目标检测数据集 深度学习框架YOLO如何训练APEX数据集

Apex检测数据集数据集详情检测类别: allies enemy tag图片总量:7247张训练集:5139张验证集:1425张测试集:683张标注状态:全部已标注,即拿即用数据格式:支持YOLO格式及其他格式&#…

2026/7/28 5:03:42 阅读更多 →

月新闻