最稳妥的迭代写法是三指针法:先暂存next=curr->next,再curr->next=prev,最后prev=curr、curr=next;prev初始化为nullptr,循环条件为curr!=nullptr,返回prev。

翻转单链表最稳妥的迭代写法
直接用三指针迭代,比递归更安全、空间复杂度 O(1),且不会因链表过长触发栈溢出。nullptr 判空和指针更新顺序是关键。
常见错误是先移动 prev 再断开 curr->next,导致后续节点丢失。正确顺序必须是:先暂存 next = curr->next,再改 curr->next,最后推进 prev 和 curr。
-
prev初始化为nullptr,不是head - 循环条件用
curr != nullptr,不是curr->next != nullptr - 退出时
prev指向新头节点,返回它
ListNode* reverseList(ListNode* head) {
ListNode* prev = nullptr;
ListNode* curr = head;
while (curr != nullptr) {
ListNode* next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
return prev;
}递归翻转链表要注意边界和返回值
递归写法简洁,但实际项目中要小心:链表长度超几百就可能栈溢出;且必须准确理解“递归返回的是新头节点”这个点,否则容易误返回 head 或中间节点。
典型错误是忘记在递归调用后修改当前节点的 next 指针,导致翻转不彻底(尾节点仍指向原下一个节点,形成环或漏连)。
立即学习“C++免费学习笔记(深入)”;
- 递归终止条件是
head == nullptr || head->next == nullptr - 递归调用后,
head->next->next = head这步不能省 - 必须置
head->next = nullptr,否则新链表尾部悬空或成环
ListNode* reverseList(ListNode* head) {
if (!head || !head->next) return head;
ListNode* newHead = reverseList(head->next);
head->next->next = head;
head->next = nullptr;
return newHead;
}翻转前 N 个节点或指定区间怎么处理
这类变体不能直接套用基础翻转逻辑,必须先定位区间起点、记录断开位置,并在翻转后重新接回。最容易错的是指针衔接——尤其是翻转后原 prev 变成新区间尾,而原 curr 是区间后第一个节点,需手动拼接。
如果翻转范围超出链表长度,行为取决于需求:严格按 N 个翻,还是翻到末尾为止?代码里得显式判断 curr 是否为 nullptr 并提前退出。
- 先走
m-1步找到翻转起始前驱(若 m > 1),保存为prevStart - 从第 m 个节点开始翻转,同时计数,到第 n 个时停止
- 翻转段的新头连到
prevStart->next,旧头(现尾)连到后续段
LeetCode 206 验证时为什么老是报 heap-use-after-free
多数是因为在翻转过程中访问了已被修改 next 指针的节点,比如循环里写了 curr = curr->next 却没提前保存原值,导致跳过节点或解引用已失效地址。
另一个高频原因是测试用例复用同一组节点,而你的函数意外修改了输入链表之外的内存(比如越界写 next 字段),尤其在边界 case(空链表、单节点)下容易暴露。
- 务必用
ListNode*类型做所有指针操作,别用int*或裸地址强转 - 提交前手动测
reverseList(nullptr)和reverseList(single_node) - 本地调试可加 assert(
curr != nullptr) 配合 AddressSanitizer
翻转链表看着简单,真正写稳的核心不在算法本身,而在每一步指针赋值前是否清楚“这个地址还有效吗”“下一步要读哪个字段”。稍一松懈,nullptr 解引用或悬空指针就来了。


















