文章目录
- 题目描述
- 思路一
- 思路二
题目描述
反转一个单链表。
图片示例:
思路一
其实,反转一个单向链表,我们可以看成是将链表中的每个结点的指向反向(即从后一个结点指向前一个结点)。
我们在考虑情况的时候,还是可以先考虑一般情况,再考虑极端情况。
一、一般情况
要使两个结点之间的指针指向反转,看似用两个变量足矣,直接让后一个结点指向前一个结点。但是仔细思考后发现并没有那么简单,我们如果直接让后一个结点指向前一个结点,那么后一个结点所指向的再后面一个结点的位置就无从知晓了。所以,我们还得定义3个指针变量:n1,n2,n3 。
n1:记录指针指向将要反转的结点反转后要指向的位置。
n2:记录指针指向将要反转的结点。
n3:记录指针指向将要反转的结点的下一个结点。
在反转时,首先让n2指向的结点指向n1指向的位置,然后让n1,n2,n3指针统一后移,准备执行下一对结点之间指向的反转。
如此进行下去,所有的结点指向都将反转。
二、极端情况
极端情况,也就是反转第一个结点指针的指向和反转最后一个结点指针的指向,以及传入的链表为空时的情况。
1.传入的链表为空时
我们可以发现,若传入的链表为空,那么我们根本就不需要对链表进行任何操作,直接返回传入的头指针即可。如果再稍加思考,当传入链表只有一个结点时,我们也不需要对链表进行任何操作,直接返回传入的头指针也没问题。
2.反转第一个结点指针的指向时
因为n2记录的是指针指向将要反转的结点,所以当反转第一个结点指针的指向时,n2指针便指向的是第一个结点。
因为在我们反转过程中就是让n2指向的结点指向n1指向的位置,所以我们只需将n1的初始值赋值为NULL即可。这样,反转后就让第一个结点指针指向NULL了(即反转后的最后一个结点指向空)。
3.反转最后一个结点指针的指向时
当最后一个结点的指针指向被反转时,n2刚好指向最后一个结点,在指针反转完成后,n1,n2,n3指针统一向后移动,位置如下:
我们可以发现逻辑上是没有问题的,而且此时也发现了遍历链表时的终止条件和需要返回的新的头指针,即当n2指针为NULL时停止遍历,并且返回n1指针指向的位置。但是,我们这里需要注意,这时这3个指针统一后移时,n3指针的后移将失败,因为n3后移前指向的是NULL,我们不能执行以下这句代码:
n3 = n3->next;
所以我们后移n3指针前需判断其是否为空。
代码示例:
struct ListNode {
int val;
struct ListNode *next;
};
struct ListNode* reverseList(struct ListNode* head)
{
if (head == NULL || head->next == NULL)//当链表为空或只有一个结点时,无需操作
return head;//直接返回
struct ListNode* n1 = NULL;//记录指针指向将要反转的结点反转后要指向的位置。
struct ListNode* n2 = head;//记录指针指向将要反转的结点。
struct ListNode* n3 = head->next;//记录指针指向将要反转的结点的下一个结点。
while (n2)//n2为NULL时,停止遍历
{
n2->next = n1;//反转结点指向
n1 = n2;//指针后移
n2 = n3;//指针后移
if (n3)//判断n3是否为NULL
n3 = n3->next;//指针后移
}
return n1;//返回n1指针指向的位置
}
思路二
如果你觉得上面这种思路有点绕的话,那么不妨看看下面这种思路:将原链表的结点,从头到尾一个个地拿下来头插到一个新链表中,这个新链表起始时为一个空链表。
这样依次进行下去,最终就能得到一个“反转后的链表”。
代码示例:
struct ListNode {
int val;
struct ListNode *next;
};
struct ListNode* reverseList(struct ListNode* head)
{
struct ListNode* cur = head;//记录当前待头插的结点
struct ListNode* newhead = NULL;//新链表初始时为空
while (cur)//链表中结点头插完毕时停止循环
{
struct ListNode* next = cur->next;//记录下一个待头插的结点
cur->next = newhead;//将结点头插至新链表
newhead = cur;//新链表头指针后移
cur = next;//指向下一个待头插的结点
}
return newhead;//返回反转后的头指针
}