本篇博客会讲解力扣“206. 反转链表”的解题思路,这是题目链接。
老规矩,先来审题:
示例如下:
提示和进阶:
本题的思路非常多,我讲解一下常见的思路。
思路1
最容易想到的方法,是直接使用3个指针,把指向给倒过来。
首先定义prev和cur,分别代表前一个结点和遍历到的结点。
struct ListNode* reverseList(struct ListNode* head){
struct ListNode* prev = NULL; // 前一个
struct ListNode* cur = head; // 当前
}
接下来开始遍历。一开始先记录下一个结点,因为指向倒过来后就找不到下一个了。
struct ListNode* reverseList(struct ListNode* head){
struct ListNode* prev = NULL; // 前一个
struct ListNode* cur = head; // 当前
while (cur)
{
struct ListNode* next = cur->next; // 下一个
}
}
然后倒转指向。
struct ListNode* reverseList(struct ListNode* head){
struct ListNode* prev = NULL; // 前一个
struct ListNode* cur = head; // 当前
while (cur)
{
struct ListNode* next = cur->next; // 下一个
// 倒转指向
cur->next = prev;
}
}
接着迭代。
struct ListNode* reverseList(struct ListNode* head){
struct ListNode* prev = NULL; // 前一个
struct ListNode* cur = head; // 当前
while (cur)
{
struct ListNode* next = cur->next; // 下一个
// 倒转指向
cur->next = prev;
// 迭代
prev = cur;
cur = next;
}
}
最后返回哪个结点呢?因为cur为空跳出循环,此时前一个结点,即prev,就是新的头结点。
struct ListNode* reverseList(struct ListNode* head){
struct ListNode* prev = NULL; // 前一个
struct ListNode* cur = head; // 当前
while (cur)
{
struct ListNode* next = cur->next; // 下一个
// 倒转指向
cur->next = prev;
// 迭代
prev = cur;
cur = next;
}
// cur为NULL,prev为新的头结点
return prev;
}
这样就过了。
思路2
如果不想要像思路1那么直接,可以考虑遍历+头插。每次遍历到一个结点,就头插到新的链表,也能够实现逆置的效果。
struct ListNode* reverseList(struct ListNode* head){
// 定义新的链表
struct ListNode* newHead = NULL;
}
接着就是遍历+头插的逻辑。
struct ListNode* reverseList(struct ListNode* head){
// 定义新的链表
struct ListNode* newHead = NULL;
// 遍历+头插
struct ListNode* cur = head;
while (cur)
{
// 头插
cur->next = newHead;
newHead = cur;
}
}
接下来要迭代。但是此时cur->next已经改了,所以要先保存next。
struct ListNode* reverseList(struct ListNode* head){
// 定义新的链表
struct ListNode* newHead = NULL;
// 遍历+头插
struct ListNode* cur = head;
while (cur)
{
struct ListNode* next = cur->next;
// 头插
cur->next = newHead;
newHead = cur;
// 迭代
cur = next;
}
return newHead;
}
这样也能过。其实,本解法和第一种解法都用到了3个指针。而且,你仔细对比一下2个代码:
// 三指针强行倒转指向
struct ListNode* reverseList(struct ListNode* head){
struct ListNode* prev = NULL; // 前一个
struct ListNode* cur = head; // 当前
while (cur)
{
struct ListNode* next = cur->next; // 下一个
// 倒转指向
cur->next = prev;
// 迭代
prev = cur;
cur = next;
}
// cur为NULL,prev为新的头结点
return prev;
}
// 遍历+头插
struct ListNode* reverseList(struct ListNode* head){
// 定义新的链表
struct ListNode* newHead = NULL;
// 遍历+头插
struct ListNode* cur = head;
while (cur)
{
struct ListNode* next = cur->next;
// 头插
cur->next = newHead;
newHead = cur;
// 迭代
cur = next;
}
return newHead;
}
把注释都去掉:
struct ListNode* reverseList(struct ListNode* head){
struct ListNode* prev = NULL;
struct ListNode* cur = head;
while (cur)
{
struct ListNode* next = cur->next;
cur->next = prev;
prev = cur;
cur = next;
}
return prev;
}
struct ListNode* reverseList(struct ListNode* head){
struct ListNode* newHead = NULL;
struct ListNode* cur = head;
while (cur)
{
struct ListNode* next = cur->next;
cur->next = newHead;
newHead = cur;
cur = next;
}
return newHead;
}
嘿,是不是一模一样!没错,就是变量名改了下,其他的都一样。所以,同一份代码,其实可以用不同的思路去理解。
思路3
本题还可以使用递归来解决。递归的本质是把大事化小,我们来分析一下:
- 可以递归调用自身,把除了头结点之外的结点逆置了。
- 接着把头结点链接到其余结点的最后面。
下面开始写代码:首先处理除了头结点之外的结点:
struct ListNode* reverseList(struct ListNode* head){
// 处理头结点后面的所有结点
struct ListNode* newHead = reverseList(head->next);
}
接着处理头结点,我们需要把头结点链接到其余结点的后面。注意:虽然其余结点都逆置了,但是头结点指向的结点并没有变化。此时,头结点指向的结点已经成为了其余结点的尾结点,所以直接head->next就能够找到其余结点的最后一个结点。
struct ListNode* reverseList(struct ListNode* head){
// 处理头结点后面的所有结点
struct ListNode* newHead = reverseList(head->next);
// 把头结点链接到其余结点的后面
head->next->next = head;
}
此时原来的头结点就成为了尾结点,但是它的next并不是NULL,所以需要置空。
struct ListNode* reverseList(struct ListNode* head){
// 处理头结点后面的所有结点
struct ListNode* newHead = reverseList(head->next);
// 把头结点链接到其余结点的后面
head->next->next = head;
// 末尾置空
head->next = NULL;
return newHead;
}
当然,以上的操作是有2个以上的结点的场景,如果只有1个结点,甚至没有结点,就直接返回就行了。
struct ListNode* reverseList(struct ListNode* head){
// 如果只有1个结点,或者没有结点,直接返回
if (head == NULL || head->next == NULL)
return head;
// 处理头结点后面的所有结点
struct ListNode* newHead = reverseList(head->next);
// 把头结点链接到其余结点的后面
head->next->next = head;
// 末尾置空
head->next = NULL;
return newHead;
}
这样就通过了。其实我非常喜欢使用递归,虽然效率可能低了点,但是代码给人一种很舒服的感觉。
总结
- 迭代法:可以用三指针强行逆置,或者遍历+头插。2种思路最终写出来的代码是一样的。
- 递归法:还是那么好用,大部分链表的题目都可以考虑递归,这样可以快速写出代码,并且逻辑较为简洁。
感谢大家的阅读!