递归反转链表:如何拆解复杂问题


1 单链表

对于单链表讲,单链表中的结点不一定存储在相邻位置,一个结点储存两个信息,一是该节点的值,二是指向下一个结点的位置信息。
在这里插入图片描述

// 单链表节点的结构
public class ListNode {
    
    
    int val;
    ListNode next;
    ListNode(int x) {
    
     val = x; }
}

本文的主要目的是以单链表出发,递归实现反转单链表——>反转链表前N个结点——>反转链表的一部分

2 递归实现反转单链表(LetCode 206)

这个算法可能很多读者都听说过,这里详细介绍一下,先直接看实现代码:

ListNode reverse(ListNode head) {
    
    
   if( head == null || head.next == null) return head;
    ListNode last = reverse(head.next);
    head.next.next = head;
    head.next = null;
    return last;
}

下面来详细解释一下这段代码。

首先(大体出现链表反转的结果):
对于递归算法,最重要的就是明确递归函数的定义。具体来说,我们的reverse函数定义是这样的:

输入一个节点head,将「以head为起点」的链表反转,并返回反转之后的头结点。

明白了函数的定义,再来看这个问题。比如说我们想反转这个链表:
在这里插入图片描述
那么输入reverse(head)后,会在这里进行递归:

ListNode last = reverse(head.next);

不要跳进递归(你的脑袋能压几个栈呀?),而是要根据刚才的函数定义,来弄清楚这段代码会产生什么结果:
在这里插入图片描述

按照定义,这个reverse(head.next)执行完成后,整个链表应该变成了这样:

在这里插入图片描述

并且根据函数定义,reverse函数会返回反转之后的头结点,我们用变量last接收了。

然后(指针进行修正上下结点):
现在再来看下面的代码:

head.next.next = head; //修正上结点——将head的地址赋予元素2的地址(即链表2后面指向1)

在这里插入图片描述
接下来进行的操作:

head.next = null; // 修正下结点
return last;

最后(递归结束边界base case):

	if (head.next == null) return head;

**小小总结下
大体可以分为以下三步骤:

  1. 对于递归问题,我们一定要知道递归的定义(递归后长怎么样的?)
  2. 对于递归后的链表修改上下结点的(位置)指向
  3. 明白递归的终止条件(basecase)——结点指向下一个位置为null

理解了这两点后,我们就可以进一步深入了,接下来的问题其实都是在这个算法上的扩展。

3 反转链表前N个结点

首先看下大概的过程:
在这里插入图片描述
代码实现如下:

ListNode tem = null; // 后驱结点(用来修改——反转后中 返回结点的指向!)
ListNode reserveN(ListNode head, int n){
    
    // 定义递归函数:反转链表的前n个结点
	if(n == 1){
    
    
		tem = head.next;
		return head;
	}
	// 以 head.next 为起点,需要反转前 n - 1 个节点
	ListNode last = reseveN(head.next, n-1);
	head.next.next = head;
	
	// 让反转之后的 head 节点和后面的节点连起来
	head.next = tem;
	return last;
	
}

小小总结下反转链表前N个结点
对比第一个反转链表,大体的思路差不多,都是

  1. 定义递归方程(明白递归反战后是长怎么样的?)
  2. 对于递归后的链表修改上下结点的(位置)指向
  3. 明白递归的终止条件(basecase)——结点指向下一个位置为null

但是相比较第一个,这里重点关注结束条件——n,和返回结点的指向(第一个是指向null,这个是指向第n个结点的下一个

4 反转链表的一部分(LetCode 92)

问题:给一个索引区间[m,n](索引从 1 开始),仅仅反转区间中的链表元素。

ListNode reverseBetween(ListNode head, int m, int n){
    
    
	// base case
	if (m==1){
    
    
		return reverseN(head, n);
	}
	// 前仅到反转的起点触发 base case
	head.next = reverseBetween(head.next, m-1, n-1);
	return head;
}

核心关注点是:边界状态调用链表中的前N个结点

总结

递归强调三步骤:

  1. 定义递归方程(递归后长怎么样的?)
  2. 递归直接调用返回(脑子别压栈!)
  3. 明白递归终止条件(basecase)

参考:
https://leetcode-cn.com/problems/reverse-linked-list-ii/solution/bu-bu-chai-jie-ru-he-di-gui-di-fan-zhuan-lian-biao/

猜你喜欢

转载自blog.csdn.net/The_dream1/article/details/111999799