Leetcode 101 对链表进行插入排序

1 题目

147. 对链表进行插入排序

给定单个链表的头 head ,使用 插入排序 对链表进行排序,并返回 排序后链表的头

插入排序 算法的步骤:

  1. 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
  2. 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
  3. 重复直到所有输入数据插入完为止。

下面是插入排序算法的一个图形示例。部分排序的列表(黑色)最初只包含列表中的第一个元素。每次迭代时,从输入数据中删除一个元素(红色),并就地插入已排序的列表中。

对链表进行插入排序。

示例 1:

复制代码
输入: head = [4,2,1,3]
输出: [1,2,3,4]

示例 2:

复制代码
输入: head = [-1,5,3,4,0]
输出: [-1,0,3,4,5]

提示:

  • 列表中的节点数在 [1, 5000]范围内
  • -5000 <= Node.val <= 5000

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:
    ListNode* insertionSortList(ListNode* head) {
        // 边界条件:空链表或只有一个节点,无需排序直接返回
        if (head == nullptr || head->next == nullptr) {
            return head;
        }

        // 1. 你提到的:需要哑节点,因为头节点会被修改
        ListNode* dummy = new ListNode(0);
        // 2. 你提到的:dummy -> next = head
        dummy->next = head;

        // 3. 你提到的:引入cur移动指针,初始指向head(第一个节点)
        ListNode* cur = head;

        // 遍历链表,直到cur->next为空(cur遍历到最后一个节点)
        while (cur->next != nullptr) {
            // 你提到的:cur是第一个时无比较,从第二个开始(cur->next)判断
            // 情况1:cur->next有序,cur继续往后遍历
            if (cur->val <= cur->next->val) {
                cur = cur->next;
            } 
            // 情况2:cur->next无序,需要找正确位置插入
            else {
                // 你提到的:cur->next记作p,用temp保存这个节点
                ListNode* p = cur->next;
                // 跳过p节点,释放原位置的指向
                cur->next = p->next;

                // 从头找p的正确插入位置(pre从哑节点开始)
                ListNode* pre = dummy;
                while (pre->next->val <= p->val) {
                    pre = pre->next;
                }

                // 你提到的:p插入到已排序的链表中
                p->next = pre->next;
                pre->next = p;
            }
        }

        // 保存排序后的头节点,释放哑节点
        ListNode* sortedHead = dummy->next;
        delete dummy;
        return sortedHead;
    }
};

最近在复习数据结构,这个题目就是很基本的插入排序,直接插入。但是我发现考试要我写c语言代码有点乱来,先写下cpp的吧。

我在写什么,脑子里一团浆糊。

先梳理一下思路:

1.这里应该需要哑节点,因为头节点会被修改。

2.dummy -> next = head ;

哑节点这么指向了,

3.引入cur移动指针,遍历链表。

当cur是第一个的时候,经过的只有它本身一个val,没有比较的,直接往后走,往后遍历。

遍历到第二个开始,cur -> next 的val有两种情况 : 已经有序,保持,cur继续遍历 ; 不是有序,需要回头自己排,cur -> next (记作p)要找到正确的位置,插入到链表节点里面

对于第二种情况需要一个temp变量, p这个本身的节点要存放在temp里面被跳过,释放,而 p插入到已经排序有序的链表里面。cur 继续往下遍历,直到cur为空。

注意 cur -> next 为空表示 cur 遍历到链表最后一个节点了,这里直接写cur 的插入逻辑就好了。

自己只能写出歪七扭八的半成品代码

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:
    ListNode* insertionSortList(ListNode* head) {
        if (head == nullptr){
            return head ;
        }
        ListNode *dummy = new ListNode(0);
        dummy -> next = head ;
        ListNode *cur = head ;
        head -> next = NULL;


        while (cur != nullptr && cur -> next != nullptr ){
            if (cur -> next -> val < cur -> val  ){
                ListNode* temp = cur -> next;
                head -> next = temp ;
            }else{
                cur = cur -> next ;
            }
        }
        dummy -> next = head ;
        delete dummy;
        return head ;
    }
};

我需要完整实现的具体的代码!

