反转链表:从双指针到递归,吃透链表反转的核心逻辑

大家好,今天我们来拆解一道链表的经典题目 ------力扣 206. 反转链表,这道题是理解链表指针操作的绝佳入门题,也是面试中高频出现的基础题。本文会从题目本身出发,一步步拆解双指针迭代和递归两种实现方式,帮你彻底搞懂链表反转的底层逻辑。


一、题目描述

题目链接: 206. 反转链表

题意: 反转一个单链表。

示例: 输入: 1->2->3->4->5->NULL输出: 5->4->3->2->1->NULL

链表反转的核心思想非常简单:不需要额外开辟新的链表空间,只需要修改链表节点的next指针指向,就能完成反转。原链表的头节点会变成反转后的尾节点,原链表的尾节点会变成反转后的头节点,整个过程只改变指针方向,不新增或删除节点。


二、核心思路:双指针迭代法(最推荐)

1. 思路解析

双指针迭代法是链表反转的最优解,时间复杂度为(O(n))(遍历一次链表),空间复杂度为(O(1))(仅使用常数级额外空间)。

核心逻辑是用两个指针pre和cur,配合一个临时变量temp完成遍历和反转:

  • cur:当前正在处理的节点,初始指向原链表的头节点head
  • pre:cur的前一个节点,初始为null(因为反转后,原链表的第一个节点会变成尾节点,尾节点的next为null)
  • temp:临时变量,用于保存cur的下一个节点,防止修改cur.next后链表断裂

反转的核心流程可以拆解为四步循环执行:

  1. 保存下一个节点 :temp = cur.next,先把cur的下一个节点存起来,避免后续修改指针时丢失后续节点
  2. 反转当前节点的指向 :cur.next = pre,让当前节点指向前一个节点,完成当前节点的反转
  3. 移动前指针 :pre = cur,pre前进到当前节点,作为下一轮的 "前一个节点"
  4. 移动当前指针 :cur = temp,cur前进到之前保存的下一个节点,继续处理后续节点

2. 完整代码实现

javascript

运行

ini 复制代码
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverseList = function(head) {
    let pre = null, temp = null, cur = head;
    while(cur) {
        temp = cur.next; // 步骤1:保存下一个节点
        cur.next = pre;  // 步骤2:反转当前节点的指向
        pre = cur;       // 步骤3:pre 前进到当前节点
        cur = temp;      // 步骤4:cur 前进到下一个节点
    }
    return pre;
};

3. 流程模拟(以1->2->3->4->5为例)

  1. 初始状态:pre = null,cur = 1,temp = null
  2. 第一次循环:temp = 2 → 1.next = null → pre = 1 → cur = 2
  3. 第二次循环:temp = 3 → 2.next = 1 → pre = 2 → cur = 3
  4. 第三次循环:temp = 4 → 3.next = 2 → pre = 3 → cur = 4
  5. 第四次循环:temp = 5 → 4.next = 3 → pre = 4 → cur = 5
  6. 第五次循环:temp = null → 5.next = 4 → pre = 5 → cur = null
  7. 循环结束,pre指向原链表的尾节点5,也就是反转后的新头节点,直接返回pre即可。

三、进阶实现:递归版(和迭代逻辑完全等价)

很多同学会觉得递归很难,但其实这段递归代码,就是双指针迭代法的 "递归改写版",核心逻辑和迭代版完全一致,只是把while循环换成了递归调用。

1. 思路解析

递归版的核心是尾递归 ,每次递归处理一个节点,把pre和cur作为参数传递,和迭代版的变量更新一一对应:

  • 递归终止条件:if(!head) return pre;,当head为null时,说明已经遍历完链表,此时的pre就是反转后的新头节点,直接返回即可
  • 递归逻辑:和迭代版的四步完全一致,先保存下一个节点,再反转当前节点,最后递归处理下一个节点

2. 完整代码实现

javascript

运行

php 复制代码
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverse = function(pre, head) {
    if(!head) return pre; // 递归终止条件
    const temp = head.next; // 步骤1:保存下一个节点
    head.next = pre;        // 步骤2:反转当前节点的指向
    pre = head;             // 步骤3:pre 前进到当前节点
    return reverse(pre, temp); // 步骤4:递归处理下一个节点
}

