题目来源于《剑指Offer 第二版》
将一个单链表进行反转,迭代实现比较容易理解,但是递归算法有点难以理解。
以下为递归实现的Java代码。
public static ListNode reverseListNodeRecur(ListNode pNode) {
if (pNode == null || pNode.next == null) return pNode;
ListNode newHead = reverseListNodeRecur(pNode.next);
//链表反转
pNode.next.next = pNode;
pNode.next = null;
return newHead;
}
个人认为递归最好理解的例子是阶乘。
定义f(n)=n * f(n-1)
那么很容易理解f(5) = 5 * f(4) , f(4) = 4 * f(3);
所以把f( )看做递归函数,那么这个递归函数其实本质上就完成了一件事,就是实现了两个数相乘!不是吗?因为f( )的返回值就是一个数。
这里的递归结束条件自然就是n > 0.
照着阶乘的递归去理解链表反转的递归。
首先这种情况下的递归结束条件是当前的节点或者当前节点的下一个节点为空。
第一件事,递归的本质到底在干嘛?
再回过头来看整个递归方法本质上就做了两个事:
- 完成两个节点的反转
- 返回一个节点
假设有两个节点的链表为 Node1 -> Node2 -> null;
那么Node1.next.next = Node1 和 Node1.next = null就是完成第一件事 —— 完成两个节点的反转!
想一想,某个节点的下一个节点指向了自己不就是反向吗?Node1.next = null不就是删除了原本的指向关系吗?(也可以说为了防止形成环)
第二件事,递归到底返回了什么?
假设现在的链表为1->2->3->4->5->(null),那么链表反转之后的结果就是1<-2<-3<-4<-5<-(null)。
根据阶乘不难发现,递归的处理和栈是一样的,先进去的最后处理,最后进去的先处理!试想一下f(5)难道不是先算f(2) = 2 x 1吗?
所以本质上第一步先处理的是5->null的反转,返回5;
接着处理(4->(5<-null)),变成(4<-5<-null),那么返回值是什么?
处理(4->(5<-null))的返回值实际上要看处理(5->null)的返回值!
明白了吧!这个递归函数的返回值最后都要看第一次处理时候的返回值,也就是5!
所以递归反转链表中
ListNode newHead = reverseListNodeRecur(pNode.next);
return newHead
返回的就是原链表中的尾节点!因为反转操作中,尾节点是第一个被处理的!它和null节点进行反转!
总结:
- 要明白一个递归函数到底在干嘛?本质上是做什么的?千万不要陷入了递归的过程!因为递归就是不断的{ }套{ },递归次数多了,头会炸的!
- 递归的返回值到底是什么?思考清楚了递归函数的本质,这个问题也就清楚了!
- 想不通递归的,深刻理解阶乘这个东西!我觉得这个是递归最容易理解,也是最直观和我们最熟悉的一个例子!