链表题目:将链表分隔成 K 个部分

题目

标题和出处

标题:将链表分隔成 K 个部分

出处:725. 将链表分隔成 K 个部分

难度

5 级

题目描述

要求

给你单链表的头结点 head \texttt{head} head 和一个整数 k \texttt{k} k,将链表分隔成 k \texttt{k} k 个连续的部分。

每部分的长度应尽可能相等:任意两部分的长度相差不超过 1 \texttt{1} 1。可能有些部分为 null \texttt{null} null

每个部分的顺序应和输入链表的顺序相同,并且前面部分的长度应总是大于或等于后面部分的长度。

返回分隔成的 k \texttt{k} k 个部分的数组。

示例

示例 1:

示例 1

输入: head   =   [1,2,3],   k   =   5 \texttt{head = [1,2,3], k = 5} head = [1,2,3], k = 5
输出: [[1],[2],[3],[],[]] \texttt{[[1],[2],[3],[],[]]} [[1],[2],[3],[],[]]
解释:
第一个元素 output[0] \texttt{output[0]} output[0] 满足 output[0].val   =   1 \texttt{output[0].val = 1} output[0].val = 1 output[0].next   =   null \texttt{output[0].next = null} output[0].next = null
最后一个元素 output[4] \texttt{output[4]} output[4] null \texttt{null} null,空链表的字符串表示是 [] \texttt{[]} []

示例 2:

示例 2

输入: head   =   [1,2,3,4,5,6,7,8,9,10],   k   =   3 \texttt{head = [1,2,3,4,5,6,7,8,9,10], k = 3} head = [1,2,3,4,5,6,7,8,9,10], k = 3
输出: [[1,2,3,4],[5,6,7],[8,9,10]] \texttt{[[1,2,3,4],[5,6,7],[8,9,10]]} [[1,2,3,4],[5,6,7],[8,9,10]]
解释:
输入链表被分隔成连续的部分,每部分的长度相差不超过 1 \texttt{1} 1,前面部分的长度大于或等于后面部分的长度。

数据范围

  • 链表中结点的数目在范围 [0,   1000] \texttt{[0, 1000]} [0, 1000]
  • 0 ≤ Node.val ≤ 1000 \texttt{0} \le \texttt{Node.val} \le \texttt{1000} 0Node.val1000
  • 1 ≤ k ≤ 50 \texttt{1} \le \texttt{k} \le \texttt{50} 1k50

解法

思路和算法

由于每个部分的长度和原始链表的长度有关,因此需要遍历原始链表得到原始链表的长度,即结点数。

记原始链表的长度为 length \textit{length} length,令 quotient = ⌊ length k ⌋ \textit{quotient} = \Big\lfloor \dfrac{\textit{length}}{k} \Big\rfloor quotient=klength remainder = length   m o d   k \textit{remainder} = \textit{length} \bmod k remainder=lengthmodk,则分隔成的 k k k 个部分中,前面 remainder \textit{remainder} remainder 个部分的长度为 quotient + 1 \textit{quotient} + 1 quotient+1,其余 k − remainder k - \textit{remainder} kremainder 个部分的长度为 quotient \textit{quotient} quotient

将原始链表分隔成 k k k 个部分的做法是,找到每个部分的头结点和尾结点,将每个部分的头结点存入结果链表,并将每个部分的尾结点和后一个部分的头结点的连接关系断开。

curr \textit{curr} curr 表示当前遍历到的结点,初始时 curr = head \textit{curr} = \textit{head} curr=head。对于每个部分进行如下操作:

  1. 当前部分的头结点即为 curr \textit{curr} curr,将 curr \textit{curr} curr 存入结果数组的对应下标处;

  2. 计算得到该部分的长度 partLength \textit{partLength} partLength

  3. curr \textit{curr} curr 向后移动 partLength − 1 \textit{partLength} - 1 partLength1 次,此时 curr \textit{curr} curr 位于该部分的尾结点;

  4. 记录 next = curr . next \textit{next} = \textit{curr}.\textit{next} next=curr.next,则 next \textit{next} next 为后一个部分的头结点;

  5. curr . next : = null \textit{curr}.\textit{next} := \text{null} curr.next:=null,将当前部分的尾结点和后一个部分的头结点的连接关系断开;

  6. curr : = next \textit{curr} := \textit{next} curr:=next,此时 curr \textit{curr} curr 位于后一个部分的头结点,重复上述操作。

分隔链表的结束条件是 k k k 个部分全部分隔完毕,或者链表遍历结束。当链表长度大于或等于 k k k 时,每个部分至少有 1 1 1 个结点,因此需要将 k k k 个部分全部分隔完毕。当链表长度小于 k k k 时,分隔成的 k k k 个部分中,前面 length \textit{length} length 个部分的长度都是 1 1 1,其余的 k − length k - \textit{length} klength 个部分都是空链表,因此当链表遍历结束时即完成分隔。

代码

class Solution {
    public ListNode[] splitListToParts(ListNode head, int k) {
        int length = 0;
        ListNode curr = head;
        while (curr != null) {
            length++;
            curr = curr.next;
        }
        int quotient = length / k, remainder = length % k;
        ListNode[] parts = new ListNode[k];
        curr = head;
        for (int i = 0; i < k && curr != null; i++) {
            parts[i] = curr;
            int partLength = quotient + (i < remainder ? 1 : 0);
            for (int j = 1; j < partLength; j++) {
                curr = curr.next;
            }
            ListNode next = curr.next;
            curr.next = null;
            curr = next;
        }
        return parts;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是链表的长度。需要遍历链表两次,第一次遍历得到链表的长度,第二次遍历分隔链表,分隔链表时对于每个结点的操作的时间都是 O ( 1 ) O(1) O(1)

  • 空间复杂度: O ( 1 ) O(1) O(1)。除了返回值以外,使用的空间复杂度是常数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伟大的车尔尼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值