var reverseList = function(head) {
    return reverse(null, head); // 初始调用:pre=null,head=原链表头节点
};

3. 递归流程模拟(以1->2->3->4->5为例)

  1. 初始调用:reverseList(head) → reverse(null, 1)
  2. 第一次递归:temp = 2 → 1.next = null → pre = 1 → 调用reverse(1, 2)
  3. 第二次递归:temp = 3 → 2.next = 1 → pre = 2 → 调用reverse(2, 3)
  4. 第三次递归:temp = 4 → 3.next = 2 → pre = 3 → 调用reverse(3, 4)
  5. 第四次递归:temp = 5 → 4.next = 3 → pre = 4 → 调用reverse(4, 5)
  6. 第五次递归:temp = null → 5.next = 4 → pre = 5 → 调用reverse(5, null)
  7. 终止条件触发:!head为true,返回pre=5,递归逐层返回,最终得到反转后的链表头节点。

四、两种实现方式对比

表格

对比维度 双指针迭代法 尾递归版
时间复杂度 (O(n)),每个节点只处理一次 (O(n)),每个节点只处理一次
空间复杂度 (O(1)),仅使用常数级额外空间 (O(n)),递归栈深度等于链表长度
优缺点 空间效率高,无栈溢出风险,工程上推荐 代码更简洁,和迭代逻辑一一对应,适合理解递归思想

需要注意的是,JavaScript 大部分环境不支持尾递归优化,当链表很长时,递归版可能会出现栈溢出错误,所以实际工程和面试中,优先推荐双指针迭代法。


五、常见易错点总结

  1. 忘记保存下一个节点 :如果不提前用temp保存cur.next,修改cur.next = pre后,就会丢失后续节点,导致链表断裂
  2. 递归版遗漏终止条件 :如果没有if(!head) return pre;,当head为null时,执行head.next会抛出Cannot read property 'next' of null错误
  3. 指针移动顺序错误 :必须先修改cur.next,再移动pre和cur,顺序颠倒会导致逻辑混乱
  4. 初始值设置错误 :pre必须初始为null,否则原链表的第一个节点反转后,next会指向错误的节点

这道题的核心就是理解指针的指向修改,无论是迭代还是递归,本质上都是用同样的逻辑遍历链表、修改指针。把这道题吃透,后续很多链表题(比如链表的区间反转、两两交换节点)都能轻松理解。

如果觉得这篇文章对你有帮助,欢迎点赞收藏,后续会更新更多链表、数组等基础算法题的拆解~

相关推荐
xys_6789 分钟前
深挖数据结构:树和二叉树(上)
数据结构·算法
honkun612 分钟前
vue 表格组件 vxe-table 修改滚动条样式,修改滚动条样式,统一宽度、颜色与自动隐藏
前端·javascript·vue.js
Interview Aid11233 分钟前
Google SWE Intern VO 面经:两轮 Coding,Follow-up 考察 BFS 状态
算法
FRing031 小时前
JavaScript入门到精通二十四(正则表达式、元字符、修饰符)
开发语言·前端·javascript·正则表达式
琳达爱分享1 小时前
从单机令牌桶到集群限流:一次配额超发的复盘
后端·算法
range1 小时前
带TTL的LRU缓存的实现-字节手撕-26/10/19
算法
糖果豆豆1 小时前
n log n 到底从哪来?排序下界、2-SAT 与 3-SAT、停机问题,卡的是同一件事
算法·架构
地平线开发者1 小时前
【玩转 征程6 算法工具链:PTQ 工具篇】hb_verifier
算法·自动驾驶
171320330计算机毕设编程1 小时前
计算机毕业设计 基于springboot+vue的在线观鸟视频播放网站 LW PPT
java·大数据·vue.js·spring boot·算法·课程设计
大明二代1 小时前
从 RAG 到有记忆的 Agent:Mastra Memory 与 PostgreSQL 实战
前端·javascript·人工智能