【Hot100|22-LeetCode 206. 反转链表 - 完整解法详解】

LeetCode 206. 反转链表 - 完整解法详解

一、问题理解

问题描述

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

示例

text

复制代码
输入: head = [1,2,3,4,5]
输出: [5,4,3,2,1]

输入: head = [1,2]
输出: [2,1]

输入: head = []
输出: []

要求

  • 时间复杂度:O(n)

  • 空间复杂度:递归解法 O(n),迭代解法 O(1)

  • 链表可以修改

二、核心思路:节点指针反转

基本思想

反转链表的核心是将每个节点的 next 指针指向前一个节点。我们需要三个指针:

  1. prev: 指向已反转部分的头节点

  2. curr: 指向当前要处理的节点

  3. next: 保存当前节点的下一个节点,防止链表断裂

递归与迭代

  • 迭代法:通过循环逐个反转节点指针

  • 递归法:通过递归到达链表末尾,然后在返回过程中反转指针

三、代码逐行解析

方法一:迭代法(最优解)

Python 解法

python

复制代码
# Definition for singly-linked list.
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        # 初始化三个指针
        prev = None      # 已反转部分的头节点
        curr = head      # 当前要处理的节点
        
        # 遍历链表
        while curr:
            # 保存当前节点的下一个节点
            next_node = curr.next
            # 反转指针:当前节点指向前一个节点
            curr.next = prev
            # 移动prev和curr指针
            prev = curr
            curr = next_node
        
        # 返回新的头节点
        return prev
Java 解法

java

复制代码
class Solution {
    public ListNode reverseList(ListNode head) {
        // 初始化指针
        ListNode prev = null;   // 已反转部分的头节点
        ListNode curr = head;   // 当前要处理的节点
        
        // 遍历链表
        while (curr != null) {
            // 保存下一个节点
            ListNode next = curr.next;
            // 反转指针
            curr.next = prev;
            // 移动指针
            prev = curr;
            curr = next;
        }
        
        // 返回新的头节点
        return prev;
    }
}

方法二:递归法(简洁但空间复杂度高)

Python 解法

python

复制代码
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        # 递归终止条件:空链表或只有一个节点
        if head is None or head.next is None:
            return head  # 链表末尾,即反转后的头节点
        
        # 递归反转剩余部分
        rev_head = self.reverseList(head.next)  # 递:到达链表末尾,拿到新链表的头节点
        
        # 归:在返回过程中反转指针
        tail = head.next    # head.next是反转后链表的尾节点
        tail.next = head    # 将当前节点接到反转链表的末尾
        head.next = None    # 断开原连接,防止成环
        
        return rev_head     # 返回反转后的头节点
Java 解法

java

复制代码
class Solution {
    public ListNode reverseList(ListNode head) {
        // 递归终止条件
        if (head == null || head.next == null) {
            return head;
        }
        
        // 递归反转剩余部分
        ListNode revHead = reverseList(head.next);
        
        // 在返回过程中反转指针
        head.next.next = head;  // 将当前节点接到反转链表的末尾
        head.next = null;       // 断开原连接,防止成环
        
        return revHead;
    }
}

四、Java 与 Python 语法对比

1. 链表节点定义

操作 Java Python
节点类 需要预定义 需要预定义
创建节点 new ListNode(val) ListNode(val)

2. 空值判断

操作 Java Python
检查null node == null node is None

3. 指针操作

操作 Java Python
访问属性 node.next node.next
修改属性 node.next = value node.next = value

五、实例演示

示例:head = [1,2,3,4,5]

迭代法步骤:
  1. 初始状态

    • prev = None

    • curr = 1 → 2 → 3 → 4 → 5

  2. 第1次循环

    • next_node = curr.next = 2

    • curr.next = prev1 → None

    • prev = curr = 1

    • curr = next_node = 2

    • 状态:1 → None2 → 3 → 4 → 5

  3. 第2次循环

    • next_node = curr.next = 3

    • curr.next = prev2 → 1 → None

    • prev = curr = 2

    • curr = next_node = 3

    • 状态:2 → 1 → None3 → 4 → 5

  4. 第3次循环

    • next_node = curr.next = 4

    • curr.next = prev3 → 2 → 1 → None

    • prev = curr = 3

    • curr = next_node = 4

    • 状态:3 → 2 → 1 → None4 → 5

  5. 第4次循环

    • next_node = curr.next = 5

    • curr.next = prev4 → 3 → 2 → 1 → None

    • prev = curr = 4

    • curr = next_node = 5

    • 状态:4 → 3 → 2 → 1 → None5

  6. 第5次循环

    • next_node = curr.next = None

    • curr.next = prev5 → 4 → 3 → 2 → 1 → None

    • prev = curr = 5

    • curr = next_node = None

    • 状态:5 → 4 → 3 → 2 → 1 → None

  7. 循环结束 ,返回 prev = 5