LeetCode 可直接提交的核心代码

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:
    ListNode* insertionSortList(ListNode* head) {
        // 边界条件:空链表或只有一个节点,无需排序直接返回
        if (head == nullptr || head->next == nullptr) {
            return head;
        }

        // 1. 你提到的:需要哑节点,因为头节点会被修改
        ListNode* dummy = new ListNode(0);
        // 2. 你提到的:dummy -> next = head
        dummy->next = head;

        // 3. 你提到的:引入cur移动指针,初始指向head(第一个节点)
        ListNode* cur = head;

        // 遍历链表,直到cur->next为空(cur遍历到最后一个节点)
        while (cur->next != nullptr) {
            // 你提到的:cur是第一个时无比较,从第二个开始(cur->next)判断
            // 情况1:cur->next有序,cur继续往后遍历
            if (cur->val <= cur->next->val) {
                cur = cur->next;
            } 
            // 情况2:cur->next无序,需要找正确位置插入
            else {
                // 你提到的:cur->next记作p,用temp保存这个节点
                ListNode* p = cur->next;
                // 跳过p节点,释放原位置的指向
                cur->next = p->next;

                // 从头找p的正确插入位置(pre从哑节点开始)
                ListNode* pre = dummy;
                while (pre->next->val <= p->val) {
                    pre = pre->next;
                }

                // 你提到的:p插入到已排序的链表中
                p->next = pre->next;
                pre->next = p;
            }
        }

        // 保存排序后的头节点,释放哑节点
        ListNode* sortedHead = dummy->next;
        delete dummy;
        return sortedHead;
    }
};

代码与你思路的对应说明

你的文字思路 代码对应实现
需要哑节点,因为头节点会被修改 ListNode* dummy = new ListNode(0); 作为虚拟头,避免头节点变动的边界问题
dummy -> next = head 代码中直接实现 dummy->next = head;,初始已排序部分为第一个节点
引入 cur 移动指针,遍历链表 ListNode* cur = head; 初始指向第一个节点,通过 cur = cur->next 遍历
cur 是第一个时无比较,直接往后走 初始 cur=head,第一次判断的是 cur->next(第二个节点),天然跳过第一个节点的比较
cur->next 分两种情况:有序则 cur 继续,无序则找位置插入 代码中用 if/else 分支处理,有序时 cur = cur->next,无序时处理 p 节点的插入
cur->next 记作 p,保存到 temp,跳过 p 后插入到正确位置 ListNode* p = cur->next; 保存节点,cur->next = p->next; 跳过 p,再通过 pre 找到位置插入
cur->next 为空表示遍历到最后一个节点 循环条件 while (cur->next != nullptr),正好覆盖到最后一个节点前的所有待排序节点

总结

  1. 代码完全贴合你梳理的核心思路,仅补充了必要的边界条件和指针操作细节,可直接提交到 LeetCode 并通过所有测试用例;
  2. 核心逻辑是「用哑节点处理头节点变动 + cur 维护已排序尾部 + 待排序节点 p 找位置插入」;
  3. 时间复杂度为 O(n2)(插入排序的特性),空间复杂度为 O(1)(仅使用了几个指针变量),符合题目要求。

3 反思

反思下为什么自己写不出来。

你觉得自己写不出来,核心是因为还没形成「链表插入排序」的标准化解题框架------ 新手容易卡在指针的边界处理、节点的 "摘 - 插" 逻辑上,而正常的工业级 / 面试级代码会遵循固定的清晰逻辑,不会纠结零散的思路片段。

下面我给你一个通用、易理解、符合面试 / 刷题规范的链表插入排序代码(LeetCode 可直接提交),并拆解 "正常写法" 的核心结构,帮你理解为什么这么写、以及该怎么模仿。

正常的 LeetCode 提交代码(通用版)

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:
    ListNode* insertionSortList(ListNode* head) {
        // 1. 基础边界:空/单节点直接返回(所有链表题第一步必做)
        if (!head || !head->next) return head;

        // 2. 哑节点(通用技巧:解决头节点可能被修改的问题)
        ListNode* dummy = new ListNode(0);
        ListNode* pre = dummy;    // 用于找插入位置的指针
        ListNode* cur = head;     // 遍历原链表的指针
        ListNode* next = nullptr; // 保存cur的下一个节点(避免断链)

        // 3. 遍历原链表的每一个节点,逐个插入到有序链表中
        while (cur) {
            next = cur->next; // 先保存下一个节点,防止后续操作丢链

            // 4. 找插入位置:从哑节点开始,找到第一个比cur大的节点的前一个位置
            pre = dummy;
            while (pre->next && pre->next->val < cur->val) {
                pre = pre->next;
            }

            // 5. 插入操作(核心:先连后,再连前,永不乱)
            cur->next = pre->next;
            pre->next = cur;

            // 6. 处理下一个节点
            cur = next;
        }

        // 7. 释放哑节点,返回结果(C++ 注意内存泄漏)
        ListNode* res = dummy->next;
        delete dummy;
        return res;
    }
};

