力扣206.反转链表-双指针法(推荐)

链表反转是数据结构与算法中的经典问题,也是面试中的高频考点。这个问题看似简单,却能考察程序员对指针操作、递归思维和边界条件的处理能力。在本文中,我们将深入探讨链表反转的两种经典解法:迭代法和递归法,并提供详细的代码实现和优化技巧。

问题描述

给定一个单链表的头节点 head,要求返回反转后的链表。

示例:

text

复制代码
输入: 1 → 2 → 3 → 4 → 5 → null
输出: 5 → 4 → 3 → 2 → 1 → null

方法一:迭代法(推荐)

核心思想

通过两个指针 precur 遍历链表,在遍历过程中逐个反转节点间的指针方向。

算法步骤

  1. 初始化两个指针:pre = null, cur = head

  2. 遍历链表,直到 cur 为 null:

    • 保存 cur 的下一个节点:temp = cur.next

    • 反转指针:cur.next = pre

    • 移动指针:pre = cur, cur = temp

  3. 返回 pre(新的头节点)

代码实现

java 复制代码
public ListNode reverseList(ListNode head) {
    ListNode pre = null;
    ListNode cur = head;
    
    while (cur != null) {
        // 保存下一个节点,防止断链
        ListNode temp = cur.next;
        // 反转指针
        cur.next = pre;
        // 移动指针
        pre = cur;
        cur = temp;
    }
    
    return pre;
}

图解过程

text

复制代码
初始状态:
pre = null, cur = 1 → 2 → 3 → 4 → null

第1次循环:
temp = 2
1.next = null
pre = 1, cur = 2
结果:null ← 1  2 → 3 → 4 → null

第2次循环:
temp = 3
2.next = 1
pre = 2, cur = 3
结果:null ← 1 ← 2  3 → 4 → null

第3次循环:
temp = 4
3.next = 2
pre = 3, cur = 4
结果:null ← 1 ← 2 ← 3  4 → null

第4次循环:
temp = null
4.next = 3
pre = 4, cur = null
结果:null ← 1 ← 2 ← 3 ← 4

结束循环,返回pre=4

复杂度分析

  • 时间复杂度:O(n),每个节点只遍历一次

  • 空间复杂度:O(1),只使用了固定的几个指针变量

方法二:递归法

核心思想

利用递归的栈结构,从链表的尾部开始反转,先反转后面的部分,再处理当前节点。

算法步骤

  1. 递归终止条件:链表为空或只有一个节点

  2. 递归反转以 head.next 为头节点的子链表

  3. 将当前节点连接到已反转的子链表的尾部

  4. 返回新的头节点

代码实现

java 复制代码
public ListNode reverseList(ListNode head) {
    // 递归终止条件
    if (head == null || head.next == null) {
        return head;
    }
    
    // 递归反转后续链表
    ListNode newHead = reverseList(head.next);
    
    // 将当前节点连接到反转后的链表尾部
    head.next.next = head;
    head.next = null;
    
    return newHead;
}

图解递归过程

text

复制代码
初始链表:1 → 2 → 3 → 4 → null

递归调用栈:
reverse(1)调用reverse(2)
reverse(2)调用reverse(3)  
reverse(3)调用reverse(4)
reverse(4)返回4(base case)

开始回溯:
reverse(3)中:3.next.next = 3,即4.next = 3
             3.next = null
             返回newHead=4(链表:4 → 3 → null)

reverse(2)中:2.next.next = 2,即3.next = 2
             2.next = null
             返回newHead=4(链表:4 → 3 → 2 → null)

reverse(1)中:1.next.next = 1,即2.next = 1
             1.next = null
             返回newHead=4(链表:4 → 3 → 2 → 1 → null)

复杂度分析

  • 时间复杂度:O(n),每个节点处理一次

  • 空间复杂度:O(n),递归调用栈的深度为n

两种方法的对比

