Day109 | 灵神 | 148.排序链表 | 归并排序
148. 排序链表 - 力扣(LeetCode)
以下是灵神的题解,笔者认为这题只要可以看懂就好了
两种方法:分治和迭代
前置题目
方法一:归并排序(分治)
- 找到链表的中间结点 head2 的前一个节点,并断开 head2 与其前一个节点的连接。这样我们就把原链表均分成了两段更短的链表
- 分治,递归调用 sortList,分别排序 head(只有前一半)和 head2。
- 排序后,我们得到了两个有序链表,那么合并两个有序链表,得到排序后的链表,返回链表头节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| class Solution { ListNode* middleNode(ListNode* head) { ListNode* pre = head; ListNode* slow = head; ListNode* fast = head; while (fast && fast->next) { pre = slow; slow = slow->next; fast = fast->next->next; } pre->next = nullptr; return slow; }
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) { ListNode dummy; ListNode* cur = &dummy; while (list1 && list2) { if (list1->val < list2->val) { cur->next = list1; list1 = list1->next; } else { cur->next = list2; list2 = list2->next; } cur = cur->next; } cur->next = list1 ? list1 : list2; return dummy.next; }
public: ListNode* sortList(ListNode* head) { if (head == nullptr || head->next == nullptr) { return head; } ListNode* head2 = middleNode(head); head = sortList(head); head2 = sortList(head2); return mergeTwoLists(head, head2); } };
|
复杂度分析
- 时间复杂度:O(nlogn),其中 n 是链表长度。递归式 T(n)=2T(n/2)+O(n),由主定理可得时间复杂度为 O(nlogn)。
- 空间复杂度:O(logn)。递归需要 O(logn) 的栈开销。
方法二:归并排序(迭代)
方法一的归并是自顶向下计算,需要 O(logn) 的递归栈开销。
方法二将其改成自底向上计算,空间复杂度优化成 O(1)。
自底向上的意思是:
- 首先,归并长度为 1 的子链表。例如 [4,2,1,3],把第一个节点和第二个节点归并,第三个节点和第四个节点归并,得到 [2,4,1,3]。
- 然后,归并长度为 2 的子链表。例如 [2,4,1,3],把前两个节点和后两个节点归并,得到 [1,2,3,4]。
- 然后,归并长度为 4 的子链表。
- 依此类推,直到归并的长度大于等于链表长度为止,此时链表已经是有序的了。
具体算法:
- 遍历链表,获取链表长度 length。
- 初始化步长 step=1。
- 循环直到 step≥length。
- 每轮循环,从链表头节点开始。
- 分割出两段长为 step 的链表,合并,把合并后的链表插到新链表的末尾。重复该步骤,直到链表遍历完毕。
- 把 step 扩大一倍。回到第 4 步。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| class Solution { int getListLength(ListNode* head) { int length = 0; while (head) { length++; head = head->next; } return length; }
ListNode* splitList(ListNode* head, int size) { ListNode* cur = head; for (int i = 0; i < size - 1 && cur; i++) { cur = cur->next; }
if (cur == nullptr || cur->next == nullptr) { return nullptr; }
ListNode* next_head = cur->next; cur->next = nullptr; return next_head; }
pair<ListNode*, ListNode*> mergeTwoLists(ListNode* list1, ListNode* list2) { ListNode dummy; ListNode* cur = &dummy; while (list1 && list2) { if (list1->val < list2->val) { cur->next = list1; list1 = list1->next; } else { cur->next = list2; list2 = list2->next; } cur = cur->next; } cur->next = list1 ? list1 : list2; while (cur->next) { cur = cur->next; } return {dummy.next, cur}; }
public: ListNode* sortList(ListNode* head) { int length = getListLength(head); ListNode dummy(0, head); for (int step = 1; step < length; step *= 2) { ListNode* new_list_tail = &dummy; ListNode* cur = dummy.next; while (cur) { ListNode* head1 = cur; ListNode* head2 = splitList(head1, step); cur = splitList(head2, step); auto [head, tail] = mergeTwoLists(head1, head2); new_list_tail->next = head; new_list_tail = tail; } } return dummy.next; } };
|
复杂度分析
- 时间复杂度:O(nlogn),其中 n 是链表长度。
- 空间复杂度:O(1)。