一、题目介绍
题目链接(力扣上第206题)
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
示例:
二、反转链表五种方法
1.方法一:原地反转链表(本质上是头插法)
代码:
ListNode* reverseList(ListNode* head) {
if(head==NULL)
return NULL;
struct ListNode* dummy=new ListNode(0,head);
struct ListNode* cur=dummy->next->next;
while(cur)
{
head->next=cur->next;
cur->next=dummy->next;
dummy->next=cur;
cur=head->next;
}
head=dummy->next;
delete dummy;
dummy=NULL;
return head;
}
两个指针:
dummy:哨兵位结点,其next指向链表头结点(力扣中的题目一般都是没有哨兵位结点的,需要自己创建)
cur:当前遍历的结点,即需要被操作的结点;
主要算法步骤:
a.head->next=cur->next; cur头插后,连接空缺位置;
b.cur->next=dummy->next; dummy->next=cur; 将cur提前后连接操作;
c.cur=head->next; 遍历结点;
注意细节:
a.要画图来理解原地反转的过程,在这个过程中,cur每次被head->next赋值,即被操作的结点永远是head的下一个结点,当head->next=NULL时,循环结束;
2.方法二:迭代法
代码:
ListNode* reverseList(ListNode* head) {
struct ListNode* pre=NULL;
struct ListNode* cur=head;
while(cur)
{
struct ListNode* next=cur->next;
cur->next=pre;
pre=cur;
cur=next;
}
return pre;
}
三个指针:
cur:指向当前遍历的结点;
pre:指向cur的上一个,用于cur->next=pre操作,初始化为NULL;
next:指向cur的下一个,用来保存cur的下一个,防止cur->next丢失;
迭代步骤:
a.next=cur->enxt; 保存cur的下一个结点,防止丢失;
b.cur->next=pre;
c.pre=cur; cur=next; 向后遍历直到当前遍历结点为NULL,即cur=NULL,迭代结束;
注意细节:
a.pre先置空,因为对链表头结点操作时需要把head->next=NULL;
b.错误写法
class Solution {
public:
ListNode* reverseList(ListNode* head) {
struct ListNode* pre=NULL;
struct ListNode* cur=head;
struct ListNode* next=head->next;
while(cur)
{
cur->next=pre;
pre=cur;
cur=next;
next=next->next;
}
return pre;
}
};
这种写法看似没有什么问题,但是有两点问题:
a.没有考虑到head=NULL的情况;
b.next=netxt->next操作可能会出现报错。当next=NULL时,会出现报错,应为此时循环的条件为while(cur),既使将循环条件改为while(next),也是错的,此时链表的最后一个元素不会被操作;
c.既然要保存cur->next,应该很自然地在while循环里定义next,而且定义在前面;
d.此种方法的正确写法要考虑head以及head->next;
3.方法三:头插法
代码:
ListNode* reverseList(ListNode* head) {
struct ListNode* dummy=new ListNode(0,NULL);
struct ListNode* cur=head;
while(cur)
{
struct ListNode* next=cur->next;
cur->next=dummy->next;
dummy->next=cur;
cur=next;
}
struct ListNode* hhead=dummy->next;
delete dummy;
dummy=NULL;
return hhead;
}
三个指针:
dummy:哨兵位结点,其next指向链表头结点(力扣中的题目一般都是没有哨兵位结点的,需要自己创建)
cur:遍历需要被反转的链表的指针;
next:保存cur的next的指针;
主要步骤:
a.next=cur->next; 先保存cur的next指针
b.cur->next=dummy->next; dummy->next=cur; 将被操作的cur指针头插到新的链表中,并且连接;
c.cur=next; 遍历操作;
错误写法:
ListNode* reverseList(ListNode* head) {
struct ListNode* dummy=new ListNode(0,NULL);
struct ListNode* cur=head;
while(cur)
{
struct ListNode* p=cur;
p->next=dummy->next;
dummy->next=p;
cur=cur->next;
}
struct ListNode* hhead=dummy->next;
delete dummy;
dummy=NULL;
return hhead;
}
注意细节:
a.不要忘记了要保存cur的next。
b.操作cur时,不需要再定义一个p指针了,直接对cur进行操作就可以了,对cur操作完再将cur赋新的值即可;
4.方法四:递归法
代码:
ListNode* reverseList(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;
}
递归思路:
a.先确定函数返回值类型和参数:最后要返回一个头结点,故返回值类型为ListNode* ;从头结点开始递归过程,故传参ListNode*(head);
b.判断递归终止条件:想象一下你想要最后返回的是什么?因为是反转链表,故最后返回的是最后一个结点,即当head->next=NULL时终止,同时要判断head一开始是否是空结点;
c.单层递归逻辑:最后我们要得到的是链表的头结点,故每次都把newhead返回;根据当前递归过程中的head来进行操作,例如最后一个过程中head指的是倒数第二个结点,想象一下应该对倒数第二个结点进行什么操作?即(head->next)->next=head;又因为第一个结点最后要将其next域置空,故还要进行head->next=NULL操作;最后再返回newhead;
5.方法五:运用栈
代码:
ListNode* reverseList(ListNode* head) {
vector<struct ListNode*> list;
while(head)
{
list.push_back(head);
head=head->next;
}
struct ListNode* dummy=new ListNode(0,NULL);
struct ListNode* cur=dummy;
while(!list.empty())
{
struct ListNode* newnode=list.back();
newnode->next=NULL;
cur->next=newnode;
cur=cur->next;
list.pop_back();
}
head=dummy->next;
delete dummy;
return head;
}
主要思路:
创建一个数组,按照反转前的遍历顺序存放链表结点指针,再反方向遍历数组,创建一个新链表,依次将数组中的结点连接起来;