递归法步骤:

text

复制代码
初始调用:reverseList(1)
    调用:reverseList(2)
        调用:reverseList(3)
            调用:reverseList(4)
                调用:reverseList(5)
                    终止条件满足,返回5
                返回:5,此时head=4
                tail = head.next = 5
                tail.next = head → 5 → 4
                head.next = None → 4 → None
                返回5 → 4
            返回:5,此时head=3
            tail = head.next = 4
            tail.next = head → 4 → 3
            head.next = None → 3 → None
            返回5 → 4 → 3
        返回:5,此时head=2
        tail = head.next = 3
        tail.next = head → 3 → 2
        head.next = None → 2 → None
        返回5 → 4 → 3 → 2
    返回:5,此时head=1
    tail = head.next = 2
    tail.next = head → 2 → 1
    head.next = None → 1 → None
    返回5 → 4 → 3 → 2 → 1
最终结果:5 → 4 → 3 → 2 → 1

六、关键细节解析

1. 为什么迭代法需要保存next_node?

  • 当我们修改 curr.next 时,会丢失原链表的下一个节点

  • 必须提前保存 curr.next,否则无法继续遍历链表

2. 递归法的终止条件为什么是 head is None or head.next is None

  • head is None:处理空链表的情况

  • head.next is None:链表只有一个节点时,反转后还是它自己

  • 这两个条件都表示不需要继续反转

3. 为什么递归法中要设置 head.next = None

  • 如果不设置,原链表的头节点会指向第二个节点,而第二个节点又指向头节点,形成环

  • 例如:原始链表 1 → 2 → 3,反转后如果不断开,会是 1 ↔ 2 ← 3

4. 递归法的空间复杂度为什么是 O(n)?

  • 递归调用会使用系统栈空间

  • 对于长度为 n 的链表,递归深度为 n

  • 空间复杂度为 O(n)

5. 如何处理空链表?

  • 迭代法:while curr 条件会跳过空链表,直接返回 prev = None

  • 递归法:终止条件 head is None 会直接返回 None

七、复杂度分析

迭代法

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

  • 空间复杂度:O(1),只使用了常数个指针变量

递归法

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

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

八、其他解法

解法一:头插法

python

复制代码
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        # 创建一个虚拟头节点
        dummy = ListNode()
        
        # 遍历原链表,将每个节点插入到虚拟头节点之后
        curr = head
        while curr:
            # 保存下一个节点
            next_node = curr.next
            # 将当前节点插入到dummy之后
            curr.next = dummy.next
            dummy.next = curr
            # 继续处理下一个节点
            curr = next_node
        
        return dummy.next

解法二:栈方法(使用额外空间)

python

复制代码
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        if not head:
            return None
        
        # 使用栈存储所有节点
        stack = []
        curr = head
        while curr:
            stack.append(curr)
            curr = curr.next
        
        # 从栈中弹出节点构建反转链表
        new_head = stack.pop()
        curr = new_head
        while stack:
            curr.next = stack.pop()
            curr = curr.next
        
        # 最后一个节点的next置为None
        curr.next = None
        
        return new_head

解法三:双指针简化版

python

复制代码
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        # 使用两个指针
        prev = None
        curr = head
        
        while curr:
            # Python的并行赋值,不需要临时变量
            curr.next, prev, curr = prev, curr, curr.next
        
        return prev

九、常见问题与解答

Q1: 反转链表时如何处理环?

A1: 题目假设链表无环。如果有环,反转操作会变得复杂,需要先检测环。

Q2: 如何测试反转链表的正确性?

A2: 可以测试以下情况:

  • 空链表

  • 单节点链表

  • 两个节点的链表

  • 多个节点的链表

  • 验证反转后头尾节点是否正确

Q3: 递归法和迭代法哪个更好?

A3:

  • 迭代法:空间复杂度 O(1),更优

  • 递归法:代码简洁,但空间复杂度 O(n),可能栈溢出

  • 在实际应用中,迭代法通常更受青睐

Q4: 如果需要反转部分链表怎么办?

A4: 可以使用类似的方法,但要记录:

  • 反转部分的前一个节点(用于连接)

  • 反转部分的第一个节点(反转后变为尾节点)

  • 反转部分的最后一个节点(反转后变为头节点)

Q5: 如何同时反转链表和打印节点值?

A5: 可以在反转过程中或反转后遍历链表:

python

复制代码
def reverse_and_print(head):
    # 反转链表
    prev = None
    curr = head
    while curr:
        next_node = curr.next
        curr.next = prev
        prev = curr
        curr = next_node
    
    # 打印反转后的链表
    curr = prev
    while curr:
        print(curr.val, end=" ")
        curr = curr.next
    print()

十、相关题目

1. LeetCode 92. 反转链表 II

python

