题目描述
给你单链表的头节点 head,请你反转链表,并返回反转后的链表。
示例
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
解题思路
反转链表是链表操作中的经典问题,需要改变每个节点的指针方向。核心思想是遍历原链表,将每个节点的 next 指针指向前一个节点。
关键点分析
-
指针方向改变 :每个节点的
next指针需要反向 -
头尾节点变化:原来的头节点变成尾节点,原来的尾节点变成新的头节点
-
边界情况:空链表、单节点链表等情况
代码实现
/**
* 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;
}
递归思路:
-
递归到链表末尾,返回新的头节点
-
在回溯过程中,逐个反转指针方向
方法三:双指针法
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;
}
这实际上就是我们优化后的迭代方法,只是变量命名不同。
总结
反转链表是链表操作的基础题,掌握迭代法和递归法都很重要:
-
迭代法:思路清晰,性能好,推荐掌握
-
递归法:代码简洁,但需要理解递归的调用栈
-
关键操作:保存下一个节点 → 反转指针 → 更新指针位置
这道题的解法体现了链表操作的核心技巧:通过临时变量保存节点地址,安全地改变指针指向。