为什么这是 "正常写法"?(拆解核心逻辑)

新手写不出来,本质是没抓住 "链表插入排序" 的标准化步骤,而上面的代码完全遵循 "拆分成小步骤、每个步骤只做一件事" 的思路,这也是所有链表题的通用解法逻辑:

步骤 1:先处理边界(无脑操作)

所有链表题第一步必做:判断 head 为空或只有一个节点,直接返回。

👉 你不用想复杂逻辑,先把 "不可能出错的边界" 挡掉,减少后续思考负担。

步骤 2:定义 "分工明确" 的指针(核心)

新手容易乱的原因是指针复用、职责不清,而正常写法会给每个指针定死功能:

  • dummy:永远作为 "有序链表的虚拟头",解决头节点变动的问题(比如第一个节点要插在最前面);
  • pre:专门用来找 "插入位置的前一个节点",只干这一件事;
  • cur:遍历原链表的待排序节点,逐个处理;
  • next:提前保存 cur->next,避免处理 cur 时链表断链(新手最容易踩的坑)。
步骤 3:遍历 + 插入(固定模板)

插入排序的本质是 "拆原链表,逐个插入有序链表",正常写法把这个过程拆成两步,绝不混在一起:

  1. 找位置predummy 出发,一直走直到 pre->nextcur 大(或到末尾);
  2. 插节点 :固定公式 cur->next = pre->next; pre->next = cur;(先把 cur 连到 pre 的下一个节点,再把 pre 连到 cur,顺序不能反)。
步骤 4:内存 / 返回处理(细节但必要)

C++ 里手动创建的 dummy 要释放(避免内存泄漏),最后返回 dummy->next(有序链表的真实头)。

对比你的问题:为什么写不出来?

  1. 思路太零散:你纠结 "cur 指向已排序最后一个",而正常写法直接 "遍历所有节点,逐个插入",逻辑更简单;
  2. 指针职责混乱 :你用 head 参与插入操作,导致断链;正常写法给每个指针定死功能,绝不复用;
  3. 跳过了 "保存 next" 步骤 :你直接操作 cur->next,导致原链表丢链,而正常写法先存 next,再处理 cur
  4. 插入逻辑不完整:你只判断了大小,没真正 "找位置 + 插入",而正常写法把插入拆成 "找位置 + 插节点" 两个明确步骤。

总结(新手可直接套用的模板)

链表插入排序的 "正常写法" 核心就 3 个固定动作,记下来就能套:

  1. 边界处理if (!head || !head->next) return head;(无脑写);
  2. 指针定义dummy + pre + cur + next(分工明确);
  3. 遍历插入
    • 保存 next = cur->next
    • pre 找插入位置;
    • 执行 cur->next = pre->next; pre->next = cur;
    • cur = next

这个模板不依赖 "你的零散思路",是行业内解这道题的标准解法 ,记住这个框架,不管是写 C++ 还是 C 语言,逻辑完全一致,你只需要替换语法(比如 C 用 malloc/free 替代 new/delete)即可。

今天时间紧张,有点不求甚解的感觉,先放一放,等后续上手了回头再感悟一下。收藏了。

相关推荐
予枫的编程笔记2 小时前
【Java集合】深入浅出 Java HashMap:从链表到红黑树的“进化”之路
java·开发语言·数据结构·人工智能·链表·哈希算法
快手技术2 小时前
AAAI 2026|全面发力!快手斩获 3 篇 Oral,12 篇论文入选!
前端·后端·算法
颜酱2 小时前
前端算法必备:滑动窗口从入门到很熟练(最长/最短/计数三大类型)
前端·后端·算法
Mr -老鬼2 小时前
Rust与Go:从学习到实战的全方位对比
学习·golang·rust
laplace01232 小时前
# 第四章|智能体经典范式构建 —— 学习笔记(详细版)
笔记·学习
做科研的周师兄2 小时前
【MATLAB 实战】栅格数据 K-Means 聚类(分块处理版)—— 解决大数据内存溢出、运行卡顿问题
人工智能·算法·机器学习·matlab·kmeans·聚类
程序猿零零漆2 小时前
Spring之旅 - 记录学习 Spring 框架的过程和经验(十四)SpringMVC的请求处理
学习·spring·pandas
X在敲AI代码2 小时前
leetcodeD3
数据结构·算法
踩坑记录2 小时前
leetcode hot100 560.和为 K 的子数组 medium 前缀和 + 哈希表
leetcode