148. 排序链表
148. 排序链表
在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
示例 1:
输入: 4->2->1->3
输出: 1->2->3->4
输出: 1->2->3->4
示例 2:
输入: -1->5->3->4->0
输出: -1->0->3->4->5
输出: -1->0->3->4->5
思路:
题目要求时间空间复杂度分别为O(nlogn)和O(1),根据时间复杂度我们自然想到二分法,从而联想到归并排序;
对数组做归并排序的空间复杂度为 O(n),分别由新开辟数组O(n)和递归函数调用O(logn)组成,而根据链表特性:
合并 merge 环节: 将两个排序链表合并,转化为一个排序链表。
当题目输入的 head == None 时,直接返回None。
根据以上推论,我们可以仅根据intv计算每个单元边界,并完成链表的每轮排序合并,例如:
此方法时间复杂度O(nlogn),空间复杂度O(1)。
在每轮intv下的合并流程:
合并长度为c1, c2的h1, h2链表,其中:
当h == None,代表此轮intv合并完成,跳出。
每轮合并完成后将单元长度×2,切换到下轮合并:intv *= 2。
解答一:归并排序(递归法)
题目要求时间空间复杂度分别为O(nlogn)和O(1),根据时间复杂度我们自然想到二分法,从而联想到归并排序;
对数组做归并排序的空间复杂度为 O(n),分别由新开辟数组O(n)和递归函数调用O(logn)组成,而根据链表特性:
数组额外空间:链表可以通过修改引用来更改节点顺序,无需像数组一样开辟额外空间;
递归额外空间:递归调用函数将带来O(logn)的空间复杂度,因此若希望达到O(1)空间复杂度,则不能使用递归。
递归额外空间:递归调用函数将带来O(logn)的空间复杂度,因此若希望达到O(1)空间复杂度,则不能使用递归。
通过递归实现链表归并排序,有以下两个环节:
分割 cut 环节: 找到当前链表中点,并从中点将链表断开(以便在下次递归 cut 时,链表片段拥有正确边界);
我们使用 fast,slow 快慢双指针法,奇数个节点找到中点,偶数个节点找到中心左边的节点。
找到中点 slow 后,执行 slow.next = None 将链表切断。
递归分割时,输入当前链表左端点 head 和中心节点 slow 的下一个节点 tmp(因为链表是从 slow 切断的)。
cut 递归终止条件: 当head.next == None时,说明只有一个节点了,直接返回此节点。
找到中点 slow 后,执行 slow.next = None 将链表切断。
递归分割时,输入当前链表左端点 head 和中心节点 slow 的下一个节点 tmp(因为链表是从 slow 切断的)。
cut 递归终止条件: 当head.next == None时,说明只有一个节点了,直接返回此节点。
合并 merge 环节: 将两个排序链表合并,转化为一个排序链表。
双指针法合并,建立辅助ListNode h 作为头部。
设置两指针 left, right 分别指向两链表头部,比较两指针处节点值大小,由小到大加入合并链表头部,指针交替前进,直至添加完两个链表。
返回辅助ListNode h 作为头部的下个节点 h.next。
时间复杂度 O(l + r),l, r 分别代表两个链表长度。
设置两指针 left, right 分别指向两链表头部,比较两指针处节点值大小,由小到大加入合并链表头部,指针交替前进,直至添加完两个链表。
返回辅助ListNode h 作为头部的下个节点 h.next。
时间复杂度 O(l + r),l, r 分别代表两个链表长度。
当题目输入的 head == None 时,直接返回None。
代码:
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode* sortList(ListNode* head) { if(head==NULL||head->next==NULL) return head; return MergeSort(head); } ListNode *Find_middle(ListNode *head) { ListNode *fast, *slow; fast = head->next; slow = head; while(fast&&fast->next) { fast = fast->next->next; slow = slow->next; } return slow; } ListNode *MergeTwoLists(ListNode *l, ListNode *r) { ListNode *mirror, *pre; mirror = new ListNode(-1); pre = mirror; while(l&&r) { if(l->val<r->val) { pre->next = l; l = l->next; } else { pre->next = r; r = r->next; } pre = pre->next; } pre->next = l==NULL? r:l; return mirror->next; } ListNode *MergeSort(ListNode *head) { ListNode *middle, *tail, *left, *right; if(head==NULL||head->next==NULL) return head; middle = Find_middle(head); tail = middle->next; middle->next = NULL; left = MergeSort(head); right = MergeSort(tail); return MergeTwoLists(left, right); } };
思路:
解答二:归并排序(从底至顶直接合并)
对于非递归的归并排序,需要使用迭代的方式替换cut环节:
我们知道,cut环节本质上是通过二分法得到链表最小节点单元,再通过多轮合并得到排序结果。
每一轮合并merge操作针对的单元都有固定长度intv,例如:
每一轮合并merge操作针对的单元都有固定长度intv,例如:
第一轮合并时intv = 1,即将整个链表切分为多个长度为1的单元,并按顺序两两排序合并,合并完成的已排序单元长度为2。
第二轮合并时intv = 2,即将整个链表切分为多个长度为2的单元,并按顺序两两排序合并,合并完成已排序单元长度为4。
以此类推,直到单元长度intv >= 链表长度,代表已经排序完成。
第二轮合并时intv = 2,即将整个链表切分为多个长度为2的单元,并按顺序两两排序合并,合并完成已排序单元长度为4。
以此类推,直到单元长度intv >= 链表长度,代表已经排序完成。
根据以上推论,我们可以仅根据intv计算每个单元边界,并完成链表的每轮排序合并,例如:
当intv = 1时,将链表第1和第2节点排序合并,第3和第4节点排序合并,……。
当intv = 2时,将链表第1-2和第3-4节点排序合并,第5-6和第7-8节点排序合并,……。
当intv = 4时,将链表第1-4和第5-8节点排序合并,第9-12和第13-16节点排序合并,……。
当intv = 2时,将链表第1-2和第3-4节点排序合并,第5-6和第7-8节点排序合并,……。
当intv = 4时,将链表第1-4和第5-8节点排序合并,第9-12和第13-16节点排序合并,……。
此方法时间复杂度O(nlogn),空间复杂度O(1)。
模拟上述的多轮排序合并:
统计链表长度length,用于通过判断intv < length判定是否完成排序;
额外声明一个节点res,作为头部后面接整个链表,用于:
额外声明一个节点res,作为头部后面接整个链表,用于:
intv *= 2即切换到下一轮合并时,可通过res.next找到链表头部h;
执行排序合并时,需要一个辅助节点作为头部,而res则作为链表头部排序合并时的辅助头部pre;后面的合并排序可以将上次合并排序的尾部tail用做辅助节点。
执行排序合并时,需要一个辅助节点作为头部,而res则作为链表头部排序合并时的辅助头部pre;后面的合并排序可以将上次合并排序的尾部tail用做辅助节点。
在每轮intv下的合并流程:
根据intv找到合并单元1和单元2的头部h1, h2。由于链表长度可能不是2^n,需要考虑边界条件:
在找h2过程中,如果链表剩余元素个数少于intv,则无需合并环节,直接break,执行下一轮合并;
若h2存在,但以h2为头部的剩余元素个数少于intv,也执行合并环节,h2单元的长度为c2 = intv - i。
若h2存在,但以h2为头部的剩余元素个数少于intv,也执行合并环节,h2单元的长度为c2 = intv - i。
合并长度为c1, c2的h1, h2链表,其中:
合并完后,需要修改新的合并单元的尾部pre指针指向下一个合并单元头部h。(在寻找h1, h2环节中,h指针已经被移动到下一个单元头部)
合并单元尾部同时也作为下次合并的辅助头部pre。
合并单元尾部同时也作为下次合并的辅助头部pre。
当h == None,代表此轮intv合并完成,跳出。
每轮合并完成后将单元长度×2,切换到下轮合并:intv *= 2。
代码:
/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode(int x) : val(x), next(NULL) {} * }; */ class Solution { public: ListNode* sortList(ListNode* head) { int intv = 1, size = 0, i, c1, c2; ListNode *h, *Mirror, *h1, *h2, *pre, *temp; if(head==NULL||head->next==NULL) return head; Mirror = new ListNode; h = head; while(h) { size++; h = h->next; }//size = 5 Mirror->next = head;//00->-1 while(intv<size) { pre = Mirror;//pre = 00 h = Mirror->next;//h = -1 h = -1 h = -1 while(h) { h1 = h;//h1 = -1 h1 = 3 h1 = 0 h1 = -1 h = -1 c1 = 1; for(i = 1; i < intv; i++) { if(h&&h->next) { h = h->next; // h = 5 h = 3 h = 4 h = 5 c1++;// c1 = 2 c1 = 2 c1 = 3 c1 = 4 } else break; } if(h&&h->next) { h = h->next;//h = 5 h = 4 h = 3 h = 0 h2 = h;//h2 = 5 h2 = 4 h2 = 3 h2 = 0 c2 = 1; for(i = 1; i < intv; i++) { if(h&&h->next) { h = h->next;// h = 4 c2++;// c2 = 2 } else break; } } else break; // if(h) h = h->next;//h = 3 h = 0 h = 0 temp = MergeTwoLists(h1, h2, c1, c2); pre->next = temp;//00->-1->5->3->4 00->-1->3->4->5 for(i = 1; i <= c1+c2; i++) pre = pre->next;//pre = 5 pre = 4 pre = 5 pre->next = h;//-1->5->3->4->0 -1->3->4->5->0->null } intv *=2; } return Mirror->next; } ListNode *MergeTwoLists(ListNode *l, ListNode *r, int c1, int c2) { ListNode *mirror, *pre; mirror = new ListNode; pre = mirror; while(c1&&c2) { if(l->val<r->val) { pre->next = l; l = l->next; c1--; } else { pre->next = r; r = r->next; c2--; } pre = pre->next; } while(c1) { pre->next = l; l = l->next; pre = pre->next; c1--; } while(c2) { pre->next = r; r = r->next; pre = pre->next; c2--; } return mirror->next; } };
posted on 2020-07-28 21:50 Little-Prince 阅读(191) 评论(0) 编辑 收藏 举报