排序链表

标签: 链表 双指针 分治 排序 归并排序

难度: Medium

给定链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表

示例 1:

输入:head = [4,2,1,3]
输出:[1,2,3,4]

示例 2:

输入:head = [-1,5,3,4,0]
输出:[-1,0,3,4,5]

示例 3:

输入:head = []
输出:[]

提示:

  • 链表中节点的数目在范围 [0, 5 * 104] 内
  • -105 <= Node.val <= 105

进阶:你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?

注意:本题与主站 148 题相同:https://leetcode-cn.com/problems/sort-list/

Submission

运行时间: 62 ms

内存: 27.2 MB

# 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:
        if head is None or head.next is None:
            return head

        dummy = ListNode(next=head)

        def get_length(head):
            p = head
            length = 0
            while p:
                p = p.next
                length += 1
            return length

        def cut(head, n):
            p = head
            while p and n - 1 > 0:
                p = p.next
                n -= 1
            if p is None:
                return
            new_head = p.next
            p.next = None
            return new_head

        def merge(l1, l2):
            dummy = ListNode(0)
            p = dummy
            while l1 and l2:
                if l1.val < l2.val:
                    p.next = l1
                    l1 = l1.next
                else:
                    p.next = l2
                    l2 = l2.next
                p = p.next
            if l1:
                p.next = l1
            if l2:
                p.next = l2
            return dummy.next

        length = get_length(head)
        i = 1
        while i < length:
            tail = dummy
            cur = dummy.next
            while cur:
                left = cur
                right = cut(left, i)
                cur = cut(right, i)
                tail.next = merge(left, right)
                while tail.next:
                    tail = tail.next
            i *= 2
        return dummy.next
    

Explain

本题解采用了自底向上的归并排序算法来对链表进行排序。首先通过get_length函数计算链表的长度。然后使用迭代的方式,通过增量i(初值为1,并每次翻倍),逐步合并更长的子链表段。cut函数用于切割链表,返回后半部分的头节点,并将前半部分尾节点的next设为None,从而实现链表的分割。merge函数负责合并两个已排序的链表段。整个过程通过一个外部的while循环控制,每次循环都将链表中所有相邻的、长度为i的链表段进行合并,直到i大于或等于链表长度,此时整个链表排序完成。

时间复杂度: O(n log n)

空间复杂度: O(1)

# 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:
        if head is None or head.next is None:
            return head

        dummy = ListNode(next=head)

        def get_length(head):
            p = head
            length = 0
            while p:
                p = p.next
                length += 1
            return length

        def cut(head, n):
            p = head
            while p and n - 1 > 0:
                p = p.next
                n -= 1
            if p is None:
                return
            new_head = p.next
            p.next = None
            return new_head

        def merge(l1, l2):
            dummy = ListNode(0)
            p = dummy
            while l1 and l2:
                if l1.val < l2.val:
                    p.next = l1
                    l1 = l1.next
                else:
                    p.next = l2
                    l2 = l2.next
                p = p.next
            if l1:
                p.next = l1
            if l2:
                p.next = l2
            return dummy.next

        length = get_length(head)
        i = 1
        while i < length:
            tail = dummy
            cur = dummy.next
            while cur:
                left = cur
                right = cut(left, i)
                cur = cut(right, i)
                tail.next = merge(left, right)
                while tail.next:
                    tail = tail.next
            i *= 2
        return dummy.next
    

Explore

自顶向下的递归排序方法需要使用额外的栈空间来实现递归调用,这在最坏的情况下可能需要O(n)的空间复杂度,而自底向上的归并排序则可以做到O(1)的空间复杂度,因为它使用迭代的方式替代了递归。对于链表这种数据结构,自底向上的归并排序可以更有效地利用链表的节点连接特性,无需进行频繁的内存分配和释放,节省空间且提高效率。

在`cut`函数中,如果在循环中`p`变为None,这意味着链表的长度小于预期切割的长度,此时应返回None,表示没有足够的节点进行下一段的切割。同时,该场景下剩余的链表部分(即从头节点到当前的尾节点)也应该保持不变,因为没有足够的节点来形成一个新的链表段。

在`merge`函数中,通过引入一个哑结点(dummy node)来辅助链表合并,确保操作的简便性。函数采用双指针技术,分别指向两个待合并链表的起始位置。通过比较两个链表头部节点的值,选择较小的节点连接到结果链表的当前节点,并移动选中链表的指针。这个过程重复进行,直到两个链表中的一个被完全合并。最后,将未完成合并的链表部分直接连接到结果链表的末尾。这样的操作确保了在整个合并过程中,新链表的构建是按照元素大小顺序进行的,从而保证了链表的有序性。