题目链接:206. 反转链表
小剧场
第一反应:单链表?反转?就这?
随即:开玩笑,5分钟内不把这题秒了,我当场,就把这个电脑屏幕吃掉!
当事人:我真菜,真的。。。只会暴力求解。。。
解法一 迭代
假设存在链表1-> 2-> 3-> 4-> 5 -> null
,反转链表应该得到null <-1 <-2 <-3 <-4 <-5
为了使用迭代法求解,需要将这个过程分解成相同的步骤:
初始化:null 1-> 2-> 3-> 4-> 5 -> null
第1次:null <-1 2-> 3-> 4-> 5 -> null
第2次:null <-1 <-2 3-> 4-> 5 -> null
。。。
第5次:null <-1 <-2 <-3 <-4 <-5 null
不难发现,每一次步骤的操作是类似的。
由于链表被分成了两部分,需要两个指针进行分别指向。因此,在初始化时,定义prev-> null
,定义cur-> head
。
此后,每一步需要做如下步骤:
- 由于
cur
后续需要修改指向,为了防止失联,首先需要定义一个临时节点tempNode
用于存放cur
的当前指向 cur
修改指向,指向prev
cur
和prev
都往后移动
如此循环,直至cur
指向null
最后,返回新的头节点prev
,大功告成!
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode cur = head;
while (cur != null){
ListNode tempNode = cur.next;
cur.next = prev;
prev = cur;
cur = tempNode;
}
return prev;
}
复杂度分析
- 时间复杂度:O(n)
- 空间复杂度:O(1)
解法二 递归
由于链表具备天然的递归性质(头节点->子序列),不难想到利用递归将子序列进行反转,再与头节点重新拼接,从而达到整个链表的反转。
问题在于如何设计重新拼接的过程。
更具体的来说,子序列进行反转后,返回的是反转后子序列的头节点,而重新拼接的过程需要子序列的尾节点指向链表的头节点。因此,问题转移到了如何检索反转后子序列的尾节点。
对于这个问题,需要分析递归过程中链表各节点的指向。
假设存在链表1-> 2-> 3-> null
,递归方法ListNode reverseList(ListNode head)
的宏观语义:将head
指向的链表进行反转,并返回反转后链表的头节点。
第1步:1-> reverseList(2-> 3-> null)
第2步:1-> reverseList(2-> reverseList(3-> null))
第2步(递归返回):1-> reverseList(2-> [null <-3])
此时2指向的仍然是3,指向还没改变
第2步(重新拼接):1-> reverseList(null <-2 <-3)
注意,拼接结束后需要将head指向null,否则就会变成这样👉1-> reverseList(2-> <-3)
3指向2,2指向3,形成循环。
第1步(递归返回):1-> null <-2 <-3
第1步(重新拼接):null <-1 <-2 <-3
结束
不难发现,在递归返回时,head的指向尚未发生改变,且head实际上就是指向反转后子序列的尾节点。这样一来,我们就方便的得到了尾节点,拼接的问题也就迎刃而解了嗷。[]( ̄▽ ̄)*
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null)
return head;
ListNode cur = reverseList(head.next);
head.next.next = head;
head.next = null;
return cur;
}
复杂度分析
- 时间复杂度:O(n)
- 空间复杂度:O(n) 递归深度可能达到n层