递归反转链表,怎么就不会了呢

源码共读

共 4820字,需浏览 10分钟

 ·

2021-02-20 20:45

Python实战社群

Java实战社群

长按识别下方二维码,按需求添加

扫码关注添加客服

进Python社群▲

扫码关注添加客服

进Java社群


作者丨编程狂想曲
来源丨编程狂想曲

今天分享的内容就是关于如何用递归思想来反转链表的,分享的题目是:
  • LeetCode #206 反转链表
  • LeetCode #92 反转链表||
友情提示:在用递归思想解题时,明确递推公式的含义后,不要试图想明白每一步是如何递归的,这很容易把自己绕晕哈。

01

LeetCode #206 反转链表

题目描述:
反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

思路分析:
示例给出的链表,如下图所示:


递归解题首先要做的是明确递推公式的含义,在这里对于head指向结点1来说,它只需要知道它之后的所有节点反转之后的结果就可以了,也就是说递推公式reverseList的含义是:把拿到的链表进行反转,然后返回新的头结点。

结点1之后的结点,经过递归公式reverseList处理之后的结果如下图:

到这里,就可以写出如下的代码了。
public ListNode reverseList(ListNode head) {    // 调用递推公式反转当前结点之后的所有节点    // 返回的结果是反转后的链表的头结点    ListNode newHead = reverseList(head.next);}
接着要做的就是反转head指向的结点1,也就是将head指向的结点作为其下一个结点的下一个结点,即head.next.next=head。

最后,将head指向的结点的下一个结点置为null,就完成了整个链表的反转。

将反转head指向的结点的代码完善之后,就可以得到如下的代码:

public ListNode reverseList(ListNode head) {    // 调用递推公式反转当前结点之后的所有节点    // 返回的结果是反转后的链表的头结点    ListNode newHead = reverseList(head.next);    head.next.next = head;    head.next = null;    return newHead;}

递归调用这一部分完成之后,还有重要的一步就是递归终止条件,递归反转链表什么时候停止呢?在head指向的结点为null或head指向的结点的下一个结点为null时停止,因为在这两种情况下,反转后的结果就是它自己。到这里,就可以写出完整的代码了:

public ListNode reverseList(ListNode head) {    if (head == null || head.next == null) {        return head;    }
// 调用递推公式反转当前结点之后的所有节点 // 返回的结果是反转后的链表的头结点 ListNode newHead = reverseList(head.next); head.next.next = head; head.next = null; return newHead;}


02

LeetCode #92 反转链表||

题目描述:
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明:
1 ≤ m ≤ n ≤ 链表长度。
示例:

输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出:
1->4->3->2->5->NULL

思路分析:

为了方便说明一个问题,这里以链表1->2->3->4->5->6->NULL, m = 3, n = 5为例进行分析如何用递归思想求解该题目。

在这里head指向的结点1,不用关注其后的所有结点时如何将m=3与n=5之间的部分反转的,它只需要知道反转后的结果就可以。也就是说,在这里递推公式reverseBetween的含义是:将拿到的链表反转,然后返回反转后的链表的头结点。

这样就可以初步写出如下的代码:
public ListNode reverseBetween(ListNode head, int m, int n) {    ListNode between = reverseBetween(head.next, m-1,n-1);}
接着要做的就是,将递推公式reverseBetween返回的结果,挂在head之后,即head.next=between。

这时,代码可以进一步完善,如下所示:
public ListNode reverseBetween(ListNode head, int m, int n) {    ListNode between = reverseBetween(head.next, m-1,n-1);    head.next = between;    return head;}

递推公式的部分已经完成了,接着要明确的就是递归终止条件。在这里原问题是反转链表:1->2->3->4->5->6->NULL, m = 3, n = 5之间的部分。

更小的子问题是反转链表:2->3->4->5->6->NULL, m = 2, n = 4之间的部分。

在进一步是反转链表:3->4->5->6->NULL, m = 1, n = 3之间的部分。但是,这时这个子问题和上一个子问题还是可以用相同思路求解的同一个子问题吗?

不是。
原因在于对于子问题:2->3->4->5->6->NULL, m = 2, n = 4来说,它只需要将其之后的所有结点反转之后的结点挂在自己之后就可以了。但是,对于问题3->4->5->6->NULL, m = 1, n = 3来说,结点3本身也是需要反转的。
对于如何用递归思想反转3->4->5->6->NULL, m = 1, n = 3一会儿再说。到这里,递归终止条件就明了了,即m=1时停止,代码如下:
public ListNode reverseBetween(ListNode head, int m, int n) {    if (m == 1) {        return 待补充;    }
ListNode between = reverseBetween(head.next, m-1,n-1); head.next = between; return head;}
说明:以下部分是借鉴的labuladong大佬新作《labuladong的算法小抄》一书中的相关内容。
接着我们看下如何用递归思想反转3->4->5->6->NULL, m = 1, n = 3,即如何反转链表的前n个结点。

这里,递推公式reverseTopN的含义是反转链表的前n个结点并返回被反转的链表的头结点。因此,其需要的参数有两个,一是待反转的链表的头结点,二是反转前几个结点,即n。

对于链表4->5->6->NULL, n = 2来说,经过递推公式reverseTopN处理之后,结果如下图所示:

至此,对于反转链表的前n个结点,可以初步写出如下的代码:

private ListNode reverseTopN(ListNode head, int n) {    ListNode newHead = reverseTopN(head.next, n-1);}

经过前面分析,我们知道head指向的结点3也是需要反转的,即head.next.next=head。但是,我们发现在完成这一步操作后,结点6没法在和其它结点产生联系了。

这个问题怎么解决呢?我们可以用topNSuccessor这个变量,指向第n个结点之后的结点。

这时,在反转结点3,即head.next.next=head之后,我们可以将topNSuccessor指向的结点挂在head指向的结点之后,即head.next=topNSuccessor。

head.next.next=head

head.next=topNSuccessor

这时,反转链表前N个结点的代码进一步完善如下:

private ListNode reverseTopN(ListNode head, int n) {    ListNode newHead = reverseTopN(head.next, n-1);    head.next.next = head;    head.next = topNSuccessor;    return newHead;}
这时就有一个问题,topNSuccessor怎么确定的呢?
我们先看下反转链表前n个结点的递归终止条件是什么。当只需要反转链表的第一个结点时,返回原链表就可以了,即反转链表前n个结点的递归终止条件是n==1。
如下图,当n==1时,我们就可以确定topNSuccessor是head指向的结点的下一个结点。

到这里反转链表的前n个结点的代码就可以完善了,具体代码如下:
ListNode topNSuccessor = null;
private ListNode reverseTopN(ListNode head, int n) { if (n == 1) { topNSuccessor = head.next; return head; }
ListNode newHead = reverseTopN(head.next, n-1); head.next.next = head; head.next = topNSuccessor; return newHead;}
最后,反转从位置 m 到 n 的链表的递归实现完整代码如下:
public ListNode reverseBetween(ListNode head, int m, int n) {    if (m == 1) {        return reverseTopN(head, n);    }
ListNode between = reverseBetween(head.next, m-1,n-1); head.next = between; return head;}
ListNode topNSuccessor = null;
private ListNode reverseTopN(ListNode head, int n) { if (n == 1) { topNSuccessor = head.next; return head; }
ListNode newHead = reverseTopN(head.next, n-1); head.next.next = head; head.next = topNSuccessor; return newHead;}

参考资料:《labuladong的算法小抄》

题图:rahu / Pixabay

程序员专栏
 扫码关注填加客服 
长按识别下方二维码进群

近期精彩内容推荐:  

 几句话,离职了

 中国男性的私密数据大赏,女生勿入!

 为什么很多人用“ji32k7au4a83”作密码?

 一个月薪 12000 的北京程序员的真实生活 !




在看点这里好文分享给更多人↓↓

浏览 10
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报