1 题目
给你一个头结点为 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 <= 10001 <= k <= 50
2 代码实现
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
vector<ListNode*> splitListToParts(ListNode* head, int k) {
int length = 0 ;
ListNode* cur =head ;
while(cur != nullptr){
length++;
cur = cur -> next;
}
int base_length = length / k;
int remain = length % k;
vector<ListNode*> res(k , nullptr);
cur = head ;
for (int i = 0 ; i < k && cur != nullptr ; i++){
res[i] = cur ;
int cur_length = base_length + (remain > 0 ? 1 : 0 );
remain--;
for(int j = 1 ; j < cur_length ; j++){
cur = cur -> next ;
}
ListNode* nextPart = cur -> next ;
cur -> next = nullptr ;
cur = nextPart;
}
return res ;
}
};
思考
这个题目...直接的思路就是遍历链表,同时计数length得到链表结点个数,也就是"链表的长度"。
然后要将这个链表分成k个部分,有可能存在null。
这个怎么实现呢?
举个例子,length = 13 , k =4 , 13 / 4 = 3 (地板除),4 ,3 , 3, 3 ,这样分,因为要求前面的部分不能比后面的长度短,那我就是小的先行,从尾部开始往回走?
length = 3 , k = 5 , 3 / 5 = 0,得到 1, 1, 1, null , null,但是一开始为什么会有null呢?地板除小于 0 的时候是不是就这么做,用null 补位,这样直接从头节点开始走,每一个分割下来的链表肯定是一个结点长度,后面不足的用null补位。
整体很直观的做题思路是这样的,但是实际上应该怎么实现呢?我觉得有点繁琐。
1.先遍历,得到length 。
2.地板除 ,length / k 如果比0大,从后往前构造(比较复杂,怎么避免),分割链表 ,如果比0小,每个分割留一个节点,从前往后分割链表,不够的用null补上
3.怎么实现链表的分割操作?
自己写不出来,还需要直接看题解。
解题思路拆解
先理清核心逻辑,避免 "从后往前构造" 的复杂思路,更简单的步骤是:
- 计算链表总长度 :遍历链表,统计节点总数
length。 - 确定每部分的基础长度和余数 :
- 基础长度
base_len = length / k(每个部分至少有的节点数); - 余数
remain = length % k(前remain个部分需要多 1 个节点)。 - 比如
length=10, k=3:base_len=3,remain=1→ 前 1 个部分长度是 4,后 2 个是 3; - 比如
length=3, k=5:base_len=0,remain=3→ 前 3 个部分长度是 1,后 2 个是 0(即 null)。
- 基础长度
- 遍历分割链表:逐个构造 k 个部分,每个部分取对应长度的节点,注意断开链表的连接(避免各部分相连)。
流程图
画个mmd流程图看看,哈哈。

