leetcode hot100 - 148. 排序連結串列
阿新 • • 發佈:2020-10-07
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)