5190 字
约 17 分钟
9
021.合并两个有序链表
无标签

021.合并两个有序链表

作者:沉默王二
发布时间:2024-02-02T20:03:13.363+0800


鲁迅说过,每天刷一遍二哥的 LeetCode 刷题笔记,离大厂就会更近一步(dog)。

技术派阅读地址:https://paicoding.com/column/7/21

  • 语雀专栏地址+密码可通过星球置顶帖查看:https://t.zsxq.com/15rEo9Pdu

  • 这道题可以说完全重写了,给出 2 道大家完全都能掌握的题解(递归和遍历)。

  • 增加代码注释,这样大家更容易看懂并掌握题解;

  • 增加参考资料,这样大家可以从零基础直接开刷,减轻新手刷题的阻力;

  • 增加手绘图,这样大家更容易理解解题思路;

  • 增加 ACM 输入输出,这样大家可以在 Intellij IDEA 中进行调试;

  • 增加题解模拟,这样大家可以根据题解模拟整个题解过程,看完会有一种“哦哦”恍然大悟的感觉;

  • 图片上传至 OSS 并配合 CDN,提升大家在技术派网站中的阅读体感。

  • 另外,这一版本会作为 PDF 的终稿随后在星球进行发布。

题意

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

难度

简单

标签

链表、排序

示例

输入:l1 = [1,2,4], l2 = [1,3,4]

输入:l1 = [], l2 = []输出:[]
输入:l1 = [], l2 = [0]输出:[0]

分析 1

先来读题,关键词,两个升序链表,合并,一个新的升序链表。

既然升序过,那最小的数字肯定是两个链表l1和l2中头节点中的一个。

如果最小的是 l1 的头节点,那么第二小的节点肯定是l2的头节点或者l1.next ,只要再比较一下两个的大小就能够得到第二小的节点。

如果最小的是 l2 的头节点,那么第二小的肯定是l1的头节点或者l2.next ;

那我们就可以采用递归的方式:

if (l1.val 	return l1;} else {	l2.next = mergeTwoLists(l1, l2.next);	return l2;}

