leetcode 148. 排序链表

在 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的长度(剩下还需要合并的长度)。

你可能感兴趣的:(leetcode)