7 递归
206. 反转链表
给你单链表的头节点head,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
算法设计
可以充分利用原有的存储空间,通过修改指针实现单链表的就地逆置。相当于将所有的箭头反向,头指针指向原链表的尾部。如图所示。
反转链表可以采用迭代法和递归算法两种方法实现,原理是一样的,只是实现方式不同而已。
1. 迭代法
以上面单链表为例,展示单链表就地逆置过程。
(1)首先处理第一个节点,反转后该节点是单链表的尾节点,其next指针指向空。
特别注意:在修改指针之前,一定要用一个辅助指针,记录断点,否则后面这一部分就会遗失到外太空,再也找不到了。
因此可以设置3个指针:p、q、r,p指向前一个节点,q指向当前节点,r指向下一个节点(断点)。
- 初始时,p = nullptr,q = head。
- 如果q非空,修改q的next指向p,即q->next = p;在修改之前,一定要先用r记录断点,r = q->next。
- 然后p、q两个指针后移。
(2)反转下一个节点。如果q非空,重复4个操作:记录断点、反转指针、两个指针后移。
r = q->next; //记录断点
q->next = p; //反转指针
p = q; //后移一位
q = r; //后移一位
(3)当处理完最后一个节点时,q为空,p指向的节点为头节点,返回头指针p即可。
算法实现
// LeetCode206 反转链表
ListNode* reverseList(ListNode* head) { // 迭代法
ListNode *p = nullptr,*q = head, *r; // 前一个节点,当前节点,下一个节点
while (q) {
r = q->next; //记录断点
q->next = p; //反转指针
p = q; //后移一位
q = r; //后移一位
}
return p; //返回反转链表的头指针
}
2. 递归算法
设计递归函数三部曲:
(1)函数名和参数
定义递归函数名为reverseList(),因为要反转链表head,因此需要设计一个参数head。
(2)递归的结束条件
考虑特殊情况,如果链表为空(head = nullptr),或者只有一个节点(head->next = nullptr),则不需要反转链表,直接返回head即可。
(3)自调用
在函数内部调用自身,调用自身时函数名相同。可以宏观考虑,如果第一个节点不动,后面所有节点完成了反转操作,反转第一个节点之后的链表可以调用同名函数reverseList(head->next)实现。
只需要两个操作:① 将该链表尾节点和第一个节点链接;② 修改第一个节点的next为空即可。后面链表反转后的尾节点正是head->next,修改其next指针指向第一个节点,即head->next->next = head。再修改第一个节点的next为空,head->next = nullptr。
最后,返回新链表的头指针r,r是自调用reverseList(head->next)的返回值。
算法实现
// LeetCode206 反转链表
ListNode* reverseList(ListNode* head) { // 递归算法
if (head == nullptr || head->next == nullptr) //递归出口
return head;
ListNode *r = reverseList(head->next); //递归调用
head->next->next = head; //反转指针
head->next = nullptr;
return r; //返回反转链表的头指针
}
算法分析
迭代法和递归算法的时间复杂度均为O(n)。递归的空间复杂度为O(n),迭代法的空间复杂度为O(1)。