特性 迭代法 递归法
时间复杂度 O(n) O(n)
空间复杂度 O(1) O(n)(递归栈)
代码简洁性 中等 简洁优雅
理解难度 较易 较难(需要理解递归)
适用场景 链表较长时 链表较短或需要简洁实现时

常见错误与陷阱

错误1:指针丢失

java

复制代码
// 错误的写法
while (cur != null) {
    cur.next = pre;  // 直接修改cur.next,丢失了下一个节点
    pre = cur;
    cur = cur.next;  // cur.next已经是pre,导致循环异常
}

解决方法:在修改指针前,先用临时变量保存下一个节点。

错误2:空指针异常

java

复制代码
// 错误的写法
ListNode temp = cur.next;  // 如果cur为null,这里会抛异常
while (cur != null) {
    // ...
}

解决方法:确保在访问节点属性前进行空值检查。

错误3:忘记处理原头节点的next指针

在递归法中,如果不将原头节点的next设为null,会导致链表成环。

变体问题

1. 反转链表的一部分(比如反转中间到最后,那要先找到中间位置:快慢指针)

反转链表中从位置mn的部分。

解题思路

  1. 找到第m-1个节点(连接点)

  2. 反转mn的节点

  3. 重新连接三部分

2. K个一组反转链表

每k个节点一组进行反转,最后不足k个的保持原样。

解题思路

  1. 统计链表长度

  2. 按组进行反转

  3. 递归或迭代处理剩余部分

3. 反转相邻节点

交换相邻的两个节点。

解题思路

使用虚拟头节点,每次处理两个节点。

面试技巧

  1. 先画图:在面试中,先用简单的例子画图说明思路

  2. 考虑边界:空链表、单节点链表、双节点链表

  3. 测试用例

    • null(空链表)

    • 1 → null(单节点)

    • 1 → 2 → null(双节点)

    • 1 → 2 → 3 → 4 → 5 → null(多节点)

  4. 复杂度分析:主动分析时间和空间复杂度

  5. 代码优化:写出代码后,思考是否有优化空间

实际应用场景

  1. 浏览器历史记录:前进后退功能需要类似的反转操作

  2. 撤销操作:文本编辑器中的撤销功能

  3. 内存管理:某些垃圾回收算法中

  4. 数据同步:双向同步的数据结构

扩展学习

  1. 双向链表反转 :需要同时处理prevnext指针

  2. 环形链表反转:需要特别注意环的断开与连接

  3. 原地反转数组:类似的思想可以应用于数组

总结

链表反转是掌握指针操作的基础,通过这个问题可以深入理解:

  • 指针的移动与修改

  • 递归思维的应用

  • 边界条件的处理

  • 时间与空间复杂度的权衡

建议初学者先从迭代法开始理解,掌握指针的操作技巧,再尝试理解递归法的精妙之处。在实际工程中,迭代法通常更受欢迎,因为它没有递归的栈溢出风险,且空间效率更高。

记住:理解一个算法的最好方式是多画图、多写代码、多思考边界情况。希望这篇文章能帮助你彻底掌握链表反转问题!

相关推荐
智航GIS2 小时前
ArcGIS大师之路500技---037普通克里金VS泛克里金
人工智能·算法·arcgis
晨晖22 小时前
循环队列:出队
算法
LYFlied2 小时前
【每日算法】LeetCode 70. 爬楼梯:从递归到动态规划的思维演进
算法·leetcode·面试·职场和发展·动态规划
一起养小猫2 小时前
LeetCode100天Day2-验证回文串与接雨水
java·leetcode
YoungHong19922 小时前
面试经典150题[073]:从中序与后序遍历序列构造二叉树(LeetCode 106)
leetcode·面试·职场和发展
最晚的py2 小时前
聚类的评估方法
人工智能·算法·机器学习
业精于勤的牙2 小时前
浅谈:算法中的斐波那契数(五)
算法·leetcode·职场和发展
液态不合群2 小时前
查找算法详解
java·数据结构·算法
代码游侠2 小时前
学习笔记——Linux进程间通信(IPC)
linux·运维·笔记·学习·算法