一、题目描述
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
二、迭代法
核心思想:
设立三个指针,依次完成反转,在反转之前需要存储下一个结点的地址,否则后面的结点会丢失。
- head 指针:待反转的结点。
- next 指针 :后继节点即 next = head ->next。
- pre 指针:前驱结点,初始化为 NULL,因为头结点的 next 指针最终指向 NULL。
首先看一下原链表:
总共需要添加两个指针,pre 和 next 。初始化 pre 指向 NULL。
然后就是迭代的步骤,总共四步,顺序一步都不能错。
- next 指向 head 的 next,防止原链表丢失
- head 的 next 从原来链表脱离,指向 pre 。
- pre 指向 head
- head 指向 next
一次迭代就完成了,如果再进行一次迭代就变成下边的样子。
可以看到整个过程无非是把旧链表的 head 取下来,添加的新链表头部。接下来就是停止条件了,我们再进行一次循环:
可以发现当 head
或者 next
指向 null
的时候,我们就可以停止了。此时将 pre
返回,便是逆序了的链表了。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
//空链表和一个结点的链表都无需反转
if(head == NULL || head->next == NULL) return head;
ListNode *pre = NULL,*cur = head, *next = head;
while(cur != NULL){
next = cur->next; //保存cur的next,以防止cur改变后丢失
cur->next = pre; //逐个结点反转
pre = cur; //更新指针位置
cur = next;
}
return pre; //返回反转后的头结点
}
};
关于链表的题,记住更改指向的时候要保存之前的节点,不然会丢失节点。
可参考这篇博客,和这篇。其实这种方式就是原地链表的翻转,即没有使用额外的空间。
这种模式的适用场景:
- 翻转链表中的一段(中等)
- 翻转每k个元素为一组的子链表段(中等)
三、递归法
- 首先假设我们实现了将单链表逆序的函数,ListNode* reverseList(ListNode* head) ,传入链表头,返回逆序后的链表头。
- 接着我们确定如何把问题一步一步的化小,我们可以这样想。
把 head
结点拿出来,剩下的部分我们调用函数 reverseList,这样剩下的部分就逆序了,接着我们把 head
结点放到新链表 的尾部就可以了,这就是整个递归的思想了。
- head 结点拿出来,对子链表进行递归:即 ListNode* newList = reverseList(head->next);递归结果如下:
- 此时第一个结点和第二个结点间还没有链接好(也没有断开,只是还未反转),让2结点的 next 指向1,1的 next 指向 null 就ok了。
- 找到递归出口
当然就是如果结点的个数是一个,那么逆序的话还是它本身,直接 return 就够了。怎么判断结点个数是不是一个呢?它 的 next
等于 null
就说明是一个了。但如果传进来的本身就是 null
,那么直接找它的 next
会报错,所以先判断传进来的是 不是 null
,如果是,也是直接返回就可以了。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
//第一个条件是判断递归开始,传入的参数的合法性。第二个是递归的终止条件
if(head == NULL || head->next == NULL)
return head;
ListNode* newhead = reverseList(head->next); //开始进行递归,先反转后面的链表,走到链表的末端
head->next->next = head; //第二个结点的 next 指向第一个结点
head->next = NULL; //第一个结点的 next 指向NULL
return newhead;
}
};
总结:
对于递归算法,最重要的就是明确递归函数的定义。具体来说,我们的 reverseList
函数定义是这样的:
输入一个节点 head
,将「以 head
为起点」的链表反转,并返回反转之后的头结点。
明白了函数的定义,在来看这个问题。比如说我们想反转这个链表:
那么输入 reverseList(head)后 ,会在这里进行递归:
ListNode* newhead = reverseList(head->next);
不要跳进递归(你的脑袋能压几个栈呀?),而是要根据刚才的函数定义,来弄清楚这段代码会产生什么结果:
这个 reverseList(head)执行完成后,整个链表就成了这样:
并且根据函数定义,reverseList 函数会返回反转之后的头结点,我们用变量 newhead(在图中是 last)接收了。
现在再来看下面的代码:
head->next->next = head;
head->next = NULL;
return newhead;
神不神奇,这样整个链表就反转过来了!递归代码就是这么简洁优雅,不过其中有两个地方需要注意:
1、递归函数要有 递归基,也就是这句:
if(head == NULL || head->next == NULL)
return head;
意思是如果链表只有一个节点的时候反转也是它自己,直接返回即可。
2、当链表递归反转之后,新的头结点是 last
,而之前的 head
变成了最后一个节点,别忘了链表的末尾要指向 null:
head->next = NULL;