力扣21:合并两个有序链表

本文详细介绍了如何使用迭代和归并两种方法对两个已排序的链表进行合并。在迭代法中,通过维护一个预头节点和已排序表尾指针,逐步合并两个链表。而在归并法中,利用递归思想,根据链表头节点的值决定合并顺序。最终,无论是迭代还是归并,都能在O(n+m)的时间复杂度和有限的空间复杂度内完成链表的合并。
摘要由CSDN通过智能技术生成

原题链接
说实话我看到这题吓到了,然后多少心态有点崩,连代码都没写就直接投降了。我确实有一个思路——不过这个思路也是我大学时听了《数据结构与算法》课听到的,大致简述如下:
两张表同时遍历,然后将比较两个表的头节点,小的节点并入已排序的表中,大的节点继续保留;然后下一次遍历继续比较两个表的头节点。
我之后自己又尝试用迭代去写,用了unsortedHead和sortedEnd两个指针来指向未排序的表头和排序的表尾,很明显我的思路是错的。
使用迭代的正确方法是:用一个prehead指向已排序表的表头,用于作为函数返回值;用一个prev指向已排序表的表尾。l1和l2两个引用变量是动态指向不断缩短的l1和l2表头的。实际上我认为其实用一个引用变量作为标记——或者说维护已排序表的表尾就好了,但是一直没想到具体怎么做。我认为可能是没有意识到可以用l1=l1->next这样覆盖原引用变量的方法来更新指针吧
而官方解答也确实是这样的思路,采用了归并和迭代两种处理方法。

官方解答

迭代

迭代的思路在上面已经说明了,补充一下循环迭代结束后的思路。在循环结束后,l1和l2至多有一个是非空的,而非空链表的剩余最后一个元素一定比已排序链表中的所有元素都大,因此我们只需要直接将其接到已排序链表的尾部即可。

/**
 * 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; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        ListNode preHead = new ListNode(-1);
        ListNode prev = preHead;//注意preHead和prev不是指针,是引用
        while(l1 != null && l2 != null){//prev指向了l1或l2表的尽头
            if(l1.val<=l2.val){
                prev.next=l1;
                l1=l1.next;
            }else{
                prev.next=l2;
                l2=l2.next;
            }
            prev=prev.next;//prev也要前移

        }
        // 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
        // 要么是 l1 为 null 要么是 l2 为 null
        prev.next = l1 == null ? l2 : l1;
        return preHead.next;
    }
}

时间复杂度是 O ( n + m ) O(n+m) O(n+m),因为我们需要处理n+m个元素;空间复杂度是 O ( 1 ) O(1) O(1)。因为无论l1和l2多长我们只需要preHead和prev两个变量的存储空间。

归并

可以递归地定义两个链表里的merge操作。
l 1 [ 0 ] < l 2 [ 0 ] l1[0]<l2[0] l1[0]<l2[0]时:
l 1 [ 0 ] + m e r g e ( l 1 [ 1 : ] , l 2 ) l1[0]+merge(l1[1:],l2) l1[0]+merge(l1[1:],l2)
否则:
l 2 [ 0 ] + m e r g e ( l 1 , l 2 [ 1 : ] ) l2[0]+merge(l1,l2[1:]) l2[0]+merge(l1,l2[1:])
我们需要的结果就是两个链表头部较小的一个节点与剩下元素的merge操作结果合并。
我们要考虑边界情况(也是递归调用的终止情况):如果l1或l2其中有一个是空链表,那么我们只需要返回两者中非空的一者。否则我们要判断l1和l2中哪一个链表的头节点的值更小,更小的那一个头节点就作为剩余元素归并后的表的头节点。

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) {
            return l2;
        } else if (l2 == null) {
            return l1;
        } else 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 ) O(m+n) O(m+n),因为每次调用都会去掉l1表或者l2表的头节点;空间复杂度为 O ( m + n ) O(m+n) O(m+n),递归调用mergeTwoLists函数需要消耗栈空间,栈空间的大小取决于递归调用的深度。因此递归调用时mergeTwoList函数最多调用n+m次。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值