链表逆置应使用next指针现场翻转,时间复杂度O(n)、空间O(1);关键在于正确维护prev、curr、next_node三指针顺序:先保存next_node,再改curr->next=prev,最后更新prev和curr;循环结束返回prev。

用 next 指针现场翻转,别建新链表
链表逆置本质是把每个节点的 next 指向“前一个”,不是复制节点、不是递归构造新链。现场翻转时间复杂度 O(n),空间 O(1),最常用也最稳妥。
常见错误是搞混三个指针的更新顺序:比如先移动 prev 再改 curr->next,结果一改就断链,再也找不到后续节点。
- 必须先用临时变量保存
curr->next(即next_node),再改curr->next = prev - 然后才更新
prev = curr和curr = next_node - 循环结束时,
prev就是新头节点 —— 别返回curr(它已是nullptr)
Node* reverseList(Node* head) {
Node* prev = nullptr;
Node* curr = head;
while (curr != nullptr) {
Node* next_node = curr->next; // 先保活
curr->next = prev; // 翻转箭头
prev = curr; // 前移 prev
curr = next_node; // 前移 curr
}
return prev; // 新头
}头结点(dummy node)不参与逆置,但能简化边界处理
如果链表带哨兵头结点(head 是 dummy,真实首节点是 head->next),逆置范围通常只从真实首节点开始。这时候直接对 head->next 调用上面的逻辑即可,head 本身不动。
容易踩的坑是误把 dummy 当作数据节点一起翻转,导致 head->next 指向了原尾节点,而原尾节点的 next 又被改成指向倒数第二节点……整个链表乱成环或悬空。
立即学习“C++免费学习笔记(深入)”;
- 确认输入参数是否为 dummy 头:看函数签名或调用上下文,比如
void reverseFromHead(Node* dummy) - 若带 dummy,操作对象是
dummy->next,最后记得保持dummy->next = new_head - 不带 dummy 时,输入
head可能为nullptr,循环前要判空,否则curr->next解引用崩溃
递归写法看似简洁,但栈深度和可读性有硬伤
递归版本代码短:reverseList(head->next) 先翻转后面,再把 head 挂到末尾。但它在链表很长时会爆栈(比如 10w 节点),而且调试时很难跟踪指针变化。
更隐蔽的问题是“挂末尾”这步:需要找到翻转后子链的尾节点,而链表不支持随机访问,只能靠递归返回时“回溯”来改 head->next->next = head,新手极易写成 head->next = head 或漏设 head->next = nullptr,造成环。
- 必须在递归返回后立刻设
head->next = nullptr,否则旧连接残留,形成自环 -
head->next->next = head这行的前提是head->next非空,所以递归基要严格判head == nullptr || head->next == nullptr - 除非明确要求练递归或链表极短(
反转后记得检查 next 是否全清空,尤其涉及多次反转或复用节点
实际项目中,链表节点可能被反复反转、拼接、甚至和其他结构共用内存。反转完成后,原尾节点(现头节点)的 next 必须是 nullptr,否则下次遍历时会越界或进死循环。
这个点常被忽略,因为单次测试看不出问题 —— 但若某次反转后没清 next,之后又把它作为子链接入另一链表,就会出现“本该断开的地方连着旧链”的诡异行为。
- 在翻转循环结束后,确认新头节点的
next是nullptr;如果不确定,手动补一句new_head->next = nullptr - 如果节点来自内存池或对象池,且生命周期长于单次反转,务必重置所有相关指针,不能只依赖算法逻辑
- 用 sanitizer(如 ASan)跑一遍,能快速暴露悬空指针或 use-after-free,比靠日志猜强得多
翻转本身不难,难的是指针改得干净、边界控得稳、副作用想得到。尤其是 next 字段,多留一行赋值,少一堆半夜调试。







