题意
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
难度
简单
标签
链表、排序
示例
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
输入:l1 = [], l2 = []
输出:[]
输入:l1 = [], l2 = [0]
输出:[0]
复制代码
分析 1
先来读题,关键词,两个升序链表,合并,一个新的升序链表。
既然升序过,那最小的数字肯定是两个链表l1和l2中头节点中的一个。
如果最小的是 l1 的头节点,那么第二小的节点肯定是l2的头节点或者l1.next,只要再比较一下两个的大小就能够得到第二小的节点。
如果最小的是 l2 的头节点,那么第二小的肯定是l1的头节点或者l2.next;
那我们就可以采用递归的方式:
if (l1.val < l2.val) {
l1.next = mergeTwoLists(l1.next, l2);
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
if (l1 == null) {
return l2;
}
// 如果 l2 为空,返回 l1
if (l2 == null) {
return l1;
}
OK,我们来看完整的题解:
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
// 如果 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
递归看起来简单,但对于新手来说,理解起来很痛苦,尤其是递归的调用栈,很深。。。深的脑子一片乱麻。
那我们换一种更直接的思路,直接遍历两个链表,依次比较两个链表的节点,将较小的节点放到新链表中。
然后移动指针,继续比较,直到其中一个链表为空,然后将另一个链表剩余的部分直接连接到新链表的末尾。
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
// 初始化哨兵节点
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;
}
}
/**
* 用循环遍历的方式
*
* 直接遍历两个链表,依次比较两个链表的节点,将较小的节点放到新链表中。
*
* 然后移动指针,继续比较,直到其中一个链表为空,然后将另一个链表剩余的部分直接连接到新链表的末尾。
* @param l1
* @param l2
* @return
*/
public ListNode mergeTwoLists2(ListNode l1 ,ListNode l2){
//1初始化哨兵节点
ListNode sentinel = new ListNode(-1);
//2初始化一个指针,最初指向哨兵节点
ListNode prev = sentinel;
//3当两个链表不为空时,循环遍历两个链表,用prev连接后续节点
while (l1 != null&& l2 != null ){
//找最小节点开头
if (l1.val <=l2.val){
//指针 链表的下一个节点为 l1 头节点
prev.next = l1;
//更新 l1 链表的节点,变成后一个节点
l1 = l1.next;
}else {
prev.next = l2;
//后移
l2 = l2.next;
}
//移动prev 。。指向 指针链表的 下一个节点 指向的为 刚才 拼接上的节点
prev = prev.next;
}
//直接将非空链表剩余部分连接到新建表末尾
prev.next = l1 ==null ? l2:l1;
// 返回哨兵节点的下一个节点,即合并后链表的头节点
return sentinel.next;
}
这个题解的效率也非常不错,但更容易理解一些:
总结
这道题目是链表的基础题,递归和迭代都可以解决,递归的思路更加简洁,但是对于新手来说,理解起来可能会有些困难,迭代的思路更加直接,但是代码量会多一些。
关键还是理解链表的数据结构,这个视频讲的不错,推荐一手。
视频链接:链表 数据结构与算法,完整代码动画版,附在线数据结构交互式工具_哔哩哔哩_bilibili
还有这个网站,对链表也进行了详细的讲解,推荐一手。
网站链接:4.2 链表 - Hello 算法
链表另外一个关键点在于理解指针的移动,比如说 prev = prev.next,这个操作不只是移动指针,还相当于删除了 prev 节点,大家可以感受一下。
力扣链接:. - 力扣(LeetCode)