leetcode 148. 排序链表

在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。

示例 1:

输入: 4->2->1->3
输出: 1->2->3->4
示例 2:

输入: -1->5->3->4->0
输出: -1->0->3->4->5

不考虑常数级空间复杂度时,可以采用递归的做法:

找到当前链表的中点后的一个节点, 将链表从中间断开,然后对两半链表进行merge,递归的完成这个操作

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    // 将两根链表合并,并返回链表的第一个节点
    private ListNode merge(ListNode a,ListNode b){
        ListNode dummy=new ListNode(0);
        ListNode tail=dummy;
        while(a!=null||b!=null){
            if(a==null){
                tail.next=b;
                break;
            }else if(b==null){
                tail.next=a;
                break;
            }else{
                // 均不为null
                if(a.val<b.val){
                    tail.next=a;
                    tail=a;
                    a=a.next;
                }else{
                    tail.next=b;
                    tail=b;
                    b=b.next;
                }
            }
        }
        return dummy.next;
    }
    public ListNode sortList(ListNode head) {
         if(head==null||head.next==null){
             return head;
         }
         ListNode fast,slow;
         fast=head.next;
         slow=head;
         while(fast!=null&&fast.next!=null){
             fast=fast.next.next;
             slow=slow.next;
         }
         ListNode t=slow.next;
         slow.next=null;
         return merge(sortList(head),sortList(t));
    }
}

注意寻找中点时,快指针一开始应该是head.next,而不是head,否则找到的不是链表中点,甚至会导致栈溢出:

比如4->2->null,此时fast到达null,slow到达2,然后将t设为slow->next即null,然后slow->next置为空,接着sortlist(head),最终导致爆栈

 

因为使用了栈实现递归,所以上述不是常数级空间复杂度,要符合这个要求,我们可以使用自底向上的归并排序,其中涉及到两个关键操作,cut和merge,merge和上述相同,cut则是将传入链表从第n个节点处断开,并返回后半部分的第一个节点

实现了这两个函数之后,我们需要从头到尾,首先是每两个一merge,然后是每4个一merge,直到排序完成


class Solution {
    // 将两根链表合并,并返回链表的第一个节点
    private ListNode merge(ListNode a, ListNode b) {
        ListNode dummy = new ListNode(0);
        ListNode tail = dummy;
        while (a != null || b != null) {
            if (a == null) {
                tail.next = b;
                break;
            } else if (b == null) {
                tail.next = a;
                break;
            } else {
                // 均不为null
                if (a.val < b.val) {
                    tail.next = a;
                    tail = a;
                    a = a.next;
                } else {
                    tail.next = b;
                    tail = b;
                    b = b.next;
                }
            }
        }
        return dummy.next;
    }

    private ListNode cut(ListNode head, int n) {
        int c = 1;
        ListNode tail = head;
        while (c < n && tail != null) {
            c++;
            tail = tail.next;
        }
        if (tail == null) {
            return null;
        }
        ListNode t = tail.next;
        tail.next = null;
        return t;
    }

    public ListNode sortList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        // 首先算出链表长度
        int len = 0;
        ListNode t = head;
        while (t != null) {
            t = t.next;
            len++;
        }
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        // 自底向上的归并排序
        for (int sz = 1; sz < len; sz *= 2) {
            // 此处应该设置为dummy.next,而不是head,因为head在下面的变换中会变化
            // 而dummy始终不变
            ListNode cur = dummy.next;
            // 这里不是指向head,是因为要将后面归并的链表(包括head)放在dummy.next,而不是head.next
            ListNode tail = dummy;
            // 使用当前sz,归并完当前链表
            while (cur != null) {
                ListNode left = cur;
                ListNode right = cut(cur, sz);
                cur = cut(right, sz);
                tail.next = merge(left, right);
                // tail始终指向已排序链表的末尾
                while (tail.next != null) {
                    tail = tail.next;
                }
            }
        }
        return dummy.next;
    }


}

细节是魔鬼,链表的自底向上的归并需要多看几遍

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值