206. 反转链表 - 题解与详细分析

题目描述

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

示例

示例 1:

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

示例 2:

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

解题思路

反转链表是链表操作中的经典问题,需要改变每个节点的指针方向。核心思想是遍历原链表,将每个节点的 next 指针指向前一个节点。

关键点分析

  1. 指针方向改变 :每个节点的 next 指针需要反向

  2. 头尾节点变化:原来的头节点变成尾节点,原来的尾节点变成新的头节点

  3. 边界情况:空链表、单节点链表等情况

代码实现

复制代码
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* newhead = NULL;  // 新链表的头节点
    struct ListNode* cur = head;      // 当前遍历的节点
    
    while(cur)
    {
        if(newhead == NULL)
        {
            // 处理第一个节点:将其作为新链表的头节点
            struct ListNode* temp = cur->next;  // 保存下一个节点
            newhead = cur;                      // 当前节点成为新头节点
            newhead->next = NULL;               // 新头节点的next置为NULL
            cur = temp;                         // 移动到下一个节点
        }
        else
        {
            // 处理后续节点:头插法
            struct ListNode* temp = cur->next;  // 保存下一个节点
            cur->next = newhead;                // 当前节点指向新头节点
            newhead = cur;                      // 更新新头节点为当前节点
            cur = temp;                         // 移动到下一个节点
        }
    }
    return newhead;
}

代码详解

核心思想:头插法

使用头插法构建反转后的链表:

  • 遍历原链表,逐个取出节点

  • 将取出的节点插入到新链表的头部

  • 最终得到的就是反转后的链表

变量说明

  • newhead :新链表的头节点,初始为 NULL

  • cur:当前正在处理的节点,从原链表的头节点开始遍历

  • temp:临时变量,用于保存下一个节点的地址

执行流程

第一步:处理第一个节点

复制代码
if(newhead == NULL)
{
    struct ListNode* temp = cur->next;
    newhead = cur;
    newhead->next = NULL;
    cur = temp;
}
  • 保存下一个节点:temp = cur->next

  • 当前节点成为新头节点:newhead = cur

  • 新头节点的 next 置为 NULL(因为是反转后的尾节点)

  • 移动到下一个节点:cur = temp

第二步:处理后续节点

复制代码
else
{
    struct ListNode* temp = cur->next;
    cur->next = newhead;
    newhead = cur;
    cur = temp;
}
  • 保存下一个节点:temp = cur->next

  • 当前节点指向新头节点:cur->next = newhead(关键的反转操作)

  • 更新新头节点:newhead = cur

  • 移动到下一个节点:cur = temp

可视化过程

以 [1,2,3,4,5] 为例:

复制代码
初始状态:
head -> 1 -> 2 -> 3 -> 4 -> 5 -> NULL
newhead = NULL

第1次循环:
取出节点1:newhead -> 1 -> NULL
剩余链表:2 -> 3 -> 4 -> 5 -> NULL

第2次循环:
取出节点2:newhead -> 2 -> 1 -> NULL
剩余链表:3 -> 4 -> 5 -> NULL

第3次循环:
取出节点3:newhead -> 3 -> 2 -> 1 -> NULL
剩余链表:4 -> 5 -> NULL

第4次循环:
取出节点4:newhead -> 4 -> 3 -> 2 -> 1 -> NULL
剩余链表:5 -> NULL

第5次循环:
取出节点5:newhead -> 5 -> 4 -> 3 -> 2 -> 1 -> NULL
剩余链表:NULL

优化后的代码

实际上,可以简化代码,去掉 if-else 判断:

复制代码
struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* newhead = NULL;
    struct ListNode* cur = head;
    
    while(cur) {
        struct ListNode* temp = cur->next;  // 保存下一个节点
        cur->next = newhead;                // 反转指针
        newhead = cur;                      // 更新新头节点
        cur = temp;                         // 移动到下一个节点
    }
    
    return newhead;
}

这个简化版本逻辑更清晰,因为 newhead 初始为 NULL,第一次循环时 cur->next = newhead 就相当于 cur->next = NULL,与原来 if 分支的效果相同。

复杂度分析

  • 时间复杂度:O(n),需要遍历整个链表一次

  • 空间复杂度:O(1),只使用了常数级别的额外空间

其他解法

方法二:递归法

复制代码
struct ListNode* reverseList(struct ListNode* head) {
    // 递归终止条件
    if (head == NULL || head->next == NULL) {
        return head;
    }
    
    // 递归反转后续链表
    struct ListNode* newHead = reverseList(head->next);
    
    // 反转当前节点
    head->next->next = head;
    head->next = NULL;
    
    return newHead;
}

递归思路:

  1. 递归到链表末尾,返回新的头节点

  2. 在回溯过程中,逐个反转指针方向

方法三:双指针法

复制代码
struct ListNode* reverseList(struct ListNode* head) {
    struct ListNode* prev = NULL;
    struct ListNode* cur = head;
    
    while (cur) {
        struct ListNode* temp = cur->next;
        cur->next = prev;
        prev = cur;
        cur = temp;
    }
    
    return prev;
}

这实际上就是我们优化后的迭代方法,只是变量命名不同。

总结

反转链表是链表操作的基础题,掌握迭代法和递归法都很重要:

  1. 迭代法:思路清晰,性能好,推荐掌握

  2. 递归法:代码简洁,但需要理解递归的调用栈

  3. 关键操作:保存下一个节点 → 反转指针 → 更新指针位置

这道题的解法体现了链表操作的核心技巧:通过临时变量保存节点地址,安全地改变指针指向。

相关推荐
wuminyu5 小时前
JVM规范学习总结:解释器与JIT编译器如何处理JVM字节码
java·linux·c语言·jvm·c++
小徐不徐说5 小时前
序列化与反序列化:把 C++ 对象可靠地保存下来
开发语言·数据库·c++·php
ragnwang6 小时前
Notes: Python pybind11 import C++ module
开发语言·c++·python
Wang's Blog6 小时前
Java 项目实战: 交友项目 - 搜索附近的功能概述和分析
java·开发语言·交友
骑着蜗牛撵大象3276 小时前
大模型 Agent 的沙箱护栏:gVisor 隔离、出口白名单与审计闭环
java·开发语言·jvm·安全·agent沙箱·gvisor·microvm
Dxy12393102167 小时前
JS DES 加密详解
开发语言·javascript·arcgis
充值内卷7 小时前
我给桌面加了个鱼缸:C++ 写后端,WebView2 里养一座 Three.js 小岛
开发语言·javascript·c++
云和数据.ChenGuang7 小时前
深入解析 Python 的 dir():从用法到 CPython 源码实现
开发语言·人工智能·python·langchain
叼烟扛炮8 小时前
[Git 实战] 学习笔记:从生成 SSH 密钥到解决 TortoiseGit 推送报错全指南
linux·运维·笔记·git·学习
nice_lcj5208 小时前
深入理解 Java 反射:从 Class 对象到方法调用(含性能实测 + 迷你 IOC 实战 + 面试考点)
java·开发语言·后端·面试