在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
示例 1:
输入: 4->2->1->3
输出: 1->2->3->4
示例 2:
输入: -1->5->3->4->0
输出: -1->0->3->4->5
递归(自顶向下)
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution(object):
def sortList(self, head):
"""
:type head: ListNode
:rtype: ListNode
"""
if not head or not head.next:
return head
fast = slow = head
while fast.next and fast.next.next:
fast = fast.next.next
slow = slow.next
mid = slow.next
slow.next = None
left, right = self.sortList(head), self.sortList(mid)
res = h = ListNode()
while left and right:
if left.val <= right.val:
h.next = left
left = left.next
elif left.val > right.val:
h.next = right
right = right.next
h = h.next
if left:
h.next = left
elif right:
h.next = right
return res.next
递归的方法是比较直观的。归并排序分为两步,第一步是将链表进行分割,分割的规则是每次都对半分割,分割的终止条件是当分割单元仅剩一个节点时停止分割并直接返回节点。在分割完毕之后就需要对其进行合并。可以相信对每次递归而言,我们会获得两条已经排序过的链表left以及right。我们可以添加一个伪头部用来方便后面的操作。对于两段以及排序的链表我们从左开始一个个进行比大小,如果哪个节点小就把它添加到伪头部后面,并将指针向后移。当某个链表遍历到空时就停止,并将另一个链表的剩余部分接上。这里就涉及到终止条件,我们为了能在这一步判断终止条件,需要在前面分割的时候将待分割的链表从中间断开,即slow.next = None。
迭代(自底向上)
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def sortList(self, head):
h, length, intv = head, 0, 1
while h:
h = h.next
length = length + 1
res = ListNode(0)
res.next = head
# merge the list in different intv.
while intv < length:
pre = res
h = res.next
while h:
h1 = h
i = intv
while i and h:
h = h.next
i = i - 1
if i:
break # no need to merge because the `h2` is None.
h2 = h
i = intv
while i and h:
h = h.next
i = i - 1
c1, c2 = intv, intv - i
# merge the `h1` and `h2`.
while c1 and c2:
if h1.val < h2.val:
pre.next = h1
h1 = h1.next
c1 = c1 - 1
else:
pre.next = h2
h2 = h2.next
c2 = c2 - 1
pre = pre.next
pre.next = h1 if c1 else h2
while c1 > 0 or c2 > 0:
pre= pre.next
c1 = c1 - 1
c2 = c2 - 1
pre.next = h
intv *= 2
return res.next
从递归的思考看我们需要把整个链表先分割成一个个节点,再将其合并成一个有序链表。那么其实知道这个方法之后,我们直接可以用迭代的方式从底开始向上思考,即我们直接就把链表当成一个个节点,然后直接从节点开始就对其进行合并。这样我们就其实不需要分割的操作了。对于一个长度为8的链表,我们的合并过程其实可以分为 1->2->4->8,即从一个节点开始每次合并长度会乘2。我们用一个变量intv来表示当前合并的链表的长度。当intv比length小时我们继续进行合并。这里到注意到的是在每次递归中,其实所有的节点都是连着一起的而不是我们想象中的分裂的。只是我们每次都通过intv这个变量来找到两个需要合并的链表的头部。假如我们有链表res->4-2-1-3,从intv = 1 开始,我们找到第一需要合并的头部h1是4,第二个需要合并的头部h2是2,此时并让h=h2.next即1,当4和2排序结束之后,后面的排序h已经指向了剩下需要排序的头部。在合并的过程中不能用链表是否为空来判断合并是否结束,而是得再设两个计数器c1和c2,他们分别表示链表1和链表2的长度(剩下还需要合并的长度)。