LeetCode 206. 反转链表:迭代 + 递归双解法全解析

206. 反转链表

一、题目

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

示例 1:

输入:head = [1,2,3,4,5]

输出:[5,4,3,2,1]

示例 2:

输入:head = [1,2]

输出:[2,1]

示例 3:

输入:head = []

输出:[]

提示:

  • 链表中节点的数目范围是 [0, 5000]
  • -5000 <= Node.val <= 5000
    进阶:链表可以选用迭代递归方式完成反转。你能否用两种方法解决这道题?

二、思路

根据题目的进阶要求,我们接下来使用递归法和迭代法两种方法来解决这个算法题目。

递归法:

自己调用自己。识别方法:在reverse函数的代码身体里,又看到了reverse(...)这个名字。

  1. 判断终止条件
  2. 使用temp暂存下一步
  3. 执行反转
  4. 指针双双后移

迭代法:

使用循环,可以看到 for 或 while 循环。

  1. 初始化两个 cur 和 pre 指针
  2. 开启循环,只要cur还有值,就要一直循环
  3. 使用temp暂存下一步
  4. 执行反转
  5. 指针双双后移
  6. 循环结束时,返回pre,结束

三、代码

递归法:

javascript 复制代码
//递归辅助函数
function reverse(cur,pre){
 // 1. 终止条件 (Base Case)
 // 如果 cur 为空,说明已经越过了链表的最后一个节点。
 // 此时 pre 指向的就是原链表的最后一个节点(也就是新链表的头节点)。
 // 将其返回,这个返回值会像接力棒一样一层层传回主函数,return后终止了。
    if(!cur) return pre;
    // 2. 暂存下一步 (Save Next)
    // 因为接下来要切断 cur 和后面的联系,所以必须先把 cur.next 存起来,
    // 否则后面的一长串链表就丢了。
    const temp = cur.next;
    //3. 执行反转
    cur.next = pre;
    //进入下一层调用,指针向后移动
    //cur后移一位到temp
    //pre后移一位到cur
    return reverse(temp,cur)
}
//主函数
var reverseList = function(head) {
    //当前cur节点是head
    //当前pre节点是null
    return reverse(head,null);
};

迭代法:

javascript 复制代码
var reverseList = function(head){
    //初始化两个指针:pre前驱,cur当前
    let pre = null;
    let cur = head;
    //开启循环,只要cur还有值,就继续干活
    while(cur){
        //暂存下一步temp(防止断链)
        const temp = cur.next;
        //反转指针
        cur.next=pre;
        //将指针集体向后移动,准备处理下一个
        pre=cur;//pre移动到当前cur
        cur=temp;//当前cur移动到temp(下一位)
    }
    //循环结束时,cur变成了null,pre刚好停在新的头节点上
    return pre;
}

四、复杂度

递归法:

缺点:这种写法属于尾递归,如果链表特别长,可能会导致栈溢出错误。
时间复杂度:O(N)

  • 分析: N 是链表的长度。代码中的递归函数 reverse 会被调用 N+1N+1N+1 次(每个节点一次,最后 null 一次)。
  • 操作: 每次调用内部只做了常数次操作(赋值、指针修改),即 O(1)。

空间复杂度:O(N) ⚠️ (这是与迭代法最大的不同)

  • 分析: 递归的本质是函数调用栈 (Call Stack)。
  • 堆叠:
    • 当处理节点 1 时,函数没结束,等着节点 2 的结果;
    • 当处理节点 2 时,等着节点 3 的结果...
    • 这意味着系统内存中同时保留了 NNN 层函数的上下文(每一层都要存 cur, pre, temp 这些变量)。

迭代法:

时间复杂度:O(N)

结论:线性时间复杂度。

其中 N 是链表的长度(节点数量)。

为什么是 O(N)?

  • 一次遍历: 代码中有一个 while (cur) 循环。这个循环从链表的头节点 (head) 开始,顺着 next 指针一直走到尾部 (null)。
  • 不走回头路: 每个节点被访问且仅被访问一次。
  • 常数级操作: 在循环内部,不管是 temp = cur.next 还是指针的赋值操作,都是最基本的指令,耗时是固定的 O(1)O(1)O(1)。

空间复杂度:O(1)

结论:常数空间复杂度。

这是迭代法相对于递归法最大的优势。

为什么是 O(1)?

  • 变量数量固定: 无论链表有 10 个节点还是 10 万个节点,这段代码运行所需的额外内存空间是固定不变的。
  • 仅使用的变量: 我们只申请了 3 个指针变量的内存空间:
    1. pre (前驱)
    2. cur (当前)
    3. temp (临时存储)
  • 原地操作 (In-Place): 我们直接修改了原链表节点的 next 指针,没有创建新的链表,也没有像递归那样消耗调用栈(Call Stack)空间。

两种方法对比:

相关推荐
NAGNIP2 小时前
才发现TensorBoard是个可视化的神器!
算法
_dindong2 小时前
算法杂谈:回溯路线
数据结构·算法·动态规划·bfs·宽度优先
咋吃都不胖lyh2 小时前
详解 UCB 算法的置信区间与核心逻辑(通俗 + 公式 + 实例)
人工智能·算法·机器学习
DanyHope2 小时前
LeetCode 两数之和:从 O (n²) 到 O (n),空间换时间的经典实践
前端·javascript·算法·leetcode·职场和发展
free-elcmacom2 小时前
机器学习高阶教程<6>推荐系统高阶修炼手册:混排、多任务与在线学习,解锁精准推荐新境界
人工智能·python·学习·算法·机器学习·机器人
断剑zou天涯2 小时前
【算法笔记】AC自动机
java·笔记·算法
IT猿手2 小时前
基于粒子群算法与动态窗口混合的无人机三维动态避障路径规划研究,MATLAB代码
算法·matlab·无人机·多目标优化算法·多目标算法
民乐团扒谱机2 小时前
【微实验】仿AU音频编辑器开发实践:从零构建音频可视化工具
算法·c#·仿真·audio·fft·频谱
DanyHope2 小时前
LeetCode 283. 移动零:双指针双解法(原地交换 + 覆盖补零)全解析
数据结构·算法·leetcode