如果 l1 的头节点小于 l2 的头节点,那么 l1 的 next 应该是 [l1.next](http://l1.next) 或者 l2;

如果 l1 的头节点大于 l2 的头节点,那么 l2 的 next 应该是 l1 或者 [l2.next](http://l2.next);

每次递归调用都会返回当前两个链表头部较小的那个节点作为合并链表的当前节点。

递归的结束条件就是 l1 或者 l2 为空,那么返回另一个链表即可。因为如果其中一个链表为空,就意味着另一个链表已经是当前最终的排序链表了,我们可以直接返回非空链表作为合并后的链表。

// 如果 l1 为空,返回 l2

	return l2;}// 如果 l2 为空,返回 l1if (l2 == null) {	return l1;}

![图片](https://clxhxhhr.top/uploads/weblog/erge/erge_lc_image_14.png)

OK,我们来看完整的题解:

class Solution {

        // 如果 l1 为空,返回 l2        if (l1 == null) {            return l2;        }        // 如果 l2 为空,返回 l1        if (l2 == null) {            return l1;        }                // 选择较小的节点,递归地合并剩余部分        if (l1.val < l2.val) {            l1.next = mergeTwoLists(l1.next, l2);            return l1;        } else {            l2.next = mergeTwoLists(l1, l2.next);            return l2;        }    }}

递归的关键在于理解每一步递归调用都在做什么,以及递归何时结束。在这个问题中,每一步递归都在处理两个链表当前的头节点,并通过递归调用来处理剩下的节点。递归使得问题规模逐渐变小,直至达到简单的基本情况(一个链表为空),从而完成整个合并过程。

我们来看题解的效率:

![图片](https://clxhxhhr.top/uploads/weblog/erge/erge_lc_image_15.png)

### **分析 2**

递归看起来简单,但对于新手来说,理解起来很痛苦,尤其是递归的调用栈,很深。。。深的脑子一片乱麻。

那我们换一种更直接的思路,直接遍历两个链表,依次比较两个链表的节点,将较小的节点放到新链表中。

然后移动指针,继续比较,直到其中一个链表为空,然后将另一个链表剩余的部分直接连接到新链表的末尾。

![图片](https://clxhxhhr.top/uploads/weblog/erge/erge_lc_image_16.png)

图片来自于Krahets

class Solution {

        // 初始化哨兵节点        ListNode sentinel = new ListNode(-1);        // 初始化一个指针,最初指向哨兵节点        ListNode prev = sentinel;                // 当两个链表都不为空时,循环继续        while (l1 != null && l2 != null) {            if (l1.val <= l2.val) {                prev.next = l1;                l1 = l1.next;            } else {                prev.next = l2;                l2 = l2.next;            }            // 移动prev指针            prev = prev.next;        }                // 直接将非空链表剩余部分连接到新链表末尾        prev.next = l1 == null ? l2 : l1;                // 返回哨兵节点的下一个节点,即合并后链表的头节点        return sentinel.next;    }}

这个题解的效率也非常不错,但更容易理解一些:

![图片](https://clxhxhhr.top/uploads/weblog/erge/erge_lc_image_17.png)

### **总结**

这道题目是链表的基础题,递归和迭代都可以解决,递归的思路更加简洁,但是对于新手来说,理解起来可能会有些困难,迭代的思路更加直接,但是代码量会多一些。

关键还是理解链表的数据结构,这个视频讲的不错,推荐一手。

视频链接:**[https://www.bilibili.com/video/BV1ea4y1r75V](https://www.bilibili.com/video/BV1ea4y1r75V)**

还有这个网站,对链表也进行了详细的讲解,推荐一手。

网站链接:**[https://www.hello-algo.com/chapter_array_and_linkedlist/linked_list/#1](https://www.hello-algo.com/chapter_array_and_linkedlist/linked_list/#1)**

链表另外一个关键点在于理解指针的移动,比如说 prev = [prev.next](http://prev.next),这个操作不只是移动指针,还相当于删除了 prev 节点,大家可以感受一下。

力扣链接:**[https://leetcode.cn/problems/merge-two-sorted-lists/](https://leetcode.cn/problems/merge-two-sorted-lists/)**

### **一步一个脚印**

不积跬步无以至千里,不积小流无以成江海。LeetCode - 100天从算法小白到卷王正式启动了,我们计划周一到周五至少每天更新一篇,周六周日更新一篇,目标 300 道 LeetCode 经典题。

该题解目前只针对**[二哥编程星球](https://javabetter.cn/zhishixingqiu/)**的球友开放,如果你也想一起打卡学习算法的话,扫下面的优惠券加入我们吧,星球目前 4300+ 人,定价 149 元,从当初的 99 元一路涨上来的,所以早就是优势,别再犹豫了。

![图片](https://clxhxhhr.top/uploads/weblog/erge/erge_lc_image_18.png)

(优惠券仅 300 张,数量有限)

一个人可以走得很快,但一群人才能走得更远。如果想去面中大厂的话,LeetCode 几乎是必刷的,二哥的 LeetCode 题解通俗易懂、图文并茂,理解给你举一反三的解题能力,冲鸭。

        
        
鲁迅说过,每天刷一遍二哥的 LeetCode 刷题笔记,离大厂就会更近一步(dog)。

技术派阅读地址:[https://paicoding.com/column/7/21](https://paicoding.com/column/7/21)

- 语雀专栏地址+密码可通过星球置顶帖查看:[https://t.zsxq.com/15rEo9Pdu](https://t.zsxq.com/15rEo9Pdu)
- 这道题可以说完全重写了,给出  2 道大家完全都能掌握的题解(递归和遍历)。

- 增加代码注释,这样大家更容易看懂并掌握题解;
- 增加参考资料,这样大家可以从零基础直接开刷,减轻新手刷题的阻力;
- 增加手绘图,这样大家更容易理解解题思路;
- 增加 ACM 输入输出,这样大家可以在 Intellij IDEA 中进行调试;
- 增加题解模拟,这样大家可以根据题解模拟整个题解过程,看完会有一种“哦哦”恍然大悟的感觉;
- 图片上传至 OSS 并配合 CDN,提升大家在技术派网站中的阅读体感。
- 另外,这一版本会作为 PDF 的终稿随后在星球进行发布。

### **题意**

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

### **难度**

简单

### **标签**

链表、排序

### **示例**

![图片](https://clxhxhhr.top/uploads/weblog/erge/erge_lc_image_13.png)

输入:l1 = [1,2,4], l2 = [1,3,4]


输入:l1 = [], l2 = []输出:[]
输入:l1 = [], l2 = [0]输出:[0]

### **分析 1**

先来读题,关键词,两个升序链表,合并,一个新的升序链表。

既然升序过,那最小的数字肯定是两个链表l1和l2中头节点中的一个。

如果最小的是 l1 的头节点,那么第二小的节点肯定是l2的头节点或者[l1.next](http://l1.next),只要再比较一下两个的大小就能够得到第二小的节点。

如果最小的是 l2 的头节点,那么第二小的肯定是l1的头节点或者[l2.next](http://l2.next);

那我们就可以采用递归的方式:

if (l1.val return l1;} else { l2.next = mergeTwoLists(l1, l2.next); return l2;}

如果 l1 的头节点小于 l2 的头节点,那么 l1 的 next 应该是 l1.next 或者 l2;

如果 l1 的头节点大于 l2 的头节点,那么 l2 的 next 应该是 l1 或者 l2.next ;

每次递归调用都会返回当前两个链表头部较小的那个节点作为合并链表的当前节点。

递归的结束条件就是 l1 或者 l2 为空,那么返回另一个链表即可。因为如果其中一个链表为空,就意味着另一个链表已经是当前最终的排序链表了,我们可以直接返回非空链表作为合并后的链表。

// 如果 l1 为空,返回 l2
return l2;}// 如果 l2 为空,返回 l1if (l2 == null) {	return l1;}

OK,我们来看完整的题解:

class Solution {
    // 如果 l1 为空,返回 l2        if (l1 == null) {            return l2;        }        // 如果 l2 为空,返回 l1        if (l2 == null) {            return l1;        }                // 选择较小的节点,递归地合并剩余部分        if (l1.val < l2.val) {            l1.next = mergeTwoLists(l1.next, l2);            return l1;        } else {            l2.next = mergeTwoLists(l1, l2.next);            return l2;        }    }}

递归的关键在于理解每一步递归调用都在做什么,以及递归何时结束。在这个问题中,每一步递归都在处理两个链表当前的头节点,并通过递归调用来处理剩下的节点。递归使得问题规模逐渐变小,直至达到简单的基本情况(一个链表为空),从而完成整个合并过程。

我们来看题解的效率:

分析 2

递归看起来简单,但对于新手来说,理解起来很痛苦,尤其是递归的调用栈,很深。。。深的脑子一片乱麻。

那我们换一种更直接的思路,直接遍历两个链表,依次比较两个链表的节点,将较小的节点放到新链表中。

然后移动指针,继续比较,直到其中一个链表为空,然后将另一个链表剩余的部分直接连接到新链表的末尾。

图片来自于Krahets

class Solution {
    // 初始化哨兵节点        ListNode sentinel = new ListNode(-1);        // 初始化一个指针,最初指向哨兵节点        ListNode prev = sentinel;                // 当两个链表都不为空时,循环继续        while (l1 != null && l2 != null) {            if (l1.val <= l2.val) {                prev.next = l1;                l1 = l1.next;            } else {                prev.next = l2;                l2 = l2.next;            }            // 移动prev指针            prev = prev.next;        }                // 直接将非空链表剩余部分连接到新链表末尾        prev.next = l1 == null ? l2 : l1;                // 返回哨兵节点的下一个节点,即合并后链表的头节点        return sentinel.next;    }}

这个题解的效率也非常不错,但更容易理解一些:

总结

这道题目是链表的基础题,递归和迭代都可以解决,递归的思路更加简洁,但是对于新手来说,理解起来可能会有些困难,迭代的思路更加直接,但是代码量会多一些。

关键还是理解链表的数据结构,这个视频讲的不错,推荐一手。

视频链接:https://www.bilibili.com/video/BV1ea4y1r75V

还有这个网站,对链表也进行了详细的讲解,推荐一手。

网站链接:https://www.hello-algo.com/chapter_array_and_linkedlist/linked_list/#1

链表另外一个关键点在于理解指针的移动,比如说 prev = prev.next ,这个操作不只是移动指针,还相当于删除了 prev 节点,大家可以感受一下。

力扣链接:https://leetcode.cn/problems/merge-two-sorted-lists/

一步一个脚印

不积跬步无以至千里,不积小流无以成江海。LeetCode - 100天从算法小白到卷王正式启动了,我们计划周一到周五至少每天更新一篇,周六周日更新一篇,目标 300 道 LeetCode 经典题。

该题解目前只针对**二哥编程星球 **的球友开放,如果你也想一起打卡学习算法的话,扫下面的优惠券加入我们吧,星球目前 4300+ 人,定价 149 元,从当初的 99 元一路涨上来的,所以早就是优势,别再犹豫了。

(优惠券仅 300 张,数量有限)

一个人可以走得很快,但一群人才能走得更远。如果想去面中大厂的话,LeetCode 几乎是必刷的,二哥的 LeetCode 题解通俗易懂、图文并茂,理解给你举一反三的解题能力,冲鸭。

鲁迅说过,每天刷一遍二哥的 LeetCode 刷题笔记,离大厂就会更近一步(dog)。

技术派阅读地址:https://paicoding.com/column/7/21

  • 语雀专栏地址+密码可通过星球置顶帖查看:https://t.zsxq.com/15rEo9Pdu

  • 这道题可以说完全重写了,给出 2 道大家完全都能掌握的题解(递归和遍历)。

  • 增加代码注释,这样大家更容易看懂并掌握题解;

  • 增加参考资料,这样大家可以从零基础直接开刷,减轻新手刷题的阻力;

  • 增加手绘图,这样大家更容易理解解题思路;

  • 增加 ACM 输入输出,这样大家可以在 Intellij IDEA 中进行调试;

  • 增加题解模拟,这样大家可以根据题解模拟整个题解过程,看完会有一种“哦哦”恍然大悟的感觉;

  • 图片上传至 OSS 并配合 CDN,提升大家在技术派网站中的阅读体感。

  • 另外,这一版本会作为 PDF 的终稿随后在星球进行发布。

题意

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

难度

简单

标签

链表、排序

示例

输入:l1 = [1,2,4], l2 = [1,3,4]

输入:l1 = [], l2 = []输出:[]
输入:l1 = [], l2 = [0]输出:[0]

分析 1

先来读题,关键词,两个升序链表,合并,一个新的升序链表。

既然升序过,那最小的数字肯定是两个链表l1和l2中头节点中的一个。

如果最小的是 l1 的头节点,那么第二小的节点肯定是l2的头节点或者l1.next ,只要再比较一下两个的大小就能够得到第二小的节点。

如果最小的是 l2 的头节点,那么第二小的肯定是l1的头节点或者l2.next ;

那我们就可以采用递归的方式:

if (l1.val 	return l1;} else {	l2.next = mergeTwoLists(l1, l2.next);	return l2;}

如果 l1 的头节点小于 l2 的头节点,那么 l1 的 next 应该是 [l1.next](http://l1.next) 或者 l2;

如果 l1 的头节点大于 l2 的头节点,那么 l2 的 next 应该是 l1 或者 [l2.next](http://l2.next);

每次递归调用都会返回当前两个链表头部较小的那个节点作为合并链表的当前节点。

递归的结束条件就是 l1 或者 l2 为空,那么返回另一个链表即可。因为如果其中一个链表为空,就意味着另一个链表已经是当前最终的排序链表了,我们可以直接返回非空链表作为合并后的链表。

// 如果 l1 为空,返回 l2

	return l2;}// 如果 l2 为空,返回 l1if (l2 == null) {	return l1;}

![图片](https://clxhxhhr.top/uploads/weblog/erge/erge_lc_image_14.png)

OK,我们来看完整的题解:

class Solution {

        // 如果 l1 为空,返回 l2        if (l1 == null) {            return l2;        }        // 如果 l2 为空,返回 l1        if (l2 == null) {            return l1;        }                // 选择较小的节点,递归地合并剩余部分        if (l1.val < l2.val) {            l1.next = mergeTwoLists(l1.next, l2);            return l1;        } else {            l2.next = mergeTwoLists(l1, l2.next);            return l2;        }    }}

递归的关键在于理解每一步递归调用都在做什么,以及递归何时结束。在这个问题中,每一步递归都在处理两个链表当前的头节点,并通过递归调用来处理剩下的节点。递归使得问题规模逐渐变小,直至达到简单的基本情况(一个链表为空),从而完成整个合并过程。

我们来看题解的效率:

![图片](https://clxhxhhr.top/uploads/weblog/erge/erge_lc_image_15.png)

### **分析 2**

递归看起来简单,但对于新手来说,理解起来很痛苦,尤其是递归的调用栈,很深。。。深的脑子一片乱麻。

那我们换一种更直接的思路,直接遍历两个链表,依次比较两个链表的节点,将较小的节点放到新链表中。

然后移动指针,继续比较,直到其中一个链表为空,然后将另一个链表剩余的部分直接连接到新链表的末尾。

![图片](https://clxhxhhr.top/uploads/weblog/erge/erge_lc_image_16.png)

图片来自于Krahets

class Solution {

    // 初始化哨兵节点        ListNode sentinel = new ListNode(-1);        // 初始化一个指针,最初指向哨兵节点        ListNode prev = sentinel;                // 当两个链表都不为空时,循环继续        while (l1 != null && l2 != null) {            if (l1.val <= l2.val) {                prev.next = l1;                l1 = l1.next;            } else {                prev.next = l2;                l2 = l2.next;            }            // 移动prev指针            prev = prev.next;        }                // 直接将非空链表剩余部分连接到新链表末尾        prev.next = l1 == null ? l2 : l1;                // 返回哨兵节点的下一个节点,即合并后链表的头节点        return sentinel.next;    }}

这个题解的效率也非常不错,但更容易理解一些:

总结

这道题目是链表的基础题,递归和迭代都可以解决,递归的思路更加简洁,但是对于新手来说,理解起来可能会有些困难,迭代的思路更加直接,但是代码量会多一些。

关键还是理解链表的数据结构,这个视频讲的不错,推荐一手。

视频链接:https://www.bilibili.com/video/BV1ea4y1r75V

还有这个网站,对链表也进行了详细的讲解,推荐一手。

网站链接:https://www.hello-algo.com/chapter_array_and_linkedlist/linked_list/#1

链表另外一个关键点在于理解指针的移动,比如说 prev = prev.next ,这个操作不只是移动指针,还相当于删除了 prev 节点,大家可以感受一下。

力扣链接:https://leetcode.cn/problems/merge-two-sorted-lists/

一步一个脚印

不积跬步无以至千里,不积小流无以成江海。LeetCode - 100天从算法小白到卷王正式启动了,我们计划周一到周五至少每天更新一篇,周六周日更新一篇,目标 300 道 LeetCode 经典题。

该题解目前只针对**二哥编程星球 **的球友开放,如果你也想一起打卡学习算法的话,扫下面的优惠券加入我们吧,星球目前 4300+ 人,定价 149 元,从当初的 99 元一路涨上来的,所以早就是优势,别再犹豫了。

(优惠券仅 300 张,数量有限)

一个人可以走得很快,但一群人才能走得更远。如果想去面中大厂的话,LeetCode 几乎是必刷的,二哥的 LeetCode 题解通俗易懂、图文并茂,理解给你举一反三的解题能力,冲鸭。

            知识星球
            
    
    
        
        扫码加入星球
        查看更多优质内容
    
    https://wx.zsxq.com/mweb/views/joingroup/join_group.html?group_id=15522885221412
021.合并两个有序链表
https://clxhxhhr.top/posts/2446/
作者
clxstart
发布于
2026-09-19
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。