Leetcode 95 分割链表

1 题目

725. 分隔链表

给你一个头结点为 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 <= 1000
  • 1 <= 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.怎么实现链表的分割操作?


自己写不出来,还需要直接看题解。

解题思路拆解

先理清核心逻辑,避免 "从后往前构造" 的复杂思路,更简单的步骤是:

  1. 计算链表总长度 :遍历链表,统计节点总数 length
  2. 确定每部分的基础长度和余数
    • 基础长度 base_len = length / k(每个部分至少有的节点数);
    • 余数 remain = length % k(前 remain 个部分需要多 1 个节点)。
    • 比如 length=10, k=3base_len=3remain=1 → 前 1 个部分长度是 4,后 2 个是 3;
    • 比如 length=3, k=5base_len=0remain=3 → 前 3 个部分长度是 1,后 2 个是 0(即 null)。
  3. 遍历分割链表:逐个构造 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;

三、针对 "写不出来" 的解决办法(你的核心痛点)

你说 "写成代码写不出来",不是思路问题,是**"练得少 + 没拆步骤"**,按这个方法练:

  1. 先 "抄着写":把上面的 5 个步骤抄一遍,每抄一步,嘴里念一遍 "这步是干嘛的"(比如 "这步是记当前部分的头节点");
  2. 再 "遮着写":把代码遮起来,只看步骤描述,自己写每一步的代码,写不出来就看一眼,再遮起来写;
  3. 最后 "空着写":只看题目,自己拆步骤,一步步写完整代码,错了也没关系,改到能跑通。

四、关键细节提醒(你容易忽略的点)

  1. 循环条件i < k && cur != nullptr:cur 为 null 时,说明糖果分完了,剩下的人直接拿 null,不用循环了;
  2. 断开连接cur->next = nullptr:如果不做这步,分割后的链表会连在一起(比如示例 2 会变成 [1-2-3-4-5-6-7-8-9-10],而不是三个独立部分);
  3. j 从 1 开始:因为 res [i] 已经记录了第 1 个节点,只需要再走 cur_len-1 步就能到最后一个节点。

总结

  1. 核心逻辑:用 "分糖果" 的比喻固化 "base_len+remain" 的分配规则,避免 "从后往前" 的复杂思路;
  2. 代码落地:拆成 5 个小步骤,每步只解决一个小问题,不用一上来写完整代码;
  3. 练习方法:先抄→再遮着写→最后空着写,重点练 "找尾节点 + 断开连接" 这两个核心操作。
相关推荐
郝学胜-神的一滴1 天前
线程同步:并行世界的秩序守护者
java·linux·开发语言·c++·程序人生
龘龍龙1 天前
Python基础学习(十)
服务器·python·学习
A9better1 天前
嵌入式开发学习日志47——任务创建与就绪列表
单片机·嵌入式硬件·学习
北岛寒沫1 天前
北京大学国家发展研究院 经济学原理课程笔记(第十八课 国内生产总值与物价水平)
经验分享·笔记·学习
明洞日记1 天前
【VTK手册032】vtkImageConstantPad:医学图像边界填充与尺寸对齐
c++·图像处理·vtk·图形渲染
Boilermaker19921 天前
[算法基础] FooldFill(DFS、BFS)
算法·深度优先·宽度优先
Aevget1 天前
MFC扩展库BCGControlBar Pro v37.1亮点:Ribbon Bar组件全新升级
c++·ribbon·mfc·bcg·界面控件·ui开发
qq_160144871 天前
低成本高效学习路线图
学习
航Hang*1 天前
第七章:综合布线技术 —— 设备间子系统的设计与施工
网络·笔记·学习·期末·复习