链表题目:将链表分隔成 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:

链表题目:将链表分隔成 K 个部分_第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)。除了返回值以外,使用的空间复杂度是常数。

你可能感兴趣的:(数据结构和算法,#,链表,链表)