LeetCode206 反转链表 递归法图示+逐行解题步骤剖析

一步一步看清迭代的每一步操作

  • LeetCode206 反转链表
    • 递归方法

刚刚开始刷LeetCode的小白,决定用博客记录自己的刷题经历,监督自己吃透每一道题,尽量用简单明了的文字表述清楚解题步骤。

LeetCode206 反转链表

反转链表有许多种方法,迭代,递归等方法都可以实现。
笔者做题时主要是对迭代的步骤难以想清楚,翻看许多题解,一般讲解地没有那么详细,于是自己对照代码把每一步都画了一遍,终于理清了这个流程。想到可能还有许多小伙伴困于同样的问题,于是决定写下这篇博客记录下来,供大家参考,笔者水平有限,会尽量详细地记录自己的理解,如果有理解错误或表述不清的地方,欢迎大家指正讨论。
接下来让我们把每一步都画出来,好好剖析一下每一步的具体操作。

递归方法

简单思路:每个函数内只包含判断是否到递归基,修改两个节点间的指向,调用自身和返回指针的功能。

代码如下:

ListNode* reverseList(ListNode* head) {
        if(head==NULL || head->next==NULL) return head;

        ListNode* p=reverseList(head->next);
        head->next->next=head;
        head->next=NULL;
        return p;
}

分步过程:

PS:题目中画了5个节点,这里为了节省篇幅和笔者时间 ,只画4个,大家注意力放在理解过程上就好。

图示中的各种表示形式 意义
不同颜色的代码块 位于不同层次的函数中
标黄色块突出显示的语句 此步骤正在执行的语句
字体变绿的语句 已经执行完毕的语句
每幅图左上角的数字 第一个表示在第几层函数中,第二个表示在该函数中执行的第几步

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第1张图片
1-1:刚开始的头指针指向第一个节点。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第2张图片
1-2:此时头指针的值为1,非空;头指针的next的值为2,也非空。if判断语句的条件不成立,将不会执行后面的return。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第3张图片
1-3:此时在这个蓝色函数中定义一个p指针,递归调用自身,此后该层蓝色函数将不会再往下执行,而是会进入到被调用的那个(紫色)函数中去,直到那个(紫色)函数执行完毕才会接着执行这个蓝色函数。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第4张图片
2-1:现在进入紫色函数的这一层。由于传入的参数是“head->next”,于是这层紫色函数中的head指针相比与上层蓝色函数的head就往后移了一个节点。

这也正好体现了在不同层级的递归函数中,即使是名字相同的变量,实际上也可能是不同的。而其实这些变量由于所在的不同函数层级的生命周期不同,这些变量其实并没有同时存在过。前面的函数在遇到递归调用后,会被压入一个栈中,相当于被冻结起来了,直到与之相邻的后面那个递归调用函数执行完毕,它才会被弹出栈重新恢复运行,在写代码和解题过程中要特别注意这一点。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第5张图片
2-2:此时头指针的值为2,非空;头指针的next的值为3,也非空。if判断语句的条件不成立,将不会执行后面的return。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第6张图片
2-3:同1-3,进入下一层递归调用。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第7张图片
3-1:现在进入橙色函数的这一层。这层橙色函数中的head指针相比与上层紫色函数的head再往后移了一个节点。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第8张图片
3-2:此时头指针的值为3,非空;头指针的next的值为4,也非空。if判断语句的条件不成立,将不会执行后面的return。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第9张图片
3-3:同上,进入下一层调用。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第10张图片
4-1:这层红色函数中的head指针相比与上层橙色函数的head再往后移一个节点。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第11张图片
4-2:此时头指针的值为4,非空;头指针的next的值为空。if判断语句的条件成立,将执行后面的return。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第12张图片

4-3:执行return head语句,返回此时的head指针。该红色函数中后面的代码将不会再执行,该红色函数的生命周期结束,红色函数中所有的存储空间都将被释放,红色函数的head指针和p指针都不再存在。该红色函数仅返回一个指向4节点的head指针给上层的橙色函数。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第13张图片
3-3:故橙色函数中的p指针将被赋值为红色函数所返回的4节点。红色函数开始接着往下执行。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第14张图片
3-4:head指针的next为4节点,该句的意思即为4节点指向head指针所指向的节点,即3节点。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第15张图片
3-5:head指针的next指向空,即将3节点指向4的箭头去掉。

3-4和3-5两步完成了指针指向的反转。注意顺序,该顺序不可逆。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第16张图片
3-6:返回橙色函数中的p节点,至此,橙色函数也全部执行完毕,内存空间全部释放。

其实此时我们可以看出p指针就变成了整个反转后链表的头指针,指向反转后链表的第一个节点,这也是不会变了的,故每次递归函数都将返回p指针,递归函数中也不会修改p指针,递归函数中的所有的改变都由head指针完成。记住此点也许能避免一些不必要的错误。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第17张图片
2-3:紫色函数中的p指针被赋值为橙色函数的返回值,即那个不会改变了的头指针。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第18张图片
2-4:同上,紫色函数中的2节点的下一个(next)节点3节点指向2节点。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第19张图片
2-5:head指针指向的节点2节点指向空,即删除2节点指向3节点的箭头。

至此,2节点与3节点间的指向反转完成。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第20张图片
2-6:返回紫色函数中的p节点,至此,紫色函数也全部执行完毕,内存空间全部释放。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第21张图片
1-3:蓝色函数中的p指针被赋值为紫色函数的返回值,即那个不会改变了的头指针。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第22张图片
1-4:同上,蓝色函数中的1节点的下一个(next)节点2节点指向1节点。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第23张图片
1-5:head指针指向的节点1节点指向空,即删除1节点指向2节点的箭头。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第24张图片
1-6:函数的最后依然返回链表的头指针,至此,整个程序执行完毕,如图所示几乎全部语句变绿,除了部分if判断语句条件没满足时没有执行的return语句。

最后让我们整理一下我们等到的链表。

LeetCode206 反转链表 递归法图示+逐行解题步骤剖析_第25张图片
head指针在程序结束后会被释放,head指针也就不复存在。p指针也不再存在,只是程序最终会返回这个p指针,再看剩下的链表,完美反转~~完美!

写在最后:递归法会反复调用自身,时间和内存的消耗相对较大,程序写起来比较简单的代价是时间和空间的效率下降。想写出高效的代码还是要尽量减少递归的使用。

贴上别的方法(效率更高),不过本文就不再赘述了。

ListNode* reverseList(ListNode* head) {
        ListNode* pre=NULL;
        ListNode* cur=head;
        while(cur)
        {
            ListNode* temp=cur->next;
            cur->next=pre;
            pre=cur;
            cur=temp;
        }
        return pre;
    }

你可能感兴趣的:(LeetCode题解,链表,c++,leetcode,递归法,数据结构)