【算法】【链表】206.反转链表--通俗讲解

119 阅读5分钟

通俗易懂讲解“反转链表”算法题目

一、题目是啥?一句话说清

给定一个单链表,反转所有节点的指向,使链表倒序,并返回新的头节点。

示例:

  • 输入:1 → 2 → 3 → 4 → 5
  • 输出:5 → 4 → 3 → 2 → 1

二、解题核心

使用三个指针:prev、curr和next。遍历链表,将每个节点的next指针指向前一个节点,直到所有节点都反转。

这就像是一队人转身,每个人转身后面对的方向相反,但需要确保每个人都知道下一个是谁,以免队伍断开。

三、关键在哪里?(3个核心点)

想理解并解决这道题,必须抓住以下三个关键点:

1. 指针的移动和反转

  • 是什么:在遍历链表时,将当前节点的next指针指向前一个节点。
  • 为什么重要:这是反转链表的直接操作,通过改变指针方向实现反转。

2. 保存下一个节点

  • 是什么:在反转当前节点的指针之前,先保存下一个节点的地址。
  • 为什么重要:如果不保存下一个节点,在反转指针后,就无法访问后续节点,导致链表断裂。

3. 头节点的更新

  • 是什么:反转后,原来的尾节点成为新的头节点,需要返回这个新头节点。
  • 为什么重要:因为函数需要返回反转后的链表头,否则无法访问整个链表。

四、看图理解流程(通俗理解版本)

假设链表为:1 → 2 → 3 → 4 → 5

  1. 初始化:prev指向null,curr指向1,next指向2(但暂时不保存)。
  2. 第一步
    • 保存next:next = curr.next (即2)
    • 反转指针:curr.next指向prev (即null),这样1指向null。
    • 移动指针:prev移动到curr (即1),curr移动到next (即2)。
    • 现在链表状态:1 → null,prev=1, curr=2, next=2(但next已经使用过)。
  3. 第二步
    • 保存next:next = curr.next (即3)
    • 反转指针:curr.next指向prev (即1),这样2指向1。
    • 移动指针:prev移动到curr (即2),curr移动到next (即3)。
    • 现在链表状态:2 → 1 → null
  4. 重复步骤直到curr为null:
    • 最后,prev指向5,curr为null。
  5. 返回新头节点:prev,即5。

最终链表:5 → 4 → 3 → 2 → 1 → null

五、C++ 代码实现(附详细注释)

#include <iostream>
using namespace std;

// 链表节点定义
struct ListNode {
    int val;
    ListNode *next;
    ListNode() : val(0), next(nullptr) {}
    ListNode(int x) : val(x), next(nullptr) {}
    ListNode(int x, ListNode *next) : val(x), next(next) {}
};

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* prev = nullptr; // 前一个节点,初始为null
        ListNode* curr = head;    // 当前节点,初始为头节点
        ListNode* next = nullptr; // 下一个节点,用于临时保存

        while (curr != nullptr) {
            next = curr->next; // 保存下一个节点
            curr->next = prev; // 反转指针,指向前一个节点
            prev = curr;       // 移动prev到当前节点
            curr = next;       // 移动curr到下一个节点
        }
        return prev; // 当循环结束时,prev指向新的头节点
    }
};

// 辅助函数:打印链表
void printList(ListNode* head) {
    while (head != nullptr) {
        cout << head->val << " ";
        head = head->next;
    }
    cout << endl;
}

// 测试代码
int main() {
    // 创建示例链表:1->2->3->4->5
    ListNode* head = new ListNode(1);
    head->next = new ListNode(2);
    head->next->next = new ListNode(3);
    head->next->next->next = new ListNode(4);
    head->next->next->next->next = new ListNode(5);
    
    Solution solution;
    ListNode* result = solution.reverseList(head);
    
    printList(result); // 输出:5 4 3 2 1
    
    // 释放内存(简单示例,实际中可能需要更完整的释放)
    while (result != nullptr) {
        ListNode* temp = result;
        result = result->next;
        delete temp;
    }
    
    return 0;
}

六、时间空间复杂度

  • 时间复杂度:O(n),其中n是链表长度。需要遍历整个链表一次。
  • 空间复杂度:O(1),只使用了常数额外空间(三个指针),没有使用递归或其他数据结构。

七、注意事项

  • 空链表处理:如果输入链表为空,直接返回null。
  • 指针操作顺序:必须先保存下一个节点,再反转指针,否则会丢失后续节点。
  • 边界条件:确保在链表只有一个节点时也能正确工作。
  • 内存管理:在C++中,如果原链表不再需要,应注意释放内存,但本题通常不要求释放原链表,因为只是反转指针。

算法通俗讲解推荐阅读
【算法--链表】83.删除排序链表中的重复元素--通俗讲解
【算法--链表】删除排序链表中的重复元素 II--通俗讲解
【算法--链表】86.分割链表--通俗讲解
【算法】92.翻转链表Ⅱ--通俗讲解
【算法--链表】109.有序链表转换二叉搜索树--通俗讲解
【算法--链表】114.二叉树展开为链表--通俗讲解
【算法--链表】116.填充每个节点的下一个右侧节点指针--通俗讲解
【算法--链表】117.填充每个节点的下一个右侧节点指针Ⅱ--通俗讲解
【算法--链表】138.随机链表的复制--通俗讲解
【算法】143.重排链表--通俗讲解
【算法--链表】146.LRU缓存--通俗讲解
【算法--链表】147.对链表进行插入排序--通俗讲解
【算法】【链表】148.排序链表--通俗讲解
【算法】【链表】160.相交链表--通俗讲解
【算法】【链表】203.移除链表元素--通俗讲解


关注公众号,获取更多底层机制/ 算法通俗讲解干货!