leetcode 148 排序链表

82 阅读3分钟

这是一道中等题,如果不考虑题目的进阶要求,可能就是一道简单题吧,因为你可以在直接使用一个O(n)的空间进行解决的话,是十分简单的,但是如果严格按照题目进阶要求,我个人认为这还是算一道困难题。因此折中就为中等题。

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

因为我曾经看过算法书,里面就有归并排序,不过这是关于数组的问题,我也是记得是有两种优化方法:

思路一 :从上到下的分治方法,也就是递归方法,类似于二分查找在数组中,在链表中也很容易想到使用快慢指针定位中点,分完后,又利用一个O(n)的空间的数组进行归并,链表也就不需要多余的空间,只需要改变指针即可。乍一看,好像符合题目要求,当时我也是这么觉得的,很遗憾该题解不合要求,时间复杂度合格,但是空间复杂度因为递归就会产生logn的空间复杂度。

代码

public ListNode sortList(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode res = DFS(head);
        return res;
    }
//    由上至下的递归归并排序,时间复杂度合格,空间复杂度不为常数级别。为递归调用的栈空间O(logn)
    private ListNode DFS(ListNode node){
        if (node.next == null) return node;
        ListNode f = node;
        ListNode s = node;
        while (f.next != null && f.next.next != null){
            s = s.next;
            f = f.next.next;
        }
        f = s.next;
        s.next = null;
        ListNode node1 = DFS(node);
        ListNode node2 = DFS(f);
        s = merge(node1,node2);
        return s;
    }
    private ListNode merge(ListNode node1, ListNode node2){
       ListNode res = new ListNode();
       ListNode pointer = res;
       while (node1 != null || node2 != null){
           if (node1 == null){
               pointer.next = node2;
               break;
           }
           else if (node2 == null) {
               pointer.next = node1;
               break;
           }
           else if (node1.val > node2.val) {
               pointer.next = node2;
               node2 = node2.next;
           }else{
               pointer.next = node1;
               node1 = node1.next;
           }
           pointer = pointer.next;
       }
       return res.next;

思路二 后来我看题解很多人说到该问题,于是我就看了第二种方法,也就是迭代法,不过说实话,当初我看书的时候,就没有看太懂,这也是一个分治方法,这两种方法同数组几乎一样,但是相对于链表来说,第二种方法更好,因为其只需要o(1)也就是常数级别的空间,具体思路就是从下到上的分治,首先分为 1 进行合并,其次就是2、4、8、16、、、直到大于链表的空间,听起来思路还是很简单,我写了好久,真的麻了。。。。。。

代码

public ListNode sortList(ListNode head) {
    if (head == null || head.next == null) return head;
    ListNode dummy = new ListNode(-1,head);
    int length = 0;
    ListNode node = head;
    while (node != null){
        node = node.next;
        length++;
    }
    ListNode pre;
    ListNode cur;
    for (int subLength = 1; subLength < length; subLength *= 2){
        pre = dummy;
        cur = dummy.next;
        while (cur != null){
            ListNode head1 = spilt(cur, subLength);
            ListNode nextHead = spilt(head1,subLength);
            ListNode listNode = merge(cur, head1);
            cur = nextHead;
            pre.next = listNode;
            while (listNode.next != null) listNode = listNode.next;
            pre = listNode;
        }
    }
    return dummy.next;
}
private ListNode merge(ListNode head1, ListNode head2){
    ListNode dummy = new ListNode(-1);
    ListNode pointer = dummy;
    while (head1 != null || head2 != null){
        if (head1 == null) {
            pointer.next = head2;
            break;
        }
        if (head2 == null) {
            pointer.next = head1;
            break;
        }
        if (head1.val > head2.val){
            pointer.next = head2;
            head2 = head2.next;
        }else {
            pointer.next = head1;
            head1 = head1.next;
        }
        pointer = pointer.next;
    }
    return dummy.next;
}
private ListNode spilt(ListNode node, int subLength){
    if (node == null) return null;
    ListNode nextHead;
    for (int i = 1; i < subLength && node.next != null; i++) {
        node = node.next;
    }
    nextHead = node.next;
    node.next = null;
    return nextHead;
}

注意 两者的merge方法不一样,还是得多学习官方的解法也就是多封装复用的地方。 相对于来说第一种方法更易于理解,更简单。但是如果面试时遇到这题,不用想面试官需要你写的是第二种方法,就不要想那些投机取巧的方法了。真的难啊。。。。。