分隔链表

标签: 链表

难度: Medium

给你一个头结点为 head 的单链表和一个整数 k ,请你设计一个算法将链表分隔为 k 个连续的部分。

每部分的长度应该尽可能的相等:任意两部分的长度差距不能超过 1 。这可能会导致有些部分为 null 。

k 个部分应该按照在链表中出现的顺序排列,并且排在前面的部分的长度应该大于或等于排在后面的长度。

返回一个由上述 k 部分组成的数组。

 

示例 1:

输入:head = [1,2,3], k = 5
输出:[[1],[2],[3],[],[]]
解释:
第一个元素 output[0] 为 output[0].val = 1 ,output[0].next = null 。
最后一个元素 output[4] 为 null ,但它作为 ListNode 的字符串表示是 [] 。

示例 2:

输入:head = [1,2,3,4,5,6,7,8,9,10], k = 3
输出:[[1,2,3,4],[5,6,7],[8,9,10]]
解释:
输入被分成了几个连续的部分,并且每部分的长度相差不超过 1 。前面部分的长度大于等于后面部分的长度。

提示:

  • 链表中节点的数目在范围 [0, 1000]
  • 0 <= Node.val <= 1000
  • 1 <= k <= 50

Submission

运行时间: 52 ms

内存: 15.4 MB

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def splitListToParts(self, root: ListNode, k: int) -> List[ListNode]:
        if root is None:
            return [None for _ in range(k)]

        length = self.get_length(root)
        a = length // k
        cut_count_arr = [a] * k
        b = length % k
        for i in range(b):
            cut_count_arr[i] += 1
        print(cut_count_arr)

        cur = root
        res = []
        for cut_count in cut_count_arr:
            left = cur
            cur = self.cut(cur, cut_count)
            res.append(left)
        return res

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

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

Explain

此题解的思路是先计算链表的总长度,然后根据总长度和要分隔的部分数k,计算每部分的长度。具体来说: 1. 用总长度整除k得到每部分的基本长度a 2. 用总长度对k取余得到多出来的节点数b 3. 将多出的b个节点平均分配到前b个部分,使得前b个部分的长度都是a+1 4. 从头节点开始,根据每部分的长度切割链表,得到k个部分 5. 将切割后的k个部分存入结果数组中返回

时间复杂度: O(n+k)

空间复杂度: O(k)

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def splitListToParts(self, root: ListNode, k: int) -> List[ListNode]:
        if root is None:
            return [None for _ in range(k)]

        # 计算链表总长度
        length = self.get_length(root)
        # 计算每部分的基本长度a
        a = length // k
        # 创建长度为k的结果数组,初始每部分长度都为a
        cut_count_arr = [a] * k
        # 计算多出的节点数b 
        b = length % k
        # 将多出的b个节点平均分配到前b个部分
        for i in range(b):
            cut_count_arr[i] += 1

        cur = root
        res = []
        # 根据每部分长度切割链表
        for cut_count in cut_count_arr:
            left = cur
            cur = self.cut(cur, cut_count)
            res.append(left)
        return res

    # 获取链表长度的函数
    def get_length(self, head):
        p = head
        length = 0
        while p:
            length += 1
            p = p.next
        return length

    # 根据长度n切割链表的函数
    def cut(self, head, n):
        p = head
        # p指针移动n-1步到达切割点的前一个节点
        while n - 1 > 0 and p:
            p = p.next
            n -= 1 
        if p is None:
            return
        # remain为切割点的下一个节点,作为剩余部分的头
        remain = p.next
        # 切断p和remain的连接
        p.next = None
        return remain

Explore

将额外的节点数b平均分配到前b个部分可以确保各部分长度尽可能均匀,从而使得各部分长度之间的差异最小。这种方法可以保持链表分割后的均衡性,避免某些部分过长或过短,从而使得处理或存储分割后的链表更加高效和方便。此外,这也有助于在一些应用场景中,如并行处理或负载均衡,确保各部分工作量尽量相等。

是的,当链表的总长度小于k时,确实需要处理长度为0的部分。在题解中通过初始化结果数组cut_count_arr的每个元素为基本长度a,并在计算多出的节点数b后,对前b个部分适当增加长度。如果总长度小于k,基本长度a将为0,且多余的部分将直接为null。这意味着算法需要正确处理这些情况,确保即使是长度为0的部分也能返回一个null值,这样在结果数组中对应的位置会有一个明确的null值,表示该部分是空的。

当n等于1时,p在移动n-1步后确实是不移动,即p仍然指向当前节点。在这种情况下,切割后的链表头节点是p本身,而切割点的下一个节点remain将成为下一部分的头节点。函数cut将p的next指向null,从而切断当前部分与其余链表的连接。这样,当前部分只包含一个节点p,而链表的其余部分从p的原来下一个节点开始。因此,即使n为1,函数cut仍然能够正确地处理链表的切割。

题解中的算法并不需要从链表头部开始重新遍历到当前切割点。每次切割使用函数cut时,都是以当前部分的头节点cur开始,然后根据该部分的长度移动到切割点。因此,每次切割是从上次切割结束的节点开始,而不是整个链表的头部开始。这种方法有效地减少了不必要的遍历,提高了效率。虽然每个部分都需要一次遍历来确定切割点,但总的遍历次数等于链表的长度,所以整体时间复杂度为O(n)。