通俗易懂讲解“反转链表”算法题目
一、题目是啥?一句话说清
给定一个单链表,反转所有节点的指向,使链表倒序,并返回新的头节点。
示例:
- 输入:1 → 2 → 3 → 4 → 5
- 输出:5 → 4 → 3 → 2 → 1
二、解题核心
使用三个指针:prev、curr和next。遍历链表,将每个节点的next指针指向前一个节点,直到所有节点都反转。
这就像是一队人转身,每个人转身后面对的方向相反,但需要确保每个人都知道下一个是谁,以免队伍断开。
三、关键在哪里?(3个核心点)
想理解并解决这道题,必须抓住以下三个关键点:
1. 指针的移动和反转
- 是什么:在遍历链表时,将当前节点的next指针指向前一个节点。
- 为什么重要:这是反转链表的直接操作,通过改变指针方向实现反转。
2. 保存下一个节点
- 是什么:在反转当前节点的指针之前,先保存下一个节点的地址。
- 为什么重要:如果不保存下一个节点,在反转指针后,就无法访问后续节点,导致链表断裂。
3. 头节点的更新
- 是什么:反转后,原来的尾节点成为新的头节点,需要返回这个新头节点。
- 为什么重要:因为函数需要返回反转后的链表头,否则无法访问整个链表。
四、看图理解流程(通俗理解版本)
假设链表为:1 → 2 → 3 → 4 → 5
- 初始化:prev指向null,curr指向1,next指向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已经使用过)。
- 第二步:
- 保存next:next = curr.next (即3)
- 反转指针:curr.next指向prev (即1),这样2指向1。
- 移动指针:prev移动到curr (即2),curr移动到next (即3)。
- 现在链表状态:2 → 1 → null
- 重复步骤直到curr为null:
- 最后,prev指向5,curr为null。
- 返回新头节点: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.移除链表元素--通俗讲解
关注公众号,获取更多底层机制/ 算法通俗讲解干货!