发布时间:2026/9/5 5:50:08
递归与链表:从基础到LeetCode实战 一、前言在数据结构与算法的学习过程中递归和链表是两个绕不开的重要概念。递归是一种优雅的编程思想而链表则是数据结构的基础之一。当两者相遇往往能碰撞出精妙的解法。本文将从递归的基本概念出发结合链表这一数据结构详细讲解LeetCode上的两道经典题目——206. 反转链表和24. 两两交换链表中的节点帮助大家建立起递归解题的思维框架。所有示例代码均使用 Python 实现。二、递归概述2.1 什么是递归递归简单来说就是在定义一个过程或函数时出现调用本过程或本函数的情况。用一句经典的故事来理解从前有座山山中有座庙庙里有个老和尚老和尚在给小和尚讲故事“从前有座山山中有座庙庙里有个老和尚老和尚在给小和尚讲故事……”递归根据调用方式可以分为两类直接递归函数直接调用自身间接递归函数p调用函数q而q又调用p如果一个递归函数中递归调用语句是最后一条执行语句则称为尾递归。2.2 递归模型一个完整的递归模型由两部分组成递归出口确定递归何时结束即终止条件递归体确定递归求解时的递推关系以经典的阶乘函数 n! 为例pythondef factorial(n):if n 1: # 递归出口return 1return n * factorial(n-1) # 递归体其递归模型可抽象为textfactorial(1) 1 // 递归出口factorial(n) n * factorial(n-1) // 递归体2.3 斐波那契数列斐波那契数列是递归的经典应用场景。这个数列从第3项开始每一项都等于前两项之和01123581321345589……其数学定义为textF(0) 0F(1) 1F(n) F(n-1) F(n-2) (n ≥ 2)用Python实现如下pythondef fibonacci5(n):def fn(i):if i 1:return 1if i 0:return 0else:return fn(i-2) fn(i-1)for i in range(n):print(fn(i))2.4 什么时候用递归以下三种情况常常会用到递归定义是递归的如斐波那契数列数据结构是递归的如链表、树问题的求解方法是递归的如分治算法三、链表基础知识在正式解题之前我们先回顾一下链表的基本概念。链表是一种通过指针将一组零散的内存块串联起来的线性数据结构。与数组不同链表不要求存储在一块连续的内存中因此对内存的要求更低但随机访问的性能不如数组。单链表的每个节点包含两部分数据域val 存储数据指针域next 指向下一个节点Python 中的链表节点定义pythonclass ListNode:definit(self, val0, nextNone):self.val valself.next next可以用一个形象的比喻来理解链表就像一列火车每节车厢就是一个节点车厢之间相互连接。四、LeetCode 206. 反转链表4.1 题目描述给你单链表的头节点 head 请你反转链表并返回反转后的链表。示例输入head [1,2,3,4,5]输出[5,4,3,2,1]4.2 方法一迭代法双指针迭代法是反转链表最直观的解法核心思想是逐个改变节点的指向。思路图解定义 cur 指针指向头节点pre 指针指向 None遍历链表每次将 cur.next 用 tmp 保存防止丢失将 cur.next 指向 pre完成当前节点的反转移动 pre 和 cur 指针继续处理下一个节点当 cur 指向 None 时循环结束pre 即为新链表的头节点代码实现Python pythondef reverseList(head):cur headpre Nonewhile cur:tmp cur.next # 保存下一个节点cur.next pre # 反转指向pre cur # pre 前移cur tmp # cur 前移return pre复杂度分析时间复杂度O(n)只需遍历一次链表空间复杂度O(1)只使用了常数个指针4.3 方法二递归法递归法同样可以实现链表反转其思路与迭代法类似但用递归的方式来实现指针的移动。代码实现Python pythondef reverse(pre, cur):if cur is None:return pretmp cur.nextcur.next prereturn reverse(cur, tmp)def reverseList(head):return reverse(None, head)递归思路reverse(pre, cur) 函数的意义是反转以 cur 为头、pre 为前驱的链表返回反转后的新头节点。递归的终止条件是 cur None此时 pre 就是新链表的头节点。此外也可以使用更简洁的单函数递归后序遍历版本pythondef reverseList(head):# 递归终止条件空链表或只有一个节点if not head or not head.next:return head# 先反转后面的链表new_head reverseList(head.next)# 将当前节点接到反转后的链表末尾head.next.next headhead.next Nonereturn new_head五、LeetCode 24. 两两交换链表中的节点5.1 题目描述给你一个链表两两交换其中相邻的节点并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题即只能进行节点交换。示例输入head [1,2,3,4]输出[2,1,4,3]5.2 方法一迭代法哨兵节点迭代法的关键在于使用一个哨兵节点dummy node 来简化头节点的处理。代码实现Python pythondef swapPairs(head):dummy ListNode(0)dummy.next headprev dummywhile head and head.next: nxt head.next # 保存第二个节点 head.next nxt.next # 第一个节点指向第三个节点 nxt.next head # 第二个节点指向第一个节点 prev.next nxt # 前驱指向新的头节点 prev head # 移动前驱 head head.next # 移动当前指针 return dummy.next复杂度分析时间复杂度O(n)空间复杂度O(1)5.3 方法二递归法递归法是解决本题的优雅方式核心思想是每次只处理链表的前两个节点其余部分交给递归函数继续处理。递归三步走终止条件链表为空或只有一个节点无法交换直接返回递归处理先处理后面的节点后序遍历保证后面的链表已经两两交换完成交换当前两个节点将处理好的子链表接到当前交换后的节点后面代码实现Python pythondef swapPairs(head):# 递归终止条件没有节点或只有一个节点if not head or not head.next:return head# 后序遍历先处理后面的节点 next_level swapPairs(head.next.next) # 交换当前两个节点 ret head.next # 保存第二个节点作为新头 ret.next head # 第二个节点指向第一个节点 head.next next_level # 第一个节点指向后面处理好的链表 return ret # 返回新的头节点代码解读第2-3行递归终止条件当链表为空或只有一个节点时无法交换第6行采用后序遍历先处理后面的节点这样在交换当前两个节点时head.next.next 指向的已经是处理好的子链表第9-11行交换当前两个节点并将处理好的子链表接在后面复杂度分析时间复杂度O(n)空间复杂度O(n)递归调用占用系统栈空间六、递归解题的心法通过以上两道题目的学习我们可以总结出递归解题的通用框架6.1 三步法明确递归函数的定义这个函数要做什么参数是什么返回值是什么确定终止条件什么情况下递归应该结束找到递推关系如何将大问题分解为规模更小的相同问题6.2 注意事项不要陷入调用栈的细节递归的本质是不断重复相同的事情我们应该关注的是一级调用的逻辑而不是去思考完整的调用栈先写终止条件这是递归的出口防止无限递归相信递归只要递归函数定义正确就相信它能处理好子问题6.3 两种题型的对比题目 迭代法特点 递归法特点反转链表 双指针逐个反转 将双指针逻辑转化为递归两两交换 哨兵节点 三个指针 后序遍历先处理后面再交换当前

