206.反转链表 Reverse Linked List
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
链接:https://leetcode-cn.com/problems/reverse-linked-list
方法一:利用头插法将链表逆序
对头插法以及尾插法不熟悉的同学可以参考一下我这篇博客:畅谈链表—初级篇3—谈谈链表的头插法与尾插法,之中有我对待头插法与尾插法的说明以及记忆方法,其中的说明形式使用的是我个人对链表的理解所建立起的一些特有的方法,有兴趣的朋友可以继续查看我过去的博客:畅谈链表—初级篇3—谈谈链表的头插法与尾插法
不得不承认,看到逆序一个链表,我脑子里第一时间蹦出来的就是头插法。题目可以转化成这样的说法:给你1, 2, 3, 4, 5这么几个数字,请你将它们以头插法的形式组合起来,这样的题恐怕是人人都会做了吧,让我们来看一看这个方法有哪些地方是需要注意的呢?
- 首先,我推荐刚刚开始刷LeetCode的同学在处理链表问题的时候都加一个头结点,对你梳理逻辑是非常有帮助的。
- 上一次操作结束后,我如何确定下一次操作插入的结点呢?就拿本题来说:我利用了头插法成功将结点插入,下一次cur应该何去何从呢?有的同学肯定会不假思索的说:“下一次操作的结点不就是cur的下一个结点嘛?”然而结果真的是这样嘛?我们来看代码
cur->next = dummyHead->next;
dummyHead->next = cur;
// 这里的cur->next已经变成了dummyHead->next,直接对cur进行下移操作是错误的
cur = cur->next; // NoNoNo!
所以这里我们用到一个链表中非常常用的手段:使用一个临时结点保存下一次操作的结点。这样的操作在之后的题目中真的会非常常见,请一定要理解这个方法。在本题中我们可以利用一个结点temp临时保存下一次操作的结点,即
cur->next,上一次操作结束后,再将cur移动到保存的那个位置。
- 记住最后的返回值是dummyHead->next。dummyHead是辅助我们做题的工具!
附上方法一的完整.cpp代码:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *dummyHead = new ListNode (-1);
ListNode *cur = head;
while ( cur ) {
ListNode *temp = cur->next;
cur->next = dummyHead->next;
dummyHead->next = cur;
cur = temp;
}
return dummyHead->next;
}
};
方法二:正常的逻辑输出:依次修改next指针位置(next指针指向前驱元素)
思路大体上我们利用一个cur指针记录即将改变next的结点,用pre记录cur->next即将指向的结点,即为cur的前一个结点,然后将cur的next指针修改到pre解答一下刚刷LeetCode萌新的疑惑:你怎么保证一个pre指针正好在cur指针之前呢。在一个链表中有头结点的时候,初始化pre的时候令pre = dummyHead,而在链表中没有头结点时可以令pre = nullptr,cur的初始化位置都是head。这样pre和cur共进退的时候就可以保证pre永远在cur指针之前一格。本题我不设立头结点了,因此我一开始的时候初始化是这样的:
ListNode *pre = nullptr, *cur = head;
由于我本人有一套将链表看作一整个街区,单个结点看作房子和路牌的结合体的方法,(不熟悉的同学可以参考一下这篇博客:畅谈链表—初级篇1—谈谈链表结构本身)所以这里我准备用我的方法来和大家讲解这道题。我们以3号房为例:这里我们确定了要改变3号房的路牌,将路牌指向2号房。我们将cur指针看做一伙建筑师团队,pre是他们每一次干活的目标,于是cur这伙建筑师团队持续的工作,不断的转移工作地点,下一次工作的地点是上一次做工的时候就已经确定好了的,这种事情马虎不得,工作地点出错了可是大事!所以他们下次工作的房子已然刷了红色的圆圈(我也不知道为什么是红色圆圈… whatever…同时这里你也可以看作我用自己独创的方法又解释了一遍利用一个temp结点保存下一次cur指针所去向的位置),而新的目标pre则是上一次他们干活的地方,于是代码便自然而然的对应了:
ListNode *temp = cur->next; // 下一次的工作地点上刷上红色圆圈
cur->next = pre; // 建筑师团队cur将路牌指向工作目标pre
cur = temp; // cur团队来到下一次的工作地点,他们看到了红色圆圈便心知肚明
/*
这里同时也告诉了大家设立pre = nullptr的一个注意点,就是pre需要被"引入"
若我这里设立了一个头结点,pre可以直接利用pre = pre->next进行推移
而此时此刻需要借助cur结点将pre引入这个街区内,不然这个被初始化为空的pre指针
将永远只是个"亡魂"
/*
pre = cur; // 下一轮的工作目标就是cur团队上一次待的地方
cur建筑师团队效率拉满!当他们改造好最后一个房子时任务完成,故上述过程均基于循环
while ( cur ) {
// ...
}
哈哈哈,没错,我敲代码的方式是利用想象力。
这里附上完整代码:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *pre = nullptr, *cur = head;
while ( cur ) {
ListNode *temp = cur->next;
cur->next = pre;
pre = cur;
cur = temp;
}
return pre;
}
};
总结一下
因为这道题的难度并不大,所以我认为拿这道题作为入门题非常合适,我们可以了解到三个非常实用的技巧
1. 在刷链表题时插入头结点帮助梳理逻辑
2. 利用一个结点保存下一次操作位置
3. 利用一前一后的pre和cur结点进行一些操作,pre结点初始化为空的方式对应了一种"引入"的推移方式。