1 题目
给定单个链表的头 head ,使用 插入排序 对链表进行排序,并返回 排序后链表的头 。
插入排序 算法的步骤:
- 插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
- 每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
- 重复直到所有输入数据插入完为止。
下面是插入排序算法的一个图形示例。部分排序的列表(黑色)最初只包含列表中的第一个元素。每次迭代时,从输入数据中删除一个元素(红色),并就地插入已排序的列表中。
对链表进行插入排序。

示例 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),正好覆盖到最后一个节点前的所有待排序节点 |
总结
- 代码完全贴合你梳理的核心思路,仅补充了必要的边界条件和指针操作细节,可直接提交到 LeetCode 并通过所有测试用例;
- 核心逻辑是「用哑节点处理头节点变动 + cur 维护已排序尾部 + 待排序节点 p 找位置插入」;
- 时间复杂度为 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:遍历 + 插入(固定模板)
插入排序的本质是 "拆原链表,逐个插入有序链表",正常写法把这个过程拆成两步,绝不混在一起:
- 找位置 :
pre从dummy出发,一直走直到pre->next比cur大(或到末尾); - 插节点 :固定公式
cur->next = pre->next; pre->next = cur;(先把cur连到pre的下一个节点,再把pre连到cur,顺序不能反)。
步骤 4:内存 / 返回处理(细节但必要)
C++ 里手动创建的 dummy 要释放(避免内存泄漏),最后返回 dummy->next(有序链表的真实头)。
对比你的问题:为什么写不出来?
- 思路太零散:你纠结 "cur 指向已排序最后一个",而正常写法直接 "遍历所有节点,逐个插入",逻辑更简单;
- 指针职责混乱 :你用
head参与插入操作,导致断链;正常写法给每个指针定死功能,绝不复用; - 跳过了 "保存 next" 步骤 :你直接操作
cur->next,导致原链表丢链,而正常写法先存next,再处理cur; - 插入逻辑不完整:你只判断了大小,没真正 "找位置 + 插入",而正常写法把插入拆成 "找位置 + 插节点" 两个明确步骤。
总结(新手可直接套用的模板)
链表插入排序的 "正常写法" 核心就 3 个固定动作,记下来就能套:
- 边界处理 :
if (!head || !head->next) return head;(无脑写); - 指针定义 :
dummy + pre + cur + next(分工明确); - 遍历插入 :
- 保存
next = cur->next; pre找插入位置;- 执行
cur->next = pre->next; pre->next = cur;; cur = next。
- 保存
这个模板不依赖 "你的零散思路",是行业内解这道题的标准解法 ,记住这个框架,不管是写 C++ 还是 C 语言,逻辑完全一致,你只需要替换语法(比如 C 用 malloc/free 替代 new/delete)即可。
今天时间紧张,有点不求甚解的感觉,先放一放,等后续上手了回头再感悟一下。收藏了。