递归逆置的做法真的很巧妙,最好画图自己想一下
我们的目标是:实现一个递归函数,给定一条单链表的首结点,逆置这条单链表,并返回逆置后的首结点

这种比较复杂的递归往往都有步骤可循,我常用的思路就是先写伪代码,按照边界条件,执行操作,返回操作的步骤来思考。
当然,第一步就是假设我们有这么一个递归函数reverse,它的功能就是给定一条单链表的首结点,逆置这条单链表,并返回逆置后的首结点
然后我们就可以写边界条件了,注意下面是类python的伪代码

  1. 边界条件
if node==null or node.next==null:
	return node

很显然,结点为空或者结点只有一个的情况我们就直接返回node就好

  1. 关键操作+处理特殊条件
    这一步是最关键的,我们之前已经假设了拥有reverse函数,那么我们可以这样做
    对于当前的node,我们先逆置node.next
tnode=reverse(node.next)

此时我们会获得以node.next为首结点逆置后的单链表

ok,然后我们画图,分析一下当前的状况

你有没有发现,reverse(node.next)之后,node.next就是逆置后的链表尾结点

Python递归函数倒序打印 python递归逆序_Python递归函数倒序打印


现在我们需要,把node连接上这条逆置的单链表,你先想一下怎么做。

答案就是这一行代码

node.next.next=node

如果到这里没看懂,或者觉得很抽象不好理解,建议自己多画图分析一下,弄懂了再往下看,经过这一步操作之后,链表会变成这个样子

Python递归函数倒序打印 python递归逆序_结点_02


看起来还不错,但是我们发现,node和node.next之间的链接没有断开 于是我们执行这一条语句

node.next=null

但是有的同学可能会问,这一步是必要的吗?因为我返回递归的时候,node.next.next=node不是会更改这个node指针吗?

这条语句的确会更改node指针,但是如果是递归到首结点呢?

Python递归函数倒序打印 python递归逆序_递归_03


很显然我们如果没有node.next=null这条语句的话,那么这条逆置单链表最后一个结点的指针(node)是错的!,它本来应该指向null的!

因此,下面这条语句是必须的

node.next=null

很完美,现在我们得到一条以node为首结点,逆置后的单链表了

  1. 返回条件
    别忘了我们的目标,返回逆置链表的首结点
    我们如何才能得到首结点呢?
    我们应该想到,对一条链表来说,逆置后的首结点就是逆置之前的尾结点。但是如何在这个递归函数里面返回这个尾结点呢?先回顾一下之前的代码
    到这里,我们已经有了如下的代码
def reverse(node):
	if node==null or node.next==null:
		return node
	tnode=reverse(node.next)	
	node.next.next=node
	node.next=null

我们先在草稿纸上面运行一下代码,发现,无论如何,在reverse(node.next)这行代码里面,node会一直传入node.next进去,循环往复,我们会得到什么? 没错,if node==null or node.next==null 这个返回条件里面,我们最终会得到 当前链表的尾结点

Python递归函数倒序打印 python递归逆序_结点_04


到最后一个结点的时候,此时tnode就是当前链表的尾结点了而node就是tnode的前一个结点

那我们应该返回tnode了

return tnode

到此为止,完成的代码如下:

def reverse(node):
	if node==null or node.next==null:
		return node
	tnode=reverse(node.next)	
	node.next.next=node
	node.next=null
	return tnode

接下来我们看看执行代码会发生什么
我们执行

node.next.next=node
	node.next=null

此时会发生什么?

Python递归函数倒序打印 python递归逆序_Python递归函数倒序打印_05


看出来了吗?此时tnode=>node 已经被逆置了,这个时候会返回上一层,继续操作

Python递归函数倒序打印 python递归逆序_递归_06


以此类推,最终我们会得到

Python递归函数倒序打印 python递归逆序_结点_07


然后函数最终会返回tnode,而tnode始终都是最后一个结点,是没有改变的!!不信你可以在纸上模拟一下递归

最终代码如下

def reverse(node):
	if node==null or node.next==null:
		return node
	tnode=reverse(node.next)	
	node.next.next=node
	node.next=null
	return tnode

分析到现在,你可能发现,我们并没有一开始就去涉及递归函数怎么做,而是假设已经存在了这么一个递归函数,然后按照以下顺序分析我们需要进行的操作

  1. 递归边界 一般考虑边界条件,例如传入空结点 or 只有一个结点的情况
  2. 关键操作 如果有多个结点,那么我应该做什么一般(common)操作使得算法能达到我想要的效果
  1. 通过特殊情况来分析一般情况(例如上面的尾结点node.next指针问题)
  2. 通过画图来模拟递归,考虑最后一层递归时应该做的操作
  1. 返回条件,明白自己的返回条件,例如这一题就是取得逆置链表的最后一个结点

leetcode上面有原题,可以自己试着做一下
https://leetcode-cn.com/problems/reverse-linked-list/submissions/

class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        if(head==None or head.next==None):
            return head
        first=self.reverseList(head.next)
        head.next.next=head
        head.next=None
        return first