单链表的反转是一个easy级别的题目,这个题目在力扣上的提交次数达到47万次,而且在面试中也频频出现,可谓是大受欢迎,它的兄弟们也跟着风光了。这道题本身是比较简单的,而它的“难兄难弟”就不是那么简单了。今天这篇文章先从简单开始,分析单链表的反转。
题目描述如下。
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
方法一:双指针迭代
迭代法在于,在遍历链表的过程中逐个改变链表节点的指向,重点在于在改变节点指向的同时,不使链表产生断链。我们可以使用两个变量pre
和cur
来表示当前访问到的节点和前一个节点,要使cur.next = pre
,为了能使链表继续向前迭代,我们还需要提前记录当前节点的下一个节点,并把下个节点赋值给cur
变量。
动画演示如下。
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode pre = null, cur = head;
while (cur != null) {
ListNode node = cur.next;
cur.next = pre;
pre = cur;
cur = node;
}
return pre;
}
}
复杂度分析
- 时间复杂度:
O(n)
。 - 空间复杂度:
O(1)
。
方法二:递归
递归是个神奇的存在,那么简单,又那么复杂,有时觉得它很近,实际上它却那么远,有时觉得重新认识了它,可它还是那个它,从未改变过。每一次使用递归,都会对它理解更深一点。
递归法解决链表反转在于假设已经反转好链表的其他节点,当前节点怎么处理。假设链表为
$$ N_1 \rightarrow N2 \rightarrow ... \rightarrow N_k \rightarrow N_{k+1} \rightarrow ... \rightarrow N_n $$
如果$N_{k+1}$到$N_n$部分已经反转完成,那么链表的结构是这个样子的:
$$ N_1 \rightarrow N2 \rightarrow ... \rightarrow N_k \rightarrow N_{k+1} \leftarrow ... \leftarrow N_n $$
当前节点在$N_{k}$的位置,为了使$N_{k+1}$和$N_k$的指向进行改变,我们要做如下操作:
$$ N_k.next.next = N_k $$
代码如下:
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode node = reverseList(head.next);
head.next.next = head;
head.next = null;
return node;
}
}
链表反转使用递归,是让人觉得很惊艳的解法,但也不太好理解,下面借助动画慢放递归调用的过程。
复杂度分析
- 时间复杂度:
O(n)
。 - 空间复杂度:
O(n)
,使用了递归,递归的栈深度为n
。
下集预告,单链表反转的“二哥”:
- 两两交换链表中的节点
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。