目录
1.前言:如何实现链表的反转?
本题来源于力扣第206题 : (题目来源)
小编和你一样是刚起步的学习者,记录从困惑到理解的完整过程,希望能帮助到同样困惑的你!

那小编问大家一个问题:什么是链表的反转呢?
那有友友们说我逆序打印一个链表不就可以了吗?那小编再问友友们一个问题:如何逆序打印链表呢?小编也会在文章结尾处附上详细的讲解.
所谓链表的反转 --->是链表本身的结构发生变化,通过逆序打印链表并不能解决本质上的问题
小编在这给友友们手搓一个链表


每个节点上方的数字(小编随便起的)是该节点在栈上的引用,通过栈上的引用来指向堆内存中的节点
通过这两张图片的观察,我们不难发现,整个链表的自身结构发生变化,具体表现为每个节点的next域指向发生变化,最初head指向第一个节点,反转之后head指向最后一个节点.
2.实现方法
2.1头插法
从上面的两张图片对比中,我们可以发现链表最开始的节点,反转之后变成在链表最末尾了
那友友们思考一个问题:在链表的插入方式中,哪种插入方式会让最先插入的节点变成在链表的最末尾处呢?
此时友友们心中肯定有一个答案:那就是头插法了
我们先把第一个节点单独拎出来,也就是断开第一个head节点,并定义一个节点来保存head后面的节点,这两步的代码如下
ListNode curNode = head.next;
head.next == null;
这两步切不可调换顺序,调换顺序之后,head后面的节点将不能再被访问到
接下来便是头插法的插入步骤了,让curNode的next域指向一开始的头结点(head节点),更新head节点的位置(也就是head节点要往前移动),这两步代码如下
curNode.next = head;
head = curNode;
各位友友们观察发现,接下来我们都要不停地重复这个插入步骤,一直到链表为空的时候,此时我们便要引入一个循环来解决这个问题,只要当前节点不是null,我们就进行头插操作.
那友友们再思考一个问题:在前面两步插入的代码中,我的curNode节点是head节点后面的第一个节点,通过这两步代码,curNode节点成功插入到head节点之前,并更新好了head节点的位置.此时会发现一个明显的问题:curNode节点后面的节点访问不到了
通过这个问题,我们思考是不是要在进行插入步骤之前要保存好curNode节点后面的节点啊,以方便后续节点访问过程
总结前面出现的问题,小编用代码实现如下
ListNode curNode = head.next;
head.next == null;
ListNode curN = null;//定义一个节点,用来保存curNode节点后面的节点
//当链表不是空的时候,就进入循环
while(curNode != null){
curN = curNode.next;//在每次头插之前,先保存好后面的节点
curNode.next = head;
head = curNode;
curNode = curN;//进行完一次头插之后,让curNode移动到后面的节点(这个节点先前保留好),继续重复头插
}
curN这个节点可以在循环外面定义,也可以在循环里面定义,影响不大.
通过这段代码,那么一般链表的反转操作,我们已经搞定了,如果各位友友们还有疑惑难解或者小编有什么错误疏忽,欢迎各位友友们在评论区进行批评指正喔!!!!
通过前面问题的解决,我们已经搞定了一般情况下的反转链表
友友们再思考一个问题,当链表为空或者只有一个节点(当只有一个节点时,不会进入while循环,讨论也没事)的时候,是否还需要反转链表呢?这便是特殊情况的链表,对这种特殊情况的链表,小编直接将代码附在下面咯!
if(head == null || head.next == null){
return head;
}
这里的返回类型是题目要求的
经过前面的分析,这道题已经被我们搞定啦!!!! 小编把完整的代码附上
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null){
return head;
}
ListNode curNode = head.next;
ListNode curN = null;
head.next = null;
while(curNode != null){
curN = curNode.next;
curNode.next = head;
head = curNode;
curNode = curN;
}
return head;
}
}
最后只需要返回反转之后链表的头结点即可
2.2带哨兵节点的插入法
通过第一种方法,我们知道处理链表的反转,分为特殊情况和一般情况进行处理,分开处理能够让代码的逻辑更严密,是好事!!!
那么有没有一种方法不用分开情况进行处理的? 有的兄弟,有的!!!!
所谓哨兵节点,通常其数据域没有实际意义,我们主要使用它的next域.哨兵节点的next域用于指向链表的第一个实际数据节点(当链表为空时,next域指向null;当链表非空时,next域指向第一个有效数据节点).

此时,我们定义一个哨兵节点newH,再定义两个节点,curNode用来保存head节点的位置,curN用来保存curNode后面的节点.
ListNode newH = new ListNode(-1);
ListNode curNode = head;
ListNode curN = null;
那有友友们问此时curN = head.next;为什么不是这样子的呢?
首先在这种方法中,我们不再分情况进行讨论,如果此时链表为空,即head == null时,这行代码会报出空指针异常.
接下来,我么进行节点的插入

插入步骤的代码如下:
curNode.next = newH.next;
newH.next = curNode;
那有友友们问:为什么不是newH.next = curNode呢?
在第一次插入的时候,这两种看不出有什么区别,但在第二次插入的时候,继续用这行代码会造成链表的断裂,不再是一个完整的链表,而且当链表不是空的时候,上面两行代码可以很巧妙地将第一个节点的next域置为空.
这个方法完整的代码如下:
class Solution {
public ListNode reverseList(ListNode head) {
ListNode newH = new ListNode(-1);
ListNode curNode = head;
ListNode curN = null;
while(curNode != null){
curN = curNode.next;//保存curNode后面的节点
curNode.next = newH.next;
newH.next = curNode;
curNode = curN;//curNode继续向后移动
}
return newH.next;
}
}
我们实际所需的链表是在哨兵节点的后面,所以最后只需要返回newH.next即可
当链表为空的时候,此时循环进不来,直接返回newH.next,也就是null.
最后相信各位友友们已经彻底搞定这道题啦!!!!!!!
3.逆序打印链表

逆序也就是从尾到头来打印链表,这里我们可以利用递归的思想.
链表的最后一个节点也就是尾节点,它有什么特点呢? 它的next域为null;
此时,我们可以想到递归的结束条件,当该节点为null的时候,我们就让它停止
在这里,小编直接将完整的代码附上
public void displayReverse(ListNode head){
//递归的结束条件
if(head == null){
return ;
}
displayReverse(head.next);//最后遍历到head.next == null作为结束条件
System.out.print(head.val + " ");//打印节点的值
}
在这里,要特别注意空指针异常的问题,在链表之类的问题中,都要尤其注意这个问题.
好了,以上就是今天的分享了!!!!!有什么问题,都可以在评论区给小编留言喔!!!!!
如果觉得小编写的还行,麻烦动动小手给小编一键三连喔!!!!!!小编在这里谢谢大家了![]()
最后希望可以与各位友友们共同学习,共同进步,在代码的海洋中不断探索.

2242

被折叠的 条评论
为什么被折叠?



