【算法】725.分割链表--通俗讲解

一、题目是啥?一句话说清

给定一个链表和一个整数k,将链表分成k个连续部分,每部分长度尽可能相等(长度差不超过1),前面的部分长度要大于等于后面的部分。

示例:

  • 输入:head = [1,2,3,4,5,6,7,8,9,10], k = 3
  • 输出:[[1,2,3,4], [5,6,7], [8,9,10]]

二、解题核心

先计算链表总长度,确定每部分的基本长度和需要额外加1的部分数量,然后遍历链表进行分割。

这就像把一列人分成k个小组,先数总人数,然后计算每组大概多少人,尽量让每组人数差不多,前面组的人可以比后面组多一个。

三、关键在哪里?(3个核心点)

想理解并解决这道题,必须抓住以下三个关键点:

1. 计算链表总长度

  • 是什么:遍历整个链表,统计节点总数。
  • 为什么重要:只有知道总长度,才能合理分配每部分的长度。

2. 确定每部分的长度

  • 是什么:每部分基本长度 = 总长度 / k,前 (总长度 % k) 个部分长度加1。
  • 为什么重要:这确保了任意两部分长度差不超过1,且前面部分长度≥后面部分。

3. 链表分割操作

  • 是什么:遍历链表,根据计算出的长度切断链表,形成k个独立部分。
  • 为什么重要:这是实际执行分割的关键步骤,需要正确操作指针。

四、看图理解流程(通俗理解版本)

假设链表:1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9 → 10,k = 3

  1. 计算总长度:10个节点
  2. 计算每部分长度
    • 基本长度 = 10 / 3 = 3
    • 余数 = 10 % 3 = 1
    • 所以前1部分长度=4,后2部分长度=3
  3. 分割链表
    • 第一部分:取4个节点 [1,2,3,4]
    • 第二部分:取3个节点 [5,6,7]
    • 第三部分:取3个节点 [8,9,10]
  4. 结果:三个链表 [1→2→3→4], [5→6→7], [8→9→10]

五、C++ 代码实现(附详细注释)

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) {
        // 第一步:计算链表总长度
        int length = 0;
        ListNode* current = head;
        while (current != nullptr) {
            length++;
            current = current->next;
        }
        
        // 第二步:计算每部分的基本长度和需要额外加1的部分数量
        int base_size = length / k;    // 每部分的基本长度
        int extra = length % k;        // 需要额外加1的部分数量
        
        vector<ListNode*> result(k, nullptr); // 初始化结果数组
        current = head;
        
        // 第三步:分割链表
        for (int i = 0; i < k && current != nullptr; i++) {
            result[i] = current; // 记录当前部分的头节点
            
            // 计算当前部分的长度
            int part_size = base_size + (i < extra ? 1 : 0);
            
            // 移动到当前部分的末尾
            for (int j = 1; j < part_size; j++) {
                current = current->next;
            }
            
            // 切断链表,形成独立部分
            if (current != nullptr) {
                ListNode* next_part = current->next;
                current->next = nullptr;
                current = next_part;
            }
        }
        
        return result;
    }
};

// 辅助函数:打印链表数组
void printParts(const vector<ListNode*>& parts) {
    for (int i = 0; i < parts.size(); i++) {
        cout << "Part " << i + 1 << ": ";
        ListNode* current = parts[i];
        while (current != nullptr) {
            cout << current->val << " ";
            current = current->next;
        }
        cout << endl;
    }
}

// 测试代码
int main() {
    // 构建示例链表:1->2->3->4->5->6->7->8->9->10
    ListNode* head = new ListNode(1);
    ListNode* current = head;
    for (int i = 2; i <= 10; i++) {
        current->next = new ListNode(i);
        current = current->next;
    }
    
    Solution solution;
    vector<ListNode*> result = solution.splitListToParts(head, 3);
    
    printParts(result);
    
    // 释放内存
    for (ListNode* part : result) {
        while (part != nullptr) {
            ListNode* temp = part;
            part = part->next;
            delete temp;
        }
    }
    
    return 0;
}

六、时间空间复杂度

  • 时间复杂度:O(n),其中n是链表长度。需要遍历链表两次:一次计算长度,一次进行分割。
  • 空间复杂度:O(k),用于存储结果数组,k是分割的部分数。

七、注意事项

  • 空链表处理:如果链表为空,返回k个nullptr。
  • k大于链表长度:当k > length时,前length部分是单个节点,后面的都是nullptr。
  • 指针操作:在切断链表时,要确保正确设置next指针为nullptr。
  • 边界检查:在移动指针时要检查current是否为nullptr,避免空指针异常。
  • 内存管理:在C++中,分割后的链表需要分别管理内存。
  • 结果顺序:结果数组中的链表必须保持原链表的顺序。
相关推荐
Lee川3 分钟前
从异步迷雾到优雅流程:JavaScript异步编程与内存管理的现代化之旅
javascript·面试
晴殇i2 小时前
揭秘JavaScript中那些“不冒泡”的DOM事件
前端·javascript·面试
绝无仅有2 小时前
Redis过期删除与内存淘汰策略详解
后端·面试·架构
绝无仅有3 小时前
Redis大Key问题排查与解决方案全解析
后端·面试·架构
AAA梅狸猫4 小时前
Looper.loop() 循环机制
面试
AAA梅狸猫4 小时前
Handler基本概念
面试
Gorway4 小时前
解析残差网络 (ResNet)
算法
Wect4 小时前
浏览器缓存机制
前端·面试·浏览器
拖拉斯旋风4 小时前
LeetCode 经典算法题解析:优先队列与广度优先搜索的巧妙应用
算法
Wect4 小时前
LeetCode 207. 课程表:两种解法(BFS+DFS)详细解析
前端·算法·typescript