-
题目描述:
-
代码:
# Definition for singly-linked list. class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next class Solution: def sortList(self, head: ListNode) -> ListNode: # 思路:对于非递归的归并排序,需要使用迭代的方式替换cut环节从中点将链表断开. cut环节本质上是通过二分法得到链表最小节点单元,再通过多轮合并得到排序结果。每一轮合并merge操作针对的单元都有固定长度intv,例如: 第一轮合并时intv = 1,即将整个链表切分为多个长度为1的单元,并按顺序两两排序合并,合并完成的已排序单元长度为2。第二轮合并时intv = 2,即将整个链表切分为多个长度为2的单元,并按顺序两两排序合并,合并完成已排序单元长度为4。以此类推,直到单元长度intv >= 链表长度,代表已经排序完成。根据以上推论,我们可以仅根据intv计算每个单元边界,并完成链表的每轮排序合并,例如: 当intv = 1时,将链表第1和第2节点排序合并,第3和第4节点排序合并,……。 # 算法:1. 统计链表长度length,用于通过判断intv < length判定是否完成排序;2. 额外声明一个节点res,作为头部后面接整个链表,用于:(A) intv *= 2即切换到下一轮合并时,可通过res.next找到链表头部h;(B) 执行排序合并时,需要一个辅助节点作为头部,而res则作为链表头部,排序合并时的辅助头部pre;后面的合并排序可以将上次合并排序的尾部tail用做辅助节点。在每轮intv下的合并流程: 1. 根据intv找到合并单元1和单元2的头部h1, h2。由于链表长度可能不是2^n,需要考虑边界条件:(A)在找h2过程中,如果链表剩余元素个数少于intv,则无需合并环节,直接break,执行下一轮合并;(B)若h2存在,但以h2为头部的剩余元素个数少于intv,也执行合并环节,h2单元的长度为c2 = intv - i。2. 合并长度为c1, c2的h1, h2链表,其中:(A) 合并完后,需要修改新的合并单元的尾部pre指针指向下一个合并单元头部h。(在寻找h1, h2环节中,h指针已经被移动到下一个单元头部)(B)合并单元尾部同时也作为下次合并的辅助头部pre。3. 当h == None,代表此轮intv合并完成,跳出。 # intv代表当前合并的子列表的长度 h, length, intv = head, 0, 1 while h: h, length = h.next, length + 1 # 额外声明一个节点res,作为头部后面接整个链表 res = ListNode(0) res.next = head # 从intv = 1开始合并母列表。 while intv < length: # pre是合并时的辅助头部,可变;res不变。 pre, h = res, res.next while h: # 得到当前未合并的子列表头部h1, h2 h1, i = h, intv while i > 0 and h: h, i = h.next, i - 1 if i > 0: # i 代表当前合并排序的剩余长度。在这里,剩余长度不为零,而h是None,说明h2不存在,不需要合并了。 break h2, i = h, intv while i and h: h, i = h.next, i - 1 # 需要分别维护c1, c2变量记录h1, h2长度,因为h2的长度可能小于intv c1, c2 = intv, intv - i while c1 > 0 and c2 > 0: # pre.next总是取h1/h2中较小的。 if h1.val < h2.val: pre.next, h1, c1 = h1, h1.next, c1 - 1 else: pre.next, h2, c2 = h2, h2.next, c2 - 1 pre = pre.next # 到这个位置,h1/h2长度相同的情况就处理完了,下面处理h1/h2长度不同的情况。 # 首先根据c1/c2得到剩余未处理的子列表。如果h1/h2长度相同,则c1/c2都等于0,这里取哪一个无所谓。 pre.next = h1 if c1 > 0 else h2 while c1 > 0 or c2 > 0: # 这里同理,因为c1/c2同时减一,不需要判断谁大于0。又因为上一行pre.next已经指向未处理的子列表,也不用重新判断pre.next的指向. pre, c1, c2 = pre.next, c1 - 1, c2 - 1 pre.next = h intv *= 2 return res.next
-
此方法时间复杂度O(nlogn),空间复杂度O(1)。
-
图解: