【LeetCode热题100精讲】Java实现「合并两个有序链表」:递归 vs 迭代|深度解析 + 面试高频考点 + 实战优化指南

🚀【LeetCode热题100精讲】Java实现「合并两个有序链表」:递归 vs 迭代|深度解析 + 面试高频考点 + 实战优化指南

关键词:LeetCode 21、合并两个有序链表、链表合并、递归、迭代、时间复杂度、空间复杂度、链表算法、Java实现、面试高频题、LeetCode Hot 100
适用人群:准备技术面试的开发者、算法初学者、Java工程师、数据结构学习者
字数:约 9500 字
阅读建议:配合代码逐段理解,动手调试示例,文末附完整可运行代码与扩展练习


🔍 一、原题回顾:LeetCode 第21题 —— 合并两个有序链表

📌 题目描述

将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

✅ 示例说明
  • 示例 1

    输入:l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,4]
  • 示例 2

    输入:l1 = [], l2 = [] 输出:[]
  • 示例 3

    输入:l1 = [], l2 = [0] 输出:[0]
⚠️ 约束条件
  • 两个链表的节点数目范围是[0, 50]
  • -100 <= Node.val <= 100
  • l1l2均按非递减顺序排列(即允许相等)

💡提示:本题是 LeetCode “Hot 100” 中的经典链表题,也是大厂面试(如字节、腾讯、阿里)的高频考点,常作为“双指针”或“递归思维”的入门题。


🧠 二、问题分析:为什么这道题值得深入研究?

虽然题目看似简单,但其背后蕴含了链表操作的核心思想

  • 如何在不创建新节点的前提下复用原有节点
  • 如何优雅处理空链表边界情况
  • 递归 vs 迭代:两种范式如何体现不同编程思维?
  • 如何保证时间/空间效率最优

更重要的是,该问题的解法可直接迁移到更复杂的场景,如:

  • 合并 K 个有序链表
  • 外排序中的多路归并
  • 分布式系统中的日志合并

因此,掌握本题不仅是刷题需要,更是夯实基础算法能力的关键一步。


🛠️ 三、解题思路构思:从直觉到算法设计

3.1 核心观察

由于两个链表均已升序排列,我们只需依次比较两个链表的当前头节点,选择较小者加入结果链表,并移动对应指针。这一过程天然适合双指针策略。

🎯关键洞察
每次只需关注两个链表的“当前最小值”,无需回溯或重排,这是贪心策略的典型应用。

3.2 两种主流解法

方法思想优点缺点
递归自顶向下分解子问题代码简洁,逻辑清晰递归栈开销大,可能栈溢出
迭代自底向上逐步构建空间复杂度 O(1),生产友好需手动维护指针,略显繁琐

我们将分别详细剖析这两种方法。


✅ 四、完整解法实现(Java)

4.1 方法一:递归实现(Recursion)

