力扣148——排序鏈表

原題

在 O(n log n) 時間複雜度和常數級空間複雜度下,對鏈表進行排序。java

示例 1:node

輸入: 4->2->1->3
輸出: 1->2->3->4

示例 2:git

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

原題url:https://leetcode-cn.com/probl...github

解決

題目很明確,排序,對於時間複雜度和空間複雜度有要求,針對O(n log n),讓我想到了歸併排序快速排序,接下來咱們各自來看看。segmentfault

對了,這裏先統一放一下節點類,單向鏈表中的節點,存儲當前節點的值和後一個節點的引用。測試

Definition for singly-linked list.
public class ListNode {
    int val;
    ListNode next;
    ListNode(int x) { val = x; }
}

歸併排序

歸併排序,說白了,就是先分解到最小單元,而後逐個進行合併並排序,這樣在合併的時候,其實兩個鏈表自己就是有序的,那麼當有一個所有取完後,另外一個能夠直接拼接在最後面。優化

讓咱們看一下代碼:url

public class Solution {
    public ListNode sortList(ListNode head) {
        // 歸併排序
        
        if (head == null || head.next == null) {
            return head;
        }

        // 先分隔,利用快慢指針分隔。
        // 快指針先走,由於只有當空節點或1個節點纔是終止條件,2個節點的時候,若是不讓快指針先走,而是也指向head,那麼2個節點永遠不會被分隔,會陷入死循環
        ListNode fast = head.next.next;
        ListNode slow = head;
        while (true) {
            if (fast == null || fast.next == null) {
                break;
            }

            fast = fast.next.next;
            slow = slow.next;
        }
        // 後半部分的開頭
        ListNode second = slow.next;
        second = sortList(second);
        // 前半部分的開頭
        slow.next = null;
        ListNode first = head;
        first = sortList(first);

        // 合併
        ListNode result = new ListNode(0);
        head = result;
        while (first != null && second != null) {
            if (first.val < second.val) {
                result.next = first;
                first = first.next;
            } else {
                result.next = second;
                second = second.next;
            }
            result = result.next;
        }
        if (first != null) {
            result.next = first;
        } else {
            result.next = second;
        }

        return head.next;
    }
}

提交OK,執行用時:5 ms,內存消耗:39.6 MB,執行用時只打敗了59.07%的 java 提交記錄,應該還有優化的空間。spa

歸併排序——優化

針對上面的代碼,在分隔的時候,設置fast = head.next.next,這是由於咱們設置的遞歸終止條件是針對null或者單個節點的。其實當只剩下兩個節點的時候,就能夠進行排序了,這樣應該能夠節省近一半的時間,固然了,從時間複雜度上來講並無改變。指針

咱們看一下代碼:

public class Solution {
    public ListNode sortList(ListNode head) {
        // 歸併排序
        if (head == null || head.next == null) {
            return head;
        }

        // 說明只有兩個節點
        if (head.next.next == null) {
            ListNode second = head.next;
            if (head.val > second.val) {
                return head;
            } else {
                second.next = head;
                head.next = null;
                return second;
            }
        }

        // 先分隔,利用快慢指針分隔。
        ListNode fast = head;
        ListNode slow = head;
        while (true) {
            if (fast == null || fast.next == null) {
                break;
            }

            fast = fast.next.next;
            slow = slow.next;
        }
        // 後半部分的開頭
        ListNode second = slow.next;
        second = sortList(second);
        // 前半部分的開頭
        slow.next = null;
        ListNode first = head;
        first = sortList(first);

        // 合併
        ListNode result = new ListNode(0);
        head = result;
        while (first != null && second != null) {
            if (first.val < second.val) {
                result.next = first;
                first = first.next;
            } else {
                result.next = second;
                second = second.next;
            }
            result = result.next;
        }
        if (first != null) {
            result.next = first;
        } else {
            result.next = second;
        }

        return head.next;
    }
}

執行用時,有的時候是4 ms,有的時候是3 ms,看來歸併排序這條路差很少就是這樣了。

快速排序

快速排序的思想就是選擇一個標準值,將比它大的和比它的小的,作交換。針對鏈表這種結構,就是將比它大的放在一個鏈表中,比它小的放在一個鏈表中,和它同樣大的,放在另外一個鏈表中。而後針對小的和大的鏈表,繼續排序。最終將三個鏈表按照小、相等、大進行鏈接。

接下來讓咱們看看代碼:

class Solution {
        public ListNode sortList(ListNode head) {
            // 利用快排

            // 單個節點是終止節點
            if (head == null || head.next == null) {
                return head;
            }
            // 比標準值小的節點
            ListNode lowHead = new ListNode(0);
            ListNode low = lowHead;
            // 和標準值同樣的節點
            ListNode midHead = new ListNode(0);
            ListNode mid = midHead;
            // 比標準值大的節點
            ListNode highHead = new ListNode(0);
            ListNode high = highHead;

            // 標準值
            int val = head.val;
            ListNode node = head;
            // 遍歷
            while (node != null) {
                // 比標準值大的節點
                if (node.val > val) {
                    high.next = node;
                    high = high.next;
                }
                // 比標準值小的節點
                else if (node.val < val) {
                    low.next = node;
                    low = low.next;
                }
                // 和標準值同樣的節點
                else {
                    mid.next = node;
                    mid = mid.next;
                }
                node = node.next;
            }
            // 終止,避免形成環
            low.next = null;
            high.next = null;

            lowHead.next = sortList(lowHead.next);
            highHead.next = sortList(highHead.next);

            // 找出小節點鏈表的末尾
            low = lowHead;
            while (low.next != null) {
                low = low.next;
            }
            // 拼接
            low.next = midHead.next;
            mid.next = highHead.next;

            return lowHead.next;
        }
    }

提交OK,執行用時:2 ms,內存消耗:40.01 MB

和歸併排序相比,時間更短,至於緣由,我確實是沒有想明白,由於都須要比較,而後從新構造新鏈表。我猜想是測試數據離散程度更高,這樣歸併排序的話,並無充分利用其特性:

當兩個鏈表合併時,若是一個鏈表已經所有結束,另外一個鏈表剩餘的部分能夠直接拼接。

總結

以上就是這道題目個人解答過程了,不知道你們是否理解了。針對它的時間複雜度要求,利用歸併排序或者快速排序解決。

有興趣的話能夠訪問個人博客或者關注個人公衆號、頭條號,說不定會有意外的驚喜。

https://death00.github.io/

公衆號:健程之道

相關文章
相關標籤/搜索