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)空间。

两种方法对比:

相关推荐
不正经学生22 分钟前
C语言预处理详解:编译器真正动手之前的那些事
c语言·开发语言·算法·面试·bug
毕竟是shy哥3 小时前
计算YOLO数据集中每个类的目标数
算法·yolo·机器学习
M78佐菲3 小时前
Linux学习笔记:TCP协议
linux·笔记·学习·tcp/ip·算法
圣保罗的大教堂3 小时前
leetcode 1406. 石子游戏 III 困难
leetcode
晊晌_h4 小时前
嵌入式从0到精通——数据结构总结[特殊字符]
数据结构·算法·排序算法
我找到地球的支点啦5 小时前
Matlab系列(009) 一CRC循环冗余校验详解
开发语言·数据结构·算法·matlab·信息与通信
罗西的思考5 小时前
【OpenClaw具身硬件】ZeroClaw 源码阅读笔记(3)--- RAG
人工智能·算法·机器学习
浪里镖客6 小时前
位姿转换矩阵写法-个人习惯(计算机理解其实是相反的)
线性代数·算法·矩阵
小白羊丨9 小时前
如何诊断 Prompt 模板导致的效果下降?
人工智能·算法·prompt
OPEN-F10 小时前
C++11/14新特性精讲:移动语义与智能指针实战
开发语言·c++·算法