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

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


一、题目描述

题目链接: 206. 反转链表

题意: 反转一个单链表。

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

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


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

1. 思路解析

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

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

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

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

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

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 = nullcur = 1temp = null
  2. 第一次循环:temp = 21.next = nullpre = 1cur = 2
  3. 第二次循环:temp = 32.next = 1pre = 2cur = 3
  4. 第三次循环:temp = 43.next = 2pre = 3cur = 4
  5. 第四次循环:temp = 54.next = 3pre = 4cur = 5
  6. 第五次循环:temp = null5.next = 4pre = 5cur = null
  7. 循环结束,pre指向原链表的尾节点5,也就是反转后的新头节点,直接返回pre即可。

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

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

1. 思路解析

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

  • 递归终止条件:if(!head) return pre;,当headnull时,说明已经遍历完链表,此时的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 = 21.next = nullpre = 1 → 调用reverse(1, 2)
  3. 第二次递归:temp = 32.next = 1pre = 2 → 调用reverse(2, 3)
  4. 第三次递归:temp = 43.next = 2pre = 3 → 调用reverse(3, 4)
  5. 第四次递归:temp = 54.next = 3pre = 4 → 调用reverse(4, 5)
  6. 第五次递归:temp = null5.next = 4pre = 5 → 调用reverse(5, null)
  7. 终止条件触发:!headtrue,返回pre=5,递归逐层返回,最终得到反转后的链表头节点。

四、两种实现方式对比

表格

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

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


五、常见易错点总结

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

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

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

相关推荐
代码煮茶8 小时前
Vue3 Mock 数据实战 | 用 Mockjs + vite-plugin-mock 搭建前端独立开发环境
javascript·vue.js
玖釉-8 小时前
旋转图像:从矩阵转置、镜像到坐标变换的系统理解
c++·windows·算法·图形渲染
码银8 小时前
在若依中如何新建一个模块(图文教程)
java·javascript
fengenrong8 小时前
20260522
算法
OrangeForce8 小时前
Monknow 书签导出工具:从本地存储提取数据并转为标准 HTML 书签
javascript·chrome·python·edge·html·firefox
一条大祥脚9 小时前
Codeforces Round 1099 (Div. 2) 构造|贪心|图论|还原数组
java·算法·图论
mCell9 小时前
JavaScript:从事件循环到手写 Promise
javascript·面试·浏览器
Sheldon Chao9 小时前
Lecture 7 基于策略梯度的算法
人工智能·算法·机器学习
始三角龙9 小时前
LeetCode hoot 100 -- 缺失的第一个正整数
算法·leetcode·职场和发展