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. 关键操作:保存下一个节点 → 反转指针 → 更新指针位置

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

相关推荐
天马37982 小时前
Canvas 倾斜矩形绘制波浪效果
开发语言·前端·javascript
探序基因2 小时前
单细胞Seurat数据结构修改分群信息
数据结构
六义义2 小时前
java基础十二
java·数据结构·算法
Tansmjs3 小时前
C++与GPU计算(CUDA)
开发语言·c++·算法
qx093 小时前
esm模块与commonjs模块相互调用的方法
开发语言·前端·javascript
Suchadar3 小时前
if判断语句——Python
开发语言·python
爱编码的小八嘎3 小时前
C语言对话-5.通过任何其他名字
c语言
莫问前路漫漫4 小时前
WinMerge v2.16.41 中文绿色版深度解析:文件对比与合并的全能工具
java·开发语言·python·jdk·ai编程
九皇叔叔4 小时前
【03】SpringBoot3 MybatisPlus BaseMapper 源码分析
java·开发语言·mybatis·mybatis plus
00后程序员张4 小时前
对比 Ipa Guard 与 Swift Shield 在 iOS 应用安全处理中的使用差异
android·开发语言·ios·小程序·uni-app·iphone·swift