1. 程式人生 > 實用技巧 >leetcode hot100 - 148. 排序連結串列

leetcode hot100 - 148. 排序連結串列

148. 排序連結串列

在O(nlogn) 時間複雜度和常數級空間複雜度下,對連結串列進行排序。

示例 1:

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

示例 2:

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

思路一:遞迴 + 歸併

思路參考:https://leetcode-cn.com/problems/sort-list/solution/sort-list-gui-bing-pai-xu-lian-biao-by-jyd/

使用遞迴歸併排序

快慢指標找到連結串列中點後分割連結串列為左右兩段,遞迴分割左右連結串列,然後合併兩個連結串列,返回合併後的結果

 1 class Solution {
 2     public ListNode sortList(ListNode head) {
 3 
 4         // 使用遞迴歸併排序
 5         if(head == null || head.next == null){
 6             return head;
 7         }
 8 
 9         // 快慢指標找到連結串列中點後分割連結串列為左右兩段
10         ListNode slow = head, fast = head.next;
11         while(fast != null
&& fast.next != null){ 12 slow = slow.next; 13 fast = fast.next.next; 14 } 15 ListNode tmp = slow.next; // 第二段連結串列,前面的作為第一段連結串列 16 slow.next = null; // 第一段連結串列和第二段連結串列斷開連線 17 18 // 遞迴分割左右連結串列 19 ListNode left = sortList(head);
20 ListNode right = sortList(tmp); 21 22 ListNode res = new ListNode(); // 定義一個空節點,簡化合並操作 23 ListNode cur = res; // 24 // 合併兩個連結串列 25 while(left != null && right != null){ 26 if(left.val < right.val){ 27 cur.next = left; 28 left = left.next; 29 }else{ 30 cur.next = right; 31 right = right.next; 32 } 33 cur = cur.next; 34 } 35 cur.next = left == null ? right : left; 36 return res.next; 37 } 38 }

leetcode執行用時:3 ms > 98.99%, 記憶體消耗:40.9 MB > 66.19%

複雜度分析:

時間複雜度:歸併排序的時間複雜度為O(nlogn), 雖然多了一個遍歷連結串列找中點的操作,但是對於同一層的連結串列,複雜度為O(n), 遞迴深度為O(logn), 所以時間複雜度為O(nlogn)

空間複雜度:沒有藉助額外的容器,但是遞迴本身需要一個遞迴棧,棧的深度為O(nlogn), 所以空間複雜度為O(nlogn)

思路二:迭代 + 歸併

思路參考:https://leetcode-cn.com/problems/sort-list/comments/

雖然上面解法通過了測試,但是其實是不符合題意的,因為題目要求空間複雜度為O(1), 但是我們使用遞迴後空間複雜度明顯不是O(1),所以需要把遞迴方式改成迭代方式。

 1 class Solution {
 2     public ListNode sortList(ListNode head) {
 3         // 非遞迴歸併排序
 4         
 5         // 先統計連結串列長度
 6         ListNode tmp = new ListNode();
 7         tmp.next = head;
 8         ListNode p = head;
 9         int length = 0;
10         while(p != null){
11             p = p.next;
12             length++;
13         }
14 
15         // 主迴圈,控制每次歸併的連結串列的大小,從1-n/2,成倍擴大
16         for(int size = 1; size < length; size <<= 1){
17             ListNode cur = tmp.next;
18             ListNode tail = tmp;    // 簡化連線
19 
20             while(cur != null){
21                 // 從連結串列中取下固定大小的結點,取兩段
22                 ListNode left = cur;
23                 ListNode right = cut(left, size);
24                 cur = cut(right, size);             // 更新cur指標的位置
25                 // 歸併這兩段
26 
27                 tail.next = mergeList(left, right);
28 
29                 // 把歸併後的結果掛到有序連結串列尾部
30                 while(tail.next != null){
31                     tail = tail.next;
32                 }
33             }
34         }
35         return tmp.next;
36     }
37 
38     // 從連結串列中取下固定大小的結點
39     public ListNode cut(ListNode head, int n){
40         ListNode p = head;
41         // 先跳過n個結點
42         while(--n != 0 && p != null){
43             p = p.next;
44         }
45         if(p == null){
46             return p;
47         }
48         ListNode next = p.next;
49         // 返回跳過size個結點的指標
50         p.next = null;
51         return next;
52     }
53 
54     // 合併兩個連結串列
55     public ListNode mergeList(ListNode left, ListNode right){
56         ListNode res = new ListNode();
57         ListNode cur = res;
58         while(left != null && right != null){
59             if(left.val < right.val){
60                 cur.next = left;
61                 left = left.next;
62             }else{
63                 cur.next = right;
64                 right = right.next;
65             }
66             cur = cur.next;
67         }
68         cur.next = (left != null ? left : right);
69         return res.next;
70     }
71 }

希望同學們能把雙路歸併和 cut 斷鏈的程式碼爛記於心,以後看到類似的題目能夠刷到手軟。

leetcode執行用時:7 ms > 27.29%, 記憶體消耗:40.9 MB > 63.86%

複雜度分析:

時間複雜度:這個就是標準的歸併排序,所以時間複雜度為O(nlogn)

空間複雜度:沒有借用集合,有沒有借用遞迴,所以複雜度為O(1)