返回

LeetCode 86:分隔链表,空间复杂度优化背后的巧思

闲谈

在软件开发领域,"分治法"是一种经典的解决复杂问题的策略。它将一个问题分解为若干个更小的子问题,分别解决这些子问题,然后将子问题的解组合起来得到原问题的解。这种方法在解决复杂算法问题时非常有用,而 LeetCode 86: 分隔链表 这道题就是一个很好的例子。

LeetCode 86: 分隔链表

给你一个头结点为 head 的单链表和一个整数 x ,返回一个链表,其中每 x 个节点组成一个块,并使用指针互相连接。

如果不足 x 个节点,最后剩余的节点应该保持原有顺序。

示例 1:

输入:head = [1,2,3,4,5,6,7,8,9,10], x = 3
输出:[1,2,3,6,7,8,10]
解释:
链表已分隔成 [(1,2,3), (4,5,6), (7,8,9), (10)]

示例 2:

输入:head = [1,2,3,4,5], x = 2
输出:[1,2,3,5]
解释:
链表已分隔成 [(1,2), (3,4,5)]

示例 3:

输入:head = [], x = 2
输出:[]

提示:

链表中节点的数目范围是 [0, 100]
0 <= Node.val <= 100
0 <= x <= 100

巧妙的解决方案

我们首先介绍一种非常巧妙的解决方案。该方案的思路是:

  1. 使用两个指针 slow 和 fast 指向链表的头部。
  2. 将 fast 指针向前移动 x 个节点。
  3. 如果 fast 指针指向最后一个节点或下一个节点是 null,那么将 slow 指针指向 fast 指针,并结束循环。
  4. 将 slow 指针指向 slow 指针的下一个节点。
  5. 重复步骤 2 和步骤 3,直到 fast 指针指向最后一个节点或下一个节点是 null。
  6. 将 slow 指针指向 null。
  7. 返回 head 指针。
def partition(head: ListNode, x: int) -> ListNode:
    slow = head
    fast = head

    while fast and fast.next:
        for _ in range(x-1):
            fast = fast.next
            if not fast:
                break
        if not fast or not fast.next:
            break
        slow.val, fast.val = fast.val, slow.val
        slow = slow.next
        fast = fast.next

    return head

该方案的时间复杂度是 O(n),空间复杂度是 O(1)。

延伸

我们还可以使用其他分治方法来解决这道题。一种常见的方法是使用递归。我们可以将链表分成两部分,然后递归地对这两部分进行分隔。最后,我们将这两部分连接起来,就得到了最终的结果。

def partition(head: ListNode, x: int) -> ListNode:
    if not head:
        return head

    left_head = ListNode(0)
    right_head = ListNode(0)

    left = left_head
    right = right_head

    while head:
        if head.val < x:
            left.next = head
            left = left.next
        else:
            right.next = head
            right = right.next

        head = head.next

    left.next = partition(right_head.next, x)
    return left_head.next

这种递归方法的时间复杂度也是 O(n),但空间复杂度是 O(n),因为我们需要额外创建一个新的链表。

无论使用哪种方法,我们都可以解决这道题。分治法是一种非常强大的解决复杂问题的策略,在解决各种各样的算法问题时都非常有用。