反转链表
本节对应代码随想录中:代码随想录,讲解视频:帮你拿下反转链表 | LeetCode:206.反转链表 | 双指针法 | 递归法_哔哩哔哩_bilibili
习题
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
示例:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
双指针
例如,上面的示例,我们想反转 1 2 3 4 5。用一个指针 cur 用来遍历单链表,再用另一个指针 pre 指向 cur 的前面一个节点。例如 cur=2时,原本是1->2,现在要变成2->1。直接让 cur->next=pre 就可以实现,但这样的话,后面的3就没法遍历到了。所以要用一个临时指针 temp 先指向 cur->next,再让 cur->next=pre。
到这里,就实现了1和2的反转,如果想继续遍历下去,还需让 pre 和 cur 都向后移一位。现在 pre、cur 和 temp 的位置为:pre cur temp,要让 pre 和 cur 向后移一位,只要让 pre=cur,cur=temp 即可。注意这两个顺序不能颠倒,否则 cur 先等于 temp,那就相当于丢失了原来的 cur。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* cur = head;
ListNode* pre = nullptr;
ListNode* tmp;
while (cur != nullptr) {
tmp = cur->next;
cur->next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
};
- 时间复杂度:O(n)。其中n为链表的长度。因为算法需要遍历整个链表,时间复杂度与链表长度成正比。
- 空间复杂度:O(1)。因为算法只使用了常数级别的额外空间,即三个指针变量。无论输入的链表有多长,算法所需的额外空间都是固定的,与链表长度无关。
递归
首先给出代码随想录上类似双指针的递归。新定义了一个函数,将初始化的步骤更改为传参给新定义的函数来实现,而将两个指针后移的步骤更改为调用函数递归来实现。同时,和双指针的结束条件一样,cur == NULL 时递归函数结束。
class Solution {
public:
ListNode* reverse(ListNode* pre,ListNode* cur){
if(cur == NULL) return pre;
ListNode* temp = cur->next;
cur->next = pre;
// 可以和双指针法的代码进行对比,如下递归的写法,其实就是做了这两步
// pre = cur;
// cur = temp;
return reverse(cur,temp);
}
ListNode* reverseList(ListNode* head) {
// 和双指针法初始化是一样的逻辑
// ListNode* cur = head;
// ListNode* pre = NULL;
return reverse(NULL, head);
}
};
- 时间复杂度:O(n)。其中n为链表的长度。因为算法需要遍历整个链表,时间复杂度与链表长度成正比。
- 空间复杂度:O(n)。因为算法使用了递归操作,递归深度最多为n层,每层递归需要保存当前节点的指针以及前一个节点的指针,因此空间复杂度为O(n)。
上面的递归由于和双指针的写法类似,还较为容易理解,而LeetCode 官方给出了更简洁的递归。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (!head || !head->next) {
return head;
}
ListNode* newHead = reverseList(head->next);
head->next->next = head;
head->next = nullptr;
return newHead;
}
};
评论区有人给出了详细的注释(java 版本)。对于 head->next->next = head;
,如 1 2 3,head=2,head->next=3,3->next=2,相当于2<->3。但是反转后2的 next 不应该再指向3了,因此 head->next = nullptr;
将2的 next 置为 nullptr。
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
/*
直到当前节点的下一个节点为空时返回当前节点
由于5没有下一个节点了,所以此处返回节点5
*/
return head;
}
//递归传入下一个节点,目的是为了到达最后一个节点
ListNode newHead = reverseList(head.next);
/*
第一轮出栈,head为5,head.next为空,返回5
第二轮出栈,head为4,head.next为5,执行head.next.next=head也就是5.next=4,
把当前节点的子节点的子节点指向当前节点
此时链表为1->2->3->4<->5,由于4与5互相指向,所以此处要断开4.next=null
此时链表为1->2->3->4<-5
返回节点5
第三轮出栈,head为3,head.next为4,执行head.next.next=head也就是4.next=3,
此时链表为1->2->3<->4<-5,由于3与4互相指向,所以此处要断开3.next=null
此时链表为1->2->3<-4<-5
返回节点5
第四轮出栈,head为2,head.next为3,执行head.next.next=head也就是3.next=2,
此时链表为1->2<->3<-4<-5,由于2与3互相指向,所以此处要断开2.next=null
此时链表为1->2<-3<-4<-5
返回节点5
第五轮出栈,head为1,head.next为2,执行head.next.next=head也就是2.next=1,
此时链表为1<->2<-3<-4<-5,由于1与2互相指向,所以此处要断开1.next=null
此时链表为1<-2<-3<-4<-5
返回节点5
出栈完成,最终头节点5->4->3->2->1
*/
head.next.next = head;
head.next = null;
return newHead;
}
如果还是觉得有点抽象不太好理解,可以看下这个视频讲解:链表反转(递归法)_哔哩哔哩_bilibili