递归反转链表:拆解复杂问题
反转单链表的迭代实现不是一个困难的问题,但是递归实现就有些难度了。反转单链表中的一部分,是否可以通过递归实现?
本文有简单到复杂,一步一步的解决反转单链表中的一部分这个问题。
单链表节点的结构
1 | class ListNode: |
什么叫做反转链表的一部分?意思是:给定一个区间,将链表中区间内的元素反转,其他部分不变。
反转从位置m到n的链表。请使用一趟扫描完成反转
说明
$1<= m <= n <= 链表长度$
示例
输入:1 -> 2 -> 3 -> 4 -> 5 -> NULL, m = 2, n = 4 。 输出: 1 -> 4 -> 3 -> 2 -> 5 -> NULL
注意这里的索引是从1开始的。迭代的思路大概是:先用一个for
循环找到第m
个位置,然后再用一个for
循环将m
和n
之间的元素反转。但是递归解法不用任何for循环,
迭代实现思路看起来虽然简单,但是细节问题很多,不太容易写对。相反,递归实现很简洁优美,下面由浅入深,先从反转整个单链表开始讲起。
一、递归反转单链表
首先先介绍一下算法实现
1 | class Solution: |
单纯从代码上来看,有一种不知所云的感觉,甚至是完全不能理解为什么能够反转链表。下面详细解释下这一段代码
对于递归算法,最重要的就是明确递归函数的定义。具体来说,这里定义的reverse
函数具体含义如下:
输入一个节点head
,将(以head
为起点)的链表反转,并返回反转之后的头节点。
了解函数的定义后,再来看这个问题,例如想反转这个链表:
1 | graph LR |
那么输入reverse(head)
后,会在这里进行递归
1 | last = reverse(head.next) |
实际理解过程中,不要试图跳进递归中,要根据函数定义,来弄清楚这段代码会产生什么结果:
按照这个定义,这个reverse(head。next)
执行完成后,整个链表应该会变成如下:
并且根据函数的定义,reverse
函数会返回反转之后的头节点,这里使用last
接收。
继续往下走:
1 | head.next.next = head |
继续进行接下来的操作
1 | head.next = None |
通过如上操作,整个链表就实现了反转。递归代码是非常简洁和优雅的。不过其中有两个地方需要特别注意:
递归函数要有边界值
1
2if !head.next:
return head解释:如果链表只有一个节点,那么反转的时候,只有它自己。直接返回即可。
当链表递归反转之后,新的头节点是:
last
,而之前的head
变成了最后一个节点,最后要将链表的末尾指向None1
head.next = None
理解单链表的递归反转之后,后面可以继续深入进行其他操作。