题目
标题和出处
标题:将链表分隔成 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:
输入:
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:
输入:
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} 0≤Node.val≤1000
- 1 ≤ k ≤ 50 \texttt{1} \le \texttt{k} \le \texttt{50} 1≤k≤50
解法
思路和算法
由于每个部分的长度和原始链表的长度有关,因此需要遍历原始链表得到原始链表的长度,即结点数。
记原始链表的长度为 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} k−remainder 个部分的长度为 quotient \textit{quotient} quotient。
将原始链表分隔成 k k k 个部分的做法是,找到每个部分的头结点和尾结点,将每个部分的头结点存入结果链表,并将每个部分的尾结点和后一个部分的头结点的连接关系断开。
用 curr \textit{curr} curr 表示当前遍历到的结点,初始时 curr = head \textit{curr} = \textit{head} curr=head。对于每个部分进行如下操作:
-
当前部分的头结点即为 curr \textit{curr} curr,将 curr \textit{curr} curr 存入结果数组的对应下标处;
-
计算得到该部分的长度 partLength \textit{partLength} partLength;
-
将 curr \textit{curr} curr 向后移动 partLength − 1 \textit{partLength} - 1 partLength−1 次,此时 curr \textit{curr} curr 位于该部分的尾结点;
-
记录 next = curr . next \textit{next} = \textit{curr}.\textit{next} next=curr.next,则 next \textit{next} next 为后一个部分的头结点;
-
令 curr . next : = null \textit{curr}.\textit{next} := \text{null} curr.next:=null,将当前部分的尾结点和后一个部分的头结点的连接关系断开;
-
令 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} k−length 个部分都是空链表,因此当链表遍历结束时即完成分隔。
代码
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)。除了返回值以外,使用的空间复杂度是常数。