📜 代码实现
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */classSolution{/** * 递归合并两个有序链表 * * @param l1 链表1头节点 * @param l2 链表2头节点 * @return 合并后的升序链表头节点 */publicListNodemergeTwoLists(ListNodel1,ListNodel2){// 边界条件:任一链表为空,直接返回另一个if(l1==null)returnl2;if(l2==null)returnl1;// 递归核心:选择较小节点作为当前头,并递归处理剩余部分if(l1.val<l2.val){l1.next=mergeTwoLists(l1.next,l2);returnl1;}else{l2.next=mergeTwoLists(l1,l2.next);returnl2;}}}
🔍 递归逻辑图解

l1 = [1→2→4],l2 = [1→3→4]为例:

merge([1,2,4], [1,3,4]) ├─ 1 ≤ 1 → 选 l2 的 1 │ └─ merge([1,2,4], [3,4]) │ ├─ 1 < 3 → 选 l1 的 1 │ │ └─ merge([2,4], [3,4]) │ │ ├─ 2 < 3 → 选 l1 的 2 │ │ │ └─ merge([4], [3,4]) │ │ │ ├─ 4 > 3 → 选 l2 的 3 │ │ │ │ └─ merge([4], [4]) │ │ │ │ ├─ 4 ≤ 4 → 选 l1 的 4 │ │ │ │ │ └─ merge(null, [4]) → 返回 [4] │ │ │ │ └─ 最终链:4 → 4 │ │ │ └─ 链:3 → 4 → 4 │ │ └─ 链:2 → 3 → 4 → 4 │ └─ 链:1 → 2 → 3 → 4 → 4 └─ 链:1 → 1 → 2 → 3 → 4 → 4

💡小贴士:递归本质是“分治”——每次解决一个最小单元(当前最小节点),然后委托子问题处理剩余部分。


4.2 方法二:迭代实现(Iteration)

📜 代码实现
classSolution{/** * 迭代合并两个有序链表(推荐生产环境使用) * * @param l1 链表1头节点 * @param l2 链表2头节点 * @return 合并后的升序链表头节点 */publicListNodemergeTwoLists(ListNodel1,ListNodel2){// 创建哨兵节点(dummy node),简化边界处理ListNodedummy=newListNode(-1);ListNodecurrent=dummy;// current 指向结果链表的尾部// 双指针遍历两个链表while(l1!=null&&l2!=null){if(l1.val<=l2.val){current.next=l1;// 接上 l1 当前节点l1=l1.next;// l1 指针后移}else{current.next=l2;// 接上 l2 当前节点l2=l2.next;// l2 指针后移}current=current.next;// 结果链表尾指针后移}// 处理剩余非空链表(最多一个非空)current.next=(l1!=null)?l1:l2;returndummy.next;// 返回真实头节点(跳过哨兵)}}
🔍 迭代过程可视化
步骤l1l2current.next说明
初始1→2→41→3→4-1dummy = [-1]
11→2→41→3→41 (l2)1≤1,选 l2,l2=3→4
21→2→43→41 (l1)13,选 l2,l2=4
5444 (l1)4≤4,选 l1,l1=null
结束null44接上剩余 l2

最终链表:-1 → 1 → 1 → 2 → 3 → 4 → 4,返回dummy.next[1,1,2,3,4,4]

优势:哨兵节点(dummy)避免了对“结果链表是否为空”的判断,极大简化逻辑。


📊 五、复杂度分析:时间与空间效率对比

方法时间复杂度空间复杂度说明
递归O(m + n)O(m + n)每个节点访问一次;递归深度 = m+n,栈空间消耗大
迭代O(m + n)O(1)仅使用常数额外变量(dummy, current, l1, l2)

📌结论

  • 面试展示:递归写法简洁,适合快速写出正确解。
  • 工程实践:迭代写法更优,避免栈溢出风险,符合生产代码规范。

❓ 六、常见问题解答(FAQ)

Q1:为什么迭代法要使用“哨兵节点”(dummy node)?

:哨兵节点是一个不存储有效数据的虚拟头节点,其作用是:

  • 避免在循环前判断结果链表是否为空
  • 统一插入逻辑(始终操作current.next
  • 最终通过dummy.next直接获取真实头节点

💡类比:就像链表的“锚点”,让整个构建过程更稳定。


Q2:如果链表很长(如百万级节点),递归会有什么问题?

:Java 默认栈深度有限(通常几千层)。若m + n > 栈深度,将抛出StackOverflowError
解决方案:强制使用迭代法,或增大 JVM 栈大小(不推荐,治标不治本)。


Q3:能否原地修改链表而不创建新节点?

:可以!上述两种方法均复用原链表节点,仅调整next指针,未创建新ListNode对象,属于原地合并


Q4:相等元素如何处理?会影响稳定性吗?

:本题中,当l1.val == l2.val时,我们优先选择l1(递归)或根据<=选择l1(迭代)。
这保证了相对顺序不变,即来自l1的相等元素排在l2之前,属于稳定合并


🧪 七、调试技巧与测试用例设计

7.1 推荐测试用例

// 测试工具类(可本地运行)publicclassMergeTwoListsTest{publicstaticvoidmain(String[]args){Solutionsol=newSolution();// Case 1: 正常合并ListNodel1=buildList(newint[]{1,2,4});ListNodel2=buildList(newint[]{1,3,4});printList(sol.mergeTwoLists(l1,l2));// [1,1,2,3,4,4]// Case 2: 空链表printList(sol.mergeTwoLists(null,null));// []// Case 3: 一个为空printList(sol.mergeTwoLists(null,buildList(newint[]{0})));// [0]// Case 4: 全相等l1=buildList(newint[]{1,1,1});l2=buildList(newint[]{1,1});printList(sol.mergeTwoLists(l1,l2));// [1,1,1,1,1]// Case 5: 交错大小l1=buildList(newint[]{2,4,6});l2=buildList(newint[]{1,3,5});printList(sol.mergeTwoLists(l1,l2));// [1,2,3,4,5,6]}privatestaticListNodebuildList(int[]vals){if(vals.length==0)returnnull;ListNodehead=newListNode(vals[0]);ListNodecur=head;for(inti=1;i<vals.length;i++){cur.next=newListNode(vals[i]);cur=cur.next;}returnhead;}privatestaticvoidprintList(ListNodehead){List<Integer>res=newArrayList<>();while(head!=null){res.add(head.val);head=head.next;}System.out.println(res);}}

7.2 调试建议

  • 画图辅助:手动画出指针移动过程,尤其注意currentl1l2的变化。
  • 断点跟踪:在 IDE 中设置断点,观察每一步next指针的指向。
  • 边界覆盖:务必测试空链表、单节点、全相等等极端情况。

🧱 八、数据结构与算法基础回顾

8.1 单链表(Singly Linked List)核心特性

  • 结构:每个节点包含val(数据)和next(指向下一节点的引用)
  • 优点:插入/删除 O(1)(已知位置),动态扩容
  • 缺点:随机访问 O(n),需顺序遍历
  • 内存布局:节点在堆中非连续存储

8.2 递归 vs 迭代:编程范式对比

维度递归迭代
思维方式自顶向下,分治自底向上,逐步构建
代码长度稍长
空间开销高(函数调用栈)低(仅局部变量)
可读性高(贴近数学定义)中(需理解指针操作)
适用场景问题可自然分解为子问题循环逻辑清晰,性能敏感场景

📚延伸阅读:《算法导论》第4章(分治策略)、第10章(基本数据结构)


💼 九、面试官提问环节(模拟)

❓ 问题1:你能解释一下递归解法的终止条件吗?

考察点:边界处理意识
期望回答
“当任一链表为空时,无需再比较,直接返回另一个链表即可。因为空链表对合并结果无贡献,且非空链表本身已有序。”


❓ 问题2:如果要求合并后的链表去重,该如何修改?

考察点:需求变更应对能力
参考方案(迭代法修改):

if(l1.val<l2.val){current.next=l1;l1=l1.next;}elseif(l1.val>l2.val){current.next=l2;l2=l2.next;}else{// 相等时只取一个,并跳过所有重复current.next=l1;intval=l1.val;while(l1!=null&&l1.val==val)l1=l1.next;while(l2!=null&&l2.val==val)l2=l2.next;}current=current.next;

❓ 问题3:这个解法能扩展到合并 K 个有序链表吗?

考察点:知识迁移能力
期望回答
“可以。一种高效方法是使用最小堆(优先队列)

  1. 将 K 个链表的头节点加入堆
  2. 弹出最小节点加入结果,并将其下一个节点入堆
  3. 重复直到堆空
    时间复杂度 O(N log K),N 为总节点数。”

🌐 十、实际开发中的应用场景

10.1 数据库查询优化

在 SQL 执行计划中,Merge Join算法正是基于“合并两个有序流”的思想。若两个表已按连接键排序,则可高效合并,避免嵌套循环。

10.2 日志系统归并

分布式系统中,多个服务实例产生的日志按时间戳排序。聚合中心需将这些有序日志流合并为全局有序日志,用于审计或监控。

10.3 外排序(External Sorting)

当数据量超过内存时,外排序将数据分块排序后写入磁盘,最后通过多路归并(K-way merge)合并所有有序块。本题是其最简形式(2路归并)。

10.4 Git 版本合并

Git 在合并两个分支时,若提交历史呈线性且有序,可采用类似策略快速生成合并提交。


🔗 十一、相关题目推荐(LeetCode)

题号题目难度关联点
23合并K个升序链表困难本题的泛化,需用堆优化
148排序链表中等归并排序在链表上的应用
328奇偶链表中等链表拆分与重组
2两数相加中等链表遍历与进位处理
25K 个一组翻转链表困难链表分段操作

📌学习路径建议:先掌握本题 → 尝试 148(归并排序) → 挑战 23(K路归并)


🧩 十二、代码健壮性增强建议

12.1 添加输入校验(工程实践)

publicListNodemergeTwoLists(ListNodel1,ListNodel2){// 虽然题目保证非空,但生产代码建议防御性编程if(l1==null&&l2==null)returnnull;if(l1==null)returnl2;if(l2==null)returnl1;// ... 后续逻辑}

12.2 使用 Optional(Java 8+ 风格)

publicOptional<ListNode>mergeTwoListsOpt(ListNodel1,ListNodel2){if(l1==null&&l2==null)returnOptional.empty();// ... 合并逻辑returnOptional.of(resultHead);}

⚠️注意:LeetCode 环境不支持 Optional 返回,仅作工程参考。


📌 十三、总结与延伸思考

✅ 核心收获

  1. 链表操作精髓:通过调整next指针实现结构重组,避免新建节点。
  2. 双指针技巧:同步遍历两个有序序列,是处理“合并”、“交集”等问题的通用模式。
  3. 哨兵节点价值:简化边界逻辑,提升代码鲁棒性。
  4. 递归思维训练:将大问题分解为“当前步骤 + 子问题”。

🔮 延伸思考

  • 如果链表是降序的,如何合并?
    → 反向比较(>替代<),或先反转再合并。

  • 能否用快慢指针优化?
    → 本题不需要,快慢指针适用于找中点、判环等场景。

  • 并发环境下如何安全合并?
    → 需加锁或使用不可变链表(Immutable List)。


📎 附录:完整可运行代码(含测试)

// ListNode 定义classListNode{intval;ListNodenext;ListNode(){}ListNode(intval){this.val=val;}ListNode(intval,ListNodenext){this.val=val;this.next=next;}}// 解法类classSolution{// 递归版publicListNodemergeTwoListsRecursive(ListNodel1,ListNodel2){if(l1==null)returnl2;if(l2==null)returnl1;if(l1.val<l2.val){l1.next=mergeTwoListsRecursive(l1.next,l2);returnl1;}else{l2.next=mergeTwoListsRecursive(l1,l2.next);returnl2;}}// 迭代版(推荐)publicListNodemergeTwoLists(ListNodel1,ListNodel2){ListNodedummy=newListNode(-1);ListNodecurrent=dummy;while(l1!=null&&l2!=null){if(l1.val<=l2.val){current.next=l1;l1=l1.next;}else{current.next=l2;l2=l2.next;}current=current.next;}current.next=l1!=null?l1:l2;returndummy.next;}}// 测试类(本地运行)publicclassMain{publicstaticvoidmain(String[]args){Solutionsol=newSolution();ListNodel1=newListNode(1,newListNode(2,newListNode(4)));ListNodel2=newListNode(1,newListNode(3,newListNode(4)));ListNoderesult=sol.mergeTwoLists(l1,l2);// 打印结果: 1->1->2->3->4->4while(result!=null){System.out.print(result.val+" ");result=result.next;}}}

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

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

相关文章

【LeetCode热题100精讲】Java实现「两数相加」:链表模拟大数加法|深度解析 + 面试高频考点 + 实战优化指南

&#x1f680;【LeetCode热题100精讲】Java实现「两数相加」&#xff1a;链表模拟大数加法&#xff5c;深度解析 面试高频考点 实战优化指南 关键词&#xff1a;LeetCode 2、两数相加、链表加法、大数运算、进位处理、Java实现、面试高频题、LeetCode Hot 100 适用人群&#…

django-flask基于python的高校奖助学金申请管理系统

目录高校奖助学金申请管理系统摘要关于博主开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;高校奖助学金申请管理系统摘要 该系统基于Python语言&#xff0c;采用Django和Flask框架开发&…

鸿蒙应用的未来发展趋势与前沿技术探索

&#x1f31f; 鸿蒙应用的未来发展趋势与前沿技术探索 一、章节概述 ✅ 学习目标 理解鸿蒙应用开发的未来发展趋势&#xff08;全场景体验、轻量化开发、AI原生应用、云原生部署、安全可信&#xff09;探索鸿蒙前沿技术&#xff08;元宇宙应用、量子计算、脑机接口、5G/6G通信、…

django-flask基于python的高校教师教学档案管理系统

目录高校教师教学档案管理系统摘要关于博主开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;高校教师教学档案管理系统摘要 该系统基于Python语言&#xff0c;采用Django或Flask框架开发&…

军工仿真软件如何实现三维公式与Word数据联动至XHEDITOR?

Word一键转存CMS升级方案 项目背景与需求分析 作为山西软件工程专业的大三学生&#xff0c;我正在给自己的CMS新闻管理系统添加Word一键转存功能。核心需求包括&#xff1a; 富文本粘贴&#xff1a;支持Word内容粘贴并保留完整样式自动上传&#xff1a;图片自动上传到阿里云…

救命神器8个AI论文写作软件,自考学生轻松搞定论文格式规范!

救命神器8个AI论文写作软件&#xff0c;自考学生轻松搞定论文格式规范&#xff01; 自考论文写作的救星&#xff1a;AI 工具如何改变你的学习节奏 在自考的路上&#xff0c;论文写作总是让人又爱又恨。它不仅是对知识的总结&#xff0c;更是对逻辑与表达能力的考验。然而&#…

django-flask基于python的高校教师职称评定管理系统

目录高校教师职称评定管理系统的设计与实现系统架构与技术栈核心功能模块系统特色与创新应用价值关于博主开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;高校教师职称评定管理系统的设计与实…

django-flask基于python的高校教材信息管理系统

目录高校教材信息管理系统摘要关于博主开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;高校教材信息管理系统摘要 该系统基于Python的Django和Flask框架开发&#xff0c;旨在实现高校教材信…

django-flask基于python的大学校内医务室就诊信息管理系统的设计与实现

目录摘要关于博主开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;摘要 随着高校规模的扩大和信息化建设的深入&#xff0c;校内医务室的管理效率和服务质量成为重要课题。传统的纸质记录和人…

django-flask基于python的大学校园失物招领平台的设计与实现

目录摘要关于博主开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;摘要 随着高校规模的扩大和师生人数的增加&#xff0c;校园内物品遗失现象频发&#xff0c;传统的线下失物招领方式效率低下…

计算机视觉“奥运会”:CVPR, ICCV, ECCV 全解析

点击 “AladdinEdu&#xff0c;你的AI学习实践工作坊”&#xff0c;注册即送-H卡级别算力&#xff0c;沉浸式云原生集成开发环境&#xff0c;80G大显存多卡并行&#xff0c;按量弹性计费&#xff0c;教育用户更享超低价。 计算机视觉“奥运会”&#xff1a;CVPR&#xff0c; IC…

AI 适合什么语言开发 —— 从工程现实出发的判断

随着 AI 参与软件开发的比例不断上升&#xff0c;一个越来越现实的问题摆在工程团队面前&#xff1a;并不是所有编程语言&#xff0c;都同样适合“AI 参与式开发”。有些语言在 AI 的加持下效率指数级提升&#xff0c;而有些语言则会在长期维护阶段成本急剧放大。本文从语言特性…

你的 IP 归属地,是咋被挖出来的?

你是小阿巴&#xff0c;正在家里偷偷欣赏一部很精彩的内容。 你&#xff1a;嗯&#xff0c;真不错啊~一时兴起&#xff0c;你留下了一条评论&#xff1a;作者牛掰&#xff01; 结果刚发出去&#xff0c;你就发现评论下面竟然显示&#xff1a;/* by 01130.hk - online tools web…

研究多 Agent 系统中的涌现行为—通过局部规则设计实现全局系统目标的方法

研究多 Agent 系统中的涌现行为—通过局部规则设计实现全局系统目标的方法 在人工智能与复杂系统研究中&#xff0c;多 Agent 系统&#xff08;Multi-Agent System, MAS&#xff09;展现出一个重要而独特的现象——涌现行为&#xff08;Emergent Behavior&#xff09;。所谓涌现…

亚马逊“防割韭菜式运营”:把 80% 的坑前置掉,你会轻松很多

很多卖家做亚马逊的痛感不是“赚不到钱”&#xff0c;而是“赚到的钱总被各种问题吞掉”&#xff1a;库存一紧就慌、广告一涨就怕、账号一出警告就崩。归根结底&#xff0c;是团队把大量精力花在“救火”&#xff0c;而不是“把坑提前填平”。这篇给你一套“防割韭菜式运营”框…

2026年38款人性化设计项目管理软件,使用感超舒适惬意

作为常年周旋于多项目统筹、跨团队协作的资深项目经理&#xff0c;你是否也曾因软件界面繁琐、操作逻辑晦涩、学习成本过高而倍感疲惫&#xff1f;在追求高效办公的当下&#xff0c;项目管理软件的“人性化设计”已成为核心竞争力——它不仅是工具&#xff0c;更是提升团队幸福…

2小时,我搭了一套生产质检闭环系统,返工、漏检一次性解决

你有没有遇到过这种情况——机器轰隆隆转了一整天&#xff0c;产量报表看着漂亮&#xff0c;结果一到出货之后&#xff0c;客户一个电话打过来&#xff1a;“你们这质量怎么回事&#xff1f;” 然后整个车间开始翻箱倒柜找问题批次……最离谱的是&#xff0c;这种事不是第一次&…

亚马逊新品冷启动别再硬砸钱:一套“3阶段打法”把预算花在刀刃上

很多新品死在同一个原因&#xff1a;还没证明“页面能卖”就开始猛推广告。结果是点击不少、转化跟不上&#xff0c;ACOS 一路飙&#xff0c;最后团队得出结论&#xff1a;“产品不行”。其实很多时候不是产品不行&#xff0c;是启动顺序错了。下面给你一套能直接照做的新品冷启…

反向工程:《辛普森一家》与人类本能解析

在《谁枪击了伯恩斯先生&#xff1f;》第二集中&#xff0c;斯米瑟斯喝醉后走在人行道上&#xff0c;突然遭到贾斯珀比尔兹利的激烈对峙。 "你给我冷静点&#xff01;人行道是给正经走路用的&#xff0c;不是用来花式走路的&#xff01;"贾斯珀抓着斯米瑟斯的肩膀说…

身心灵产业为何会崛起?背后的推手是谁?

身心灵产业是以身体、心理、灵性三者协调统一为核心&#xff0c;聚焦“身心健康精神成长意义重构”的大健康细分领域&#xff0c;核心是帮人缓解压力、找回情绪平衡与内在价值&#xff0c;属于“疗愈经济”核心赛道。一、核心定义与底层逻辑身体&#xff1a;生理层面&#xff0…