【LeetCode】剑指 Offer 25. 合并两个排序的链表
一、递归
思路:
- 如果有一个链表为空,返回另一个链表
- 如果 l1 结点小于 l2,下一个结点就是 l1,应该 return l1。在 return 之前,指定 l1 的下一个结点为 l1.next 和 l2 两个链表合并后的头结点
- 如果 l1 结点大于 l2,下一个结点就是 l2,应该 return l2。在 return 之前,指定 l2 的下一个结点为 l1 和 l2.next 两个链表合并后的头结点
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1 == null || l2 == null)
return l1 == null ? l2 : l1;
if(l1.val <= l2.val){
l1.next = mergeTwoLists(l1.next,l2);
return l1;
}else{
l2.next = mergeTwoLists(l1,l2.next);
return l2;
}
}
}
- 时间复杂度:O(m+n),m 和 n 分别为两链表长度
- 空间复杂度:O(m+n)
二、伪头节点
根据题目描述,链表 l1,l2 是递增的,因此容易想到使用双指针 l1 和 l2 遍历两链表,根据 l1.val 和 l2.val 的大小关系确定结点添加顺序,两结点指针交替前进,直至遍历完毕
引入伪头结点:由于初始状态合并链表中无结点,因此循环第一轮时无法将结点添加到合并链表中。解决方案:初始化一个辅助结点 dum 作为合并链表的伪头结点,将各结点添加至 dum 之后
算法流程:
- 初始化:伪头结点 dum,结点 cur 指向 dum
- 循环合并:当 l1 或 l2 为空时跳出
- 当 l1.val < l2.val 时:cur 的后继节点指定为 l1,并且 l1 向前走一步
- 当 l1.val ≥ l2.val 时:cur 的后继节点指定为 l2,并且 l2 向前走一步
- 结点 cur 向前走一步,即 cur = cur.next
- 合并剩余尾部:跳出时有两种情况,即 l1 为空或 l2 为空
- 若 l1 ≠ null:将 l1 添加至结点 cur 之后
- 否则:将 l2 添加至结点 cur 之后
- 返回值:合并链表在伪头结点 dum 之后,因此返回 dum.next 即可
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution{
public ListNode mergeTwoLists(ListNode l1, ListNode l2){
ListNode dum = new ListNode(0);
ListNode cur = dum;
while(l1 != null && l2 != null){
if(l1.val < l2.val){
cur.next = l1;
l1 = l1.next;
}else{
cur.next = l2;
l2 = l2.next;
}
}
if(l1 == null || l2 == null)
l1 == null ? cur.next = l2 : cur.next = l1;
return cur.next;
}
}
- 时间复杂度 O(m + n)
- 空间复杂度 O(1):结点引用 dum,cur 使用常数大小的额外空间