代码
cpp
#include <iostream>
#include <vector>
using namespace std;
// 定义链表节点结构
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
class Solution {
public:
vector<ListNode*> splitListToParts(ListNode* head, int k) {
// 步骤1:计算链表总长度
int length = 0;
ListNode* cur = head;
while (cur != nullptr) {
length++;
cur = cur->next;
}
// 步骤2:计算基础长度和余数
int base_len = length / k; // 每个部分的基础长度
int remain = length % k; // 前remain个部分需要多1个节点
// 结果数组,大小为k
vector<ListNode*> result(k, nullptr);
cur = head; // 重新指向链表头,开始分割
// 步骤3:逐个构造k个部分
for (int i = 0; i < k && cur != nullptr; ++i) {
result[i] = cur; // 当前部分的头节点
// 计算当前部分的实际长度:前remain个部分长度=base_len+1,其余=base_len
int current_len = base_len + (remain > 0 ? 1 : 0);
remain--; // 每处理一个多1的部分,余数减1
// 移动到当前部分的最后一个节点
for (int j = 1; j < current_len; ++j) {
cur = cur->next;
}
// 断开当前部分和下一部分的连接
ListNode* next_part = cur->next;
cur->next = nullptr;
cur = next_part; // 指向下一部分的头节点
}
// 剩下的部分(cur已经为null),result中默认是null,无需处理
return result;
}
};
// 辅助函数:创建链表(根据数组)
ListNode* createList(const vector<int>& nums) {
if (nums.empty()) return nullptr;
ListNode* head = new ListNode(nums[0]);
ListNode* cur = head;
for (int i = 1; i < nums.size(); ++i) {
cur->next = new ListNode(nums[i]);
cur = cur->next;
}
return head;
}
// 辅助函数:打印分割后的结果
void printResult(const vector<ListNode*>& parts) {
cout << "[";
for (int i = 0; i < parts.size(); ++i) {
cout << "[";
ListNode* cur = parts[i];
while (cur != nullptr) {
cout << cur->val;
if (cur->next != nullptr) cout << ",";
cur = cur->next;
}
cout << "]";
if (i != parts.size() - 1) cout << ",";
}
cout << "]" << endl;
}
// 测试示例
int main() {
// 示例1:head = [1,2,3], k = 5
vector<int> nums1 = {1,2,3};
ListNode* head1 = createList(nums1);
Solution s;
vector<ListNode*> res1 = s.splitListToParts(head1, 5);
cout << "示例1结果:";
printResult(res1); // 输出:[[1],[2],[3],[],[]]
// 示例2:head = [1,2,3,4,5,6,7,8,9,10], k = 3
vector<int> nums2 = {1,2,3,4,5,6,7,8,9,10};
ListNode* head2 = createList(nums2);
vector<ListNode*> res2 = s.splitListToParts(head2, 3);
cout << "示例2结果:";
printResult(res2); // 输出:[[1,2,3,4],[5,6,7],[8,9,10]]
return 0;
}
cpp
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
vector<ListNode*> splitListToParts(ListNode* head, int k) {
// 步骤1:计算链表总长度
int length = 0;
ListNode* cur = head;
while (cur != nullptr) {
length++;
cur = cur->next;
}
// 步骤2:计算基础长度和余数
int base_len = length / k; // 每个部分的基础长度
int remain = length % k; // 前remain个部分需要多1个节点
// 结果数组,大小为k
vector<ListNode*> result(k, nullptr);
cur = head; // 重新指向链表头,开始分割
// 步骤3:逐个构造k个部分
for (int i = 0; i < k && cur != nullptr; ++i) {
result[i] = cur; // 当前部分的头节点
// 计算当前部分的实际长度:前remain个部分长度=base_len+1,其余=base_len
int current_len = base_len + (remain > 0 ? 1 : 0);
remain--; // 每处理一个多1的部分,余数减1
// 移动到当前部分的最后一个节点
for (int j = 1; j < current_len; ++j) {
cur = cur->next;
}
// 断开当前部分和下一部分的连接
ListNode* next_part = cur->next;
cur->next = nullptr;
cur = next_part; // 指向下一部分的头节点
}
// 剩下的部分(cur已经为null),result中默认是null,无需处理
return result;
}
};
3 小结
思路大方向是对的,直观的。
但是注意不要从后往前写链表比较复杂,所以引入remain,也比较好理解,好比分东西,每人都一样了,但是还有多出来的remain,然后又一点一点从头分下去,remain多出来的肯定比人数少,那么就直接每人从头再分一个就好。
写成代码写不出来,这个最重要了,要多练,把题解删了也自己能写出来才行。
ai老师帮忙小结一下这题的收获和反思。
思考和总结
你对「分隔链表」的思考和总结非常到位,核心是抓住了**"先算长度→分基础长度 + 余数→正向分割"**的核心逻辑,也意识到了自己 "思路能想通但代码写不出来" 的核心问题 ------ 这是编程新手最常见的痛点,接下来我帮你把 "从思路到代码" 的落地路径拆得更细,让你能一步步独立写出来。
一、先帮你把核心思路再简化(对应你 "分东西" 的比喻)
你总结的 "分东西" 思路特别好,再固化一下:
- 总节点数 = 要分的 "总糖果数"
- k = 要分给的 "人数"
- base_len = 每个人先平均分到的 "基础糖果数"
- remain = 分完基础数后剩下的 "多余糖果数"(只会比 k 小)
- 分配规则:前 remain 个人,每人多拿 1 颗,后面的人拿基础数;如果基础数是 0(总糖果 < 人数),那前 remain 个人拿 1 颗,剩下的人拿 0 颗(对应 null)。
二、"思路转代码" 的分步落地法(对着这个步骤,你能一步步写)
不用一上来就写完整代码,拆成 5 个小步骤,每步只关注一个小目标:
步骤 1:先写 "统计链表长度" 的代码(最简单,先搞定)
cpp
// 目标:算出链表总节点数length
int length = 0;
ListNode* cur = head; // 用cur指针遍历,不破坏原链表头
while (cur != nullptr) {
length++;
cur = cur->next;
}
步骤 2:计算 base_len 和 remain(纯数学计算,无难度)
cpp
int base_len = length / k;
int remain = length % k;
步骤 3:初始化结果数组(你问过的核心点)
cpp
// 目标:创建k个位置的数组,默认存nullptr,对应"没分到糖果的人"
vector<ListNode*> res(k, nullptr);
cur = head; // 重置cur,回到链表头,准备分割
步骤 4:核心循环 ------ 逐个分割链表(拆成 3 个小操作)
这是你觉得最难的部分,再拆成 "记头节点→找尾节点→断开连接":
cpp
for (int i = 0; i < k && cur != nullptr; i++) {
// 操作1:记录当前部分的头节点(分给第i个人的第一个糖果)
res[i] = cur;
// 操作2:计算当前人该拿的"糖果数"(当前部分长度)
int cur_len = base_len + (remain > 0 ? 1 : 0);
remain--; // 分完一个多拿的,余数减1
// 操作3:找到当前部分的最后一个节点(拿到第cur_len颗糖果)
// j从1开始,因为已经拿了第1颗(res[i]=cur),再走cur_len-1步
for (int j = 1; j < cur_len; j++) {
cur = cur->next;
}
// 操作4:断开当前部分和后面的连接(避免"串味")
ListNode* nextPart = cur->next; // 先记住下一部分的头
cur->next = nullptr; // 切断当前部分的尾巴
cur = nextPart; // cur移到下一部分的头,准备分下一个人
}
步骤 5:返回结果
cpp
return res;
三、针对 "写不出来" 的解决办法(你的核心痛点)
你说 "写成代码写不出来",不是思路问题,是**"练得少 + 没拆步骤"**,按这个方法练:
- 先 "抄着写":把上面的 5 个步骤抄一遍,每抄一步,嘴里念一遍 "这步是干嘛的"(比如 "这步是记当前部分的头节点");
- 再 "遮着写":把代码遮起来,只看步骤描述,自己写每一步的代码,写不出来就看一眼,再遮起来写;
- 最后 "空着写":只看题目,自己拆步骤,一步步写完整代码,错了也没关系,改到能跑通。
四、关键细节提醒(你容易忽略的点)
- 循环条件
i < k && cur != nullptr:cur 为 null 时,说明糖果分完了,剩下的人直接拿 null,不用循环了; - 断开连接
cur->next = nullptr:如果不做这步,分割后的链表会连在一起(比如示例 2 会变成 [1-2-3-4-5-6-7-8-9-10],而不是三个独立部分); - j 从 1 开始:因为 res [i] 已经记录了第 1 个节点,只需要再走 cur_len-1 步就能到最后一个节点。
总结
- 核心逻辑:用 "分糖果" 的比喻固化 "base_len+remain" 的分配规则,避免 "从后往前" 的复杂思路;
- 代码落地:拆成 5 个小步骤,每步只解决一个小问题,不用一上来写完整代码;
- 练习方法:先抄→再遮着写→最后空着写,重点练 "找尾节点 + 断开连接" 这两个核心操作。