题目描述
给定一个头节点为root
的链表,编写一个函数,将链表分隔为k
个连续的部分。
每部分的长度应该尽可能的相等:认一两部分的长度差距不能超过1
,也就是说可能有些部分为null
;
这k
个部分应该按照在链表中出现的顺序进行输出,并且排在前面的部分的长度大于等于后面的长度。
返回一个符合上述规则的链表的列表。
示例1:
输入: root= [1, 2, 3], k = 5
输出: output = [[1], [2], [3], [], []]
解释: 输入节点root.val = 1, root.next.val = 2
; 输出output[0].val = 1, output[0].next = null; output[1].val = 2, output[1].next = null; output[3] = null
示例2:
输入: root= [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3
输出: output = [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
解释: 输入被分成了几个连续的部分,并且每部分的长度相差不超过1
,前面部分的长度大于后面部分的长度。
思路分析
我们依旧先考虑一般情况,当len
恰好可被链表长度k
整除时,我们直接可通过求得的商确定每个分区的节点个数。我们直接给出核心代码:
代码1: 刚好均分情况下的一般规则
// record numbers of node in every section (0 - default)
int[] numsNodePerSec = new int[k];
ListNode[] output = new ListNode[k];
ListNode pA = root;
// Step1: obtain length of root
int len = 0;
while (pA != null) {
len++;
pA = pA.next;
}
/* Step2: obtain carry */
int NumPerSec = len / k;
if (NumPerSec > 0) {
for(int i = 0; i< k; i++)
numsNodePerSec[i] = NumPerSec;
}
/* Step3: split root according to numsNodePreSec */
pA = root;
for (int i = 0; i < k; i++) {
int steps = numsNodePerSec[i] - 1; // start inclued
output[i] = pA;
while (steps-- > 0) {
pA = pA.next;
}
// upgrate pos of pA
ListNode tmpNode = pA;
if(pA.next == null)
break;
pA = pA.next;
tmpNode.next = null;
}
难点分析
我们通过代码1了解到了一般规则实现,这里主要讨论当链表无法被均分情况下的代码完善,我们不妨设root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
,即包含10
个节点;分别讨论k = 3、9、12
三种情况:
-
root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; k = 3
我们通过简单分析可知,输出应为
[[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
; 这里我们的3
个分区的长度分别为4, 3, 3
。
我们根据代码1,可知此时numsNodePerSec = [3, 3, 3]
, 则可知我们需要对长度整除k
后的余数进行保存,这里为rmdr = 1
; 此时直接将余数与分区1的节点个数进行相加即可,numsNodePerSec[0] = numsNodePerSec[0] + rmdr = numsNodePerSec[0] + 1
,此时numsNodePerSec = [4, 3, 3]
,为我们的期望值。
我们对这个情况进行了简单分析后,再接着对情况2进行进一步分析。 -
root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; k = 7
此时,输出应为
[ [1, 2], [3, 4], [5, 6], [7], [8], [9], [10] ]
,numsNodePerSec = [2, 2, 2, 1,1,1,1]
。 此时,我们知道链表长度与k
进行除法操作后,商NumPerSec = 1
,余数rmdr = 3
, 此时若依旧按照情况1中的直接令numsNodePerSec[0]
更新额为numsNodePerSec[0] + rmdr = numsNodePerSec[0] + 3
, 则有numsNodePerSec = [4, 1, 1, 1,1,1,1]
,显然时不正确的。
我们需要将余数进行均衡分配,将[0, rmdr-1]范围内numsNodePerSec
均加1
; 即numsNodePerSec[ 0 : rmdr-1] ++
,这里我们可以给出改进后的代码2:
代码2: 余数均分
/* Step1:
Init. pointers and integers
*/
ListNode pA = root;
// record numbers of node in every section (0 - default)
int[] numsNodePerSec = new int[k];
ListNode[] output = new ListNode[k];
// Step1: obtain length of root
int len = 0;
while (pA != null) {
len++;
pA = pA.next;
}
/* Step2: obtain carry and remainder
and
average number if rmdr more than 0.
*/
int minNumPerSec = len / k;
int rmdr = len % k;
if (minNumPerSec > 0) {
for(int i = 0; i< k; i++)
numsNodePerSec[i] = minNumPerSec;
}
if (rmdr > 0) {
for (int i = 0; i < Math.abs(rmdr); i++) {
numsNodePerSec[i]++;
}
}
/* Step3: split root according to numsNodePreSec
*/
pA = root;
for (int i = 0; i < k; i++) {
int steps = numsNodePerSec[i] - 1; // start inclued
output[i] = pA;
while (steps-- > 0) {
pA = pA.next;
}
// upgrate pos of pA
ListNode tmpNode = pA;
if(pA.next == null)
break;
pA = pA.next;
tmpNode.next = null;
}
return output;
-
root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; k = 12
这里主要考虑,当输出数组存在空链表时,代码2是否能够正常运行。 我们先进行简单的分析,当
len = 10, k = 12
时,此时NumPerSec = 0, rmdr = 10
,numsNodePerSec
长度为k
,且元素值均为0
; 此时,根据代码2,会对numsNodePerSec
的前rmdr
的元素进行+1
操作,这里即前10
个元素进行+1
操作,这时候我们则有numsNodePerSec = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0]
。
接着进入到Step 3: spilt root according to numsNodePerSec
,根据指定的分区个数对root
链表进行分区。当pA.next = null
时,终止循环,且输出链表数组output
的链表元素默认初始化为null
,故output
中最后的两个null
链表不需要做额外的处理,已经满足题干要求。
至此,我们对一般规则及难点进行了完善及统一,接下来我们直接给出解题步骤及完整代码:
解题步骤
- 求得链表长度
len
; - 求取余数与商,并根据余数对各分区节点个数进行均摊;
- 根据分区节点数切分链表;
解题代码
public static ListNode[] solution(ListNode root, int k) {
/* Step1:
Init. pointers and integers
*/
ListNode pA = root;
// record numbers of node in every section (0 - default)
int[] numsNodePerSec = new int[k];
ListNode[] output = new ListNode[k];
// Step1: obtain length of root
int len = 0;
while (pA != null) {
len++;
pA = pA.next;
}
/* Step2: obtain carry and remainder
and
average number if rmdr more than 0.
*/
int minNumPerSec = len / k;
int rmdr = len % k;
if (minNumPerSec > 0) {
for(int i = 0; i< k; i++)
numsNodePerSec[i] = minNumPerSec;
}
if (rmdr > 0) {
for (int i = 0; i < Math.abs(rmdr); i++) {
numsNodePerSec[i]++;
}
}
/* Step3: split root according to numsNodePreSec
*/
pA = root;
for (int i = 0; i < k; i++) {
int steps = numsNodePerSec[i] - 1; // start inclued
output[i] = pA;
while (steps-- > 0) {
pA = pA.next;
}
// upgrate pos of pA
ListNode tmpNode = pA;
if(pA.next == null)
break;
pA = pA.next;
tmpNode.next = null;
}
return output;
}
复杂度分析
**时间复杂度:**我们对数据进行了两遍历,2N
次节点访问;且对numsNodePerSec
进行了k
次访问,故时间复杂度为O(N + k)
; (这里N = len
)
**空间复杂度:**这里需要k
长度的整型数组numsNodePerSec
,输出链表output
是直接对原始链表root
的拆分,故空间复杂度可计为O( k)
GitHub源码
完整可运行文件请访问GitHub。