相关新闻

2026/9/5 5:50:08

音频处理核心参数解析:从动态范围到谐波失真的实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/5 5:50:08

STM32 MPU6050数据滤波实战:组合滤波算法原理与实现

最近在调一个用 STM32 读取 MPU6050 的小项目,本以为传感器数据直接拿来用就行,结果一上电就傻眼了:静止状态下,角度数据在 3 之间来回飘,波形图看起来跟心电图似的。网上搜了一圈,发现提“滤波”的教程很多…

2026/9/5 6:40:17

策略模式实战:隐藏复杂逻辑的设计模式核心解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/5 6:40:17

游戏多结局系统设计:从状态管理到条件判定的Java实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/5 6:35:17

面向自由职业群体的远程访问木马钓鱼攻击案例研究

摘要:以 Searzhudin Tamirlanovich Aktulaev 涉案的大规模网络钓鱼犯罪事件为研究样本,完整剖析该犯罪团伙依托自由职业平台消息通道,利用 255 个虚假账号投放带恶意宏的 Excel 文档,部署 TVRAT 与 DarkVNC 两类远程访问木马&…

2026/9/5 2:46:54

vSound小提琴数字处理器实操指南:从接线到演出的完整配置

电小提琴或者原声小提琴插电演出,第一个绕不开的坎就是声音难听。原声琴的共鸣和空气感一旦进了拾音器,出来的往往是一坨干瘪、发尖、带着奇怪塑料味的信号。我当初第一次把琴接上乐队调音台,直接被主唱吐槽"你这声音像在锯钢丝"。…

2026/9/5 2:46:52

传感器接口IC如何攻克生物化学传感的微弱信号难题?

1. 从电极到比特流:为什么生物化学传感必须依赖专用接口IC 做生物化学传感的人都有过类似的经历:明明传感器本身性能很好,信号输出却一塌糊涂——噪声大、漂移明显、重复性差,怎么调都达不到预期。很多时候问题并不在传感器&#…

2026/9/5 2:44:34

STM32F411CEU6多通道ADC采集:扫描模式+DMA实现详解

1. 多通道 ADC 的用武之地把“Multichannel ADC”和“STM32F411CEU6”这两个关键字放在一起,其实就是嵌入式开发里最常遇到的一类需求:用一块不算贵的 MCU,同时采集多路模拟信号。STM32F411CEU6 是 48 引脚的 Cortex-M4F 主控,主频…

2026/9/5 0:04:47

流式背压机制:避免前端渲染卡死与内存暴涨的滑动窗口限流

流式背压机制:避免前端渲染卡死与内存暴涨的滑动窗口限流在大模型流式输出(Streaming)与智能体实时推流的架构中,生产环境中经常出现一种“上下游生产消费速率严重失衡”的极端情况: 生产端极速产出:大模型…

2026/9/5 2:45:13

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/5 2:30:42

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/5 2:46:50

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…