LeetCode--合并有序链表总结

这篇博客介绍了如何合并两个及多个有序链表,从LeetCode的21题出发,详细分析了直接合并和递归合并的方法,并探讨了合并K个有序链表的优化策略,包括使用堆和分治思想,讨论了不同方法的时间复杂度和空间复杂度。
摘要由CSDN通过智能技术生成

合并两个有序的单链表为一个链表,原题目链接:LeetCode--21. Merge Two Sorted Lists

原题目:

Merge two sorted linked lists and return it as a new list. The new list should be made by splicing together the nodes of the first two lists.

Example:

Input: 1->2->4, 1->3->4
Output: 1->1->2->3->4->4

分析:本题应该是比较简单的,直接按照大小顺序合并两个链表就好了,需要注意的是,这里返回的新链表的头结点肯定是输入的两个链表中较小的一个,一般不确定最终返回链表的头结点时,我们会使用一个不包含任何数据,next指向真正的头结点的“虚结点”,有些书上也把这种结构称为带表头的链表。好处就是可以像处理其他结点一样来处理头结点,(我们知道在处理头结点的时候需要特别小心,在头结点出插入删除元素都需要改变头结点,而在其他位置插入,删除结点是不需要改变头结点的)

下面看AC代码:

    public ListNode mergeTwoLists0(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(0);  // 表头节点,next域指向真正的头结点
        ListNode cur = dummy;
        while (l1 != null && l2 != null) {
            cur.next = l1.val < l2.val ? l1 : l2;
            cur = cur.next;
            if (l1.val < l2.val) {
                l1 = l1.next;
            } else {
                l2 = l2.next;
            }
        }
        cur.next = l1 != null ? l1 : l2;
        return dummy.next;
    }

本题还有更简洁的递归版本,需要大家好好理解下,有时候递归能写出特别漂亮的程序,就是不好想!

    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) return l2;
        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;
        }
    }

分析完了合并两个链表,下面我们来看合并K个有序链表一个怎样操作,LeetCode--23. Merge k Sorted Lists

原题目:

Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity.

Example:

Input:
[
  1->4->5,
  1->3->4,
  2->6
]
Output: 1->1->2->3->4->4->5->6

本题是上一题的扩展,需要合并k个有序的链表。

分析:最为直观的解法就是两两合并,最后合并成一个大链表了,下面是AC代码:

    public ListNode mergeKLists(ListNode[] lists) {
        if (lists == null || lists.length == 0) return null;
        ListNode res = null;
        for (ListNode l : lists) {
            res = mergeTwoLists(l, res);
        }
        return res;
    }

其中,mergeTwoLists方法就是上面合并两个链表的方法。

这里我们假设链表的个数为K个,K个链表包含的结点个数和为N。

使用上诉方法,很容易的知道,每个结点被访问的平均次数为 K/2,所以时间复杂度为O(NK),空间复杂度为O(1)。

接着来分析:由于每一次我们只需要比较K个链表的第一个结点,选出最小的哪一个作为新链表的下一个结点,所以我们可以使用一个堆来维护这个性质,下面是AC代码:

    public ListNode mergeKLists(ListNode[] lists) {
        if (lists == null || lists.length == 0) return null;
        PriorityQueue<ListNode> pq = new PriorityQueue<>((n1, n2) -> n1.val - n2.val);
        ListNode dummy = new ListNode(0);  // 表头结点
        ListNode cur = dummy;
        for (ListNode node : lists)
            if (node != null)
                pq.add(node);

        while (!pq.isEmpty()) {
            cur.next = pq.poll();
            cur = cur.next;
            if (cur.next != null) {
                pq.add(cur.next);
            }
        }
        return dummy.next;
    }

其中也用到了表头结点。

堆中最大元素数量为k个,每次调整时时间复杂度为O(lgK),总共有N个元素,所以时间复杂度为O(NlgK),空间复杂度为O(K)。

接着分析:对于上诉的两两合并,我们还可以进一步的改进,我们以K=8(l1,l2,l3,l4,l5,l6,l7,l8)为例:

在两两合并的方法中:第一趟:合并 l1 + l2

                                    第二趟:合并 l1 + l2 + l3

                                    第三趟:合并 l1 + l2 + l3 + l4

                                    第四趟:合并 l1 + l2 + l3 + l4 + l5

                                    第五趟:合并 l1 + l2 +l3 + l4 + l5 + l6

                                    第六趟:合并 l1 + l2 +l3 + l4 + l5 + l6 + l7

                                    第七趟:合并 l1 + l2 +l3 + l4 + l5 + l6 + l7 + l8

改进:第一趟,合并 l1 + l2    l3 + l4    l5 + l6    l7 + l8

           第二趟,合并l1 + l2 + l3 + l4     l5 + l6 + l7 + l8

           第三趟,合并 l1 + l2 +l3 + l4 + l5 + l6 + l7 + l8

可以看到在改进的方法中,每个元素被访问的次数为O(lgK),所以时间复杂度为O(NlgK),空间复杂度为O(1).

下面是AC代码:

    public ListNode mergeKLists(ListNode[] lists) {
        if (lists == null || lists.length == 0) return null;
        return mergeKLists(lists, 0, lists.length);
    }
    private ListNode mergeKLists(ListNode[] lists, int start, int stop) {
        if (start + 1 == stop) return lists[start];
        return mergeTwoLists(mergeKLists(lists, start, (start + stop) / 2),
                mergeKLists(lists, (start + stop) / 2, stop));
    }

好了,我们下期见!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值