复制代码
class Solution:
    def reverseBetween(self, head: ListNode, left: int, right: int) -> ListNode:
        # 创建虚拟头节点,简化边界处理
        dummy = ListNode(0, head)
        
        # 找到left位置的前一个节点
        pre = dummy
        for _ in range(left - 1):
            pre = pre.next
        
        # 反转从left到right的部分
        curr = pre.next
        prev = None
        for _ in range(right - left + 1):
            next_node = curr.next
            curr.next = prev
            prev = curr
            curr = next_node
        
        # 连接反转部分的前后
        pre.next.next = curr  # 反转部分的尾节点连接right后面的节点
        pre.next = prev       # left前面的节点连接反转部分的头节点
        
        return dummy.next

2. LeetCode 25. K 个一组翻转链表

python

复制代码
class Solution:
    def reverseKGroup(self, head: ListNode, k: int) -> ListNode:
        # 辅助函数:反转链表的一部分
        def reverse(head, tail):
            prev = tail.next  # 注意这里连接到下一组的头节点
            curr = head
            while prev != tail:
                next_node = curr.next
                curr.next = prev
                prev = curr
                curr = next_node
            return tail, head  # 返回新的头尾节点
        
        # 创建虚拟头节点
        dummy = ListNode(0, head)
        pre = dummy
        
        while head:
            # 检查是否有k个节点
            tail = pre
            for _ in range(k):
                tail = tail.next
                if not tail:
                    return dummy.next
            
            # 记录下一组的头节点
            next_group = tail.next
            
            # 反转当前组
            head, tail = reverse(head, tail)
            
            # 连接反转后的组
            pre.next = head
            tail.next = next_group
            
            # 移动指针到下一组
            pre = tail
            head = next_group
        
        return dummy.next

3. LeetCode 234. 回文链表

python

复制代码
class Solution:
    def isPalindrome(self, head: ListNode) -> bool:
        # 找到链表中间节点
        slow = fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
        
        # 反转后半部分链表
        prev = None
        curr = slow
        while curr:
            next_node = curr.next
            curr.next = prev
            prev = curr
            curr = next_node
        
        # 比较前半部分和反转后的后半部分
        left, right = head, prev
        while right:  # 只需要比较后半部分长度
            if left.val != right.val:
                return False
            left = left.next
            right = right.next
        
        return True

十一、总结

核心要点

  1. 指针操作 :反转链表的核心是改变节点的 next 指针指向

  2. 三指针法 :使用 prevcurrnext 三个指针可以高效地反转链表

  3. 边界处理:注意处理空链表和单个节点的情况

算法步骤(迭代法)

  1. 初始化 prev = Nonecurr = head

  2. 循环直到 curr 为空:

    • 保存 next_node = curr.next

    • 反转指针:curr.next = prev

    • 移动指针:prev = currcurr = next_node

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

时间复杂度与空间复杂度

  • 迭代法

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

    • 空间复杂度:O(1),只使用常数个指针

  • 递归法

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

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

适用场景

  • 需要反转链表顺序

  • 链表操作的基础算法

  • 许多其他链表问题的基础(如回文链表、分组反转等)

扩展思考

反转链表是链表操作中最基础也最重要的算法之一,掌握它可以帮助解决:

  • 链表的部分反转

  • 链表的回文判断

  • 链表的成对或分组反转

  • 链表与栈、队列的转换

无论是面试还是实际开发,反转链表都是必须掌握的基本功。通过理解指针操作的本质,可以更好地处理各种链表相关问题。

相关推荐
hans汉斯3 小时前
国产生成式人工智能解决物理问题能力研究——以“智谱AI”、“讯飞星火认知大模型”、“天工”、“360智脑”、“文心一言”为例
大数据·人工智能·算法·aigc·文心一言·汉斯出版社·天工
v_for_van3 小时前
力扣刷题记录3(无算法背景,纯C语言)
c语言·算法·leetcode
ValhallaCoder3 小时前
hot100-矩阵
数据结构·python·算法·矩阵
散峰而望3 小时前
【基础算法】穷举的艺术:在可能性森林中寻找答案
开发语言·数据结构·c++·算法·随机森林·github·动态规划
心.c4 小时前
Vue3+Node.js实现文件上传分片上传和断点续传【详细教程】
前端·javascript·vue.js·算法·node.js·哈希算法
散峰而望4 小时前
【基础算法】算法的“预谋”:前缀和如何改变游戏规则
开发语言·数据结构·c++·算法·github·动态规划·推荐算法
We་ct4 小时前
LeetCode 48. 旋转图像:原地旋转最优解法
前端·算法·leetcode·typescript
爱尔兰极光4 小时前
LeetCode--长度最小的子数组
算法·leetcode·职场和发展
仰泳的熊猫4 小时前
题目1432:蓝桥杯2013年第四届真题-剪格子
数据结构·c++·算法·蓝桥杯·深度优先·图论