[力扣题目]寻找链表的中间节点,超详细干货讲解,快慢法手把手教会!!

目录

1.前言

2.实现方法

2.1遍历法

2.2快慢引用法

3.总结

1.前言

本题题目来源:寻找链表的中间节点

如图所示,图中红色的节点便是中间节点

可以看到在奇数和偶数节点数量的两种不同情况下,对中间节点的定义是有所不一样的

当是奇数个节点的时候,直接取中间那个节点;当是偶数个节点的时候,取中间节点的第二个

老规矩,小编在给各位友友们手搓两个链表,希望通过这种图解的方式,给友友们带来清晰易懂的讲解.

每个节点上方的数字是该节点在栈上的引用(小编随便起的),指向堆内存中的真实节点对象.

2.实现方法

2.1遍历法

观察上面两个链表,我们不难发现链表的长度和中间节点的下标存在一定关系

在第一个链表中,链表的长度为5,链表中间节点的下标为2 --->=length / 2 = 5 / 2;(除法会取整)

在第二个链表中,链表的长度为4,链表中间节点的下标为2---->= length / 2 = 4 / 2;

基于前面两种情况,我们可以总结出:当链表长度不管是奇数还是偶数时,中间节点的下标都是链表长度的一半,即length / 2;

所以第一种遍历法的核心思路就是:先遍历一次链表得到链表长度length,再遍历(length / 2)步得到中间节点.(在这里,以步为单位更能体现这个寻找过程,例如在第一个链表中,链表中间节点的下标是2,也就是从头结点开始需要走两步才能到中间节点)

求链表长度,只要当前节点不是null,我们就让计数器+1,小编把这部分代码附上

 int length = 0;//定义一开始的长度
 ListNode curNode = head;//保存头结点
 while(curNode != null){
           length ++;//只要当前节点不是null,我们就让length++
           curNode = curNode.next;//节点继续向后移动
  }     

接下来就是再遍历一次链表,走length / 2 步得到中间节点,小编也把代码附上

curNode =  head;
int count = length / 2;
while(count != 0){
     curNode = curNode.next;
     count --;
}
return curNode;

经过走length / 2 步之后,此时curNode顺利的来到了中间节点的位置,直接返回curNode即可.

这时,可能有的友友们会有疑问:curNode = head;为什么会有这行代码呢?这行代码有什么用呢?

小编在这里给友友们娓娓道来

在前面求链表长度中,我们使用到了curNode这个节点,当求完链表的长度之后,curNode最终会变成null,而在求中间节点的过程中,我们还是需要从头开始遍历的,此时直接用curNode是不行的,因为此时curNode == null,我们只需要再拿到一开始的头结点 即curNode = head,把头结点head重新赋值给curNode即可

那有友友们会有疑惑:为什么不直接使用head节点呢?

在这里,如果直接使用head,head这个引用会发生变化,而非一开始指向头结点了,当我们后续要再想访问到头结点时会造成一定的困难.

这里,你也可以让计数器count++,当count等于length / 2时,退出循环,本质上都是走length / 2步

处理完一般情况后,我们再来处理一下边界情况的

当链表为空时,此时是否还有中间节点?答案是没有的

通过上面这些,这个方法已经被我们搞定了,小编把这个方法完整的代码的附上

class Solution {
    public ListNode middleNode(ListNode head) {
       //当链表为空的时候
        if(head == null ){
            return head;
        }
        //其他情况
        int length = 0;
        ListNode curNode = head;
        //求链表的长度
        while(curNode != null){
            length ++;
            curNode = curNode.next;
        }
        curNode =  head;
        int count = length / 2;
        //遍历寻找中间节点
        while(count != 0){
            curNode = curNode.next;
            count --;
        }
        return curNode;
    }
}

2.2快慢引用法

在上面的遍历法中,我们需要遍历两次链表才能得到中间节点,这个方法很暴力,小编不太推荐这个方法,那有没有只需要遍历一次链表就能得到中间节点的呢?   有的兄弟,有的!!!!!

所谓快慢引用,肯定是有一个走的快(fast),另一个走的慢(slow)

在c/c++中,叫快慢指针法,指针是用来存放地址的;在Java中,叫快慢引用,引用是用来存放地址的,两者在方法上没有什么本质区别.

首先我们先得把fast和slow定义出来先,让fast和slow都指向一开始的head

ListNode fast = head;
ListNode slow = head;

核心思想:fast每次走两步,slow每次走一步,当fast达到条件时,此时的slow即是中间节点

那有友友们问:为什么是这样子做的呢?这个方法背后的原理是什么呢?

这个方法的数学原理如下:当fast的速度为2,slow的速度为1时,在相同时间内,当fast走到终点时,slow走的路程刚好是fast所走路程的一半,slow此时在整段路程的中间位置,即链表的中间节点处

小编接下来会给各位友友们讲的明明白白,让大家彻底吃透这个方法!!

小编再次手搓链表,并把每一步的过程展示出来,希望通过这种清晰的方式,帮助各位友友们理解.

链表长度为奇数情况下:

第一次移动:fast走两步,slow走一步

第二次移动:fast走两步,slow走一步

此时slow已经来到了中间节点的位置,fast节点来到了尾节点,fast节点的特点是fast.next == null;

链表长度为偶数情况下:

第一次移动:fast走两步,slow走一步

第二次移动:fast走两步,slow走一步

此时slow顺利的来到了中间节点的位置,fast节点此时的特点是:fast == null;

通过上面两种奇数长度下和偶数长度下的讨论,我们可以总结出slow节点来到中间节点的条件是:

fast.next == null 或者fast == null,也就是说当fast != null 并且 fast.next != null的时候,我们都要不停的重复fast走两步,slow走一步,即要通过循环来解决这个问题.

那有友友们会疑惑不是分两种情况的吗,不应该是fast != null  或者 fast.next != null 吗?

在这里,如果fast 已经等于空了即fast ==  null ,那再访问fast.next 会报出空指针异常,这个问题后面也会探讨到

那到底是怎么实现fast一次走两步,slow一次走一步的呢?小编把代码附上

 while(fast != null && fast.next != null){
       fast = fast.next.next;
       slow = slow.next;
  }

在这里,小编觉得循环的条件很巧妙,这个条件更多是fast能走两步的前提条件,但也和前面两种情况中fast的结束条件有关,不知道各位友友们有没有感受到这一点.

要想访问到fast.next ,fast不能为空,即fast != null;

要想访问到fast.next.next,fast.next不能为空,即fast.next != null;

这两个很好地防范了空指针异常的问题,所以要两个条件同时成立,即fast != null && fast.next != null;

那有友友们问:那我两个条件调换一下顺序有问题吗?即fast.next != null && fast != null,可以吗?

在链表这一类问题中,尤其要特别注意防范空指针异常的问题,这个问题可以说是重中之重!!!

当两个条件调换之后,循环的条件是(fast.next!= null&& fast != null), 如果fast == null 的时候,会首先判断fast.next 是否为空,此时就会报出空指针异常.

在解决完一般情况之后,我们再来处理一下边界情况:当链表为空的时候,直接返回即可

这个方法也已经被我们搞定了,小编把完整的代码附上

class Solution {
    public ListNode middleNode(ListNode head) {
       //判断链表是否为空
        if(head == null ){
            return head;
        }
        //定义两个快慢引用
        ListNode fast = head;
        ListNode slow = head;
        //利用循环
        while(fast != null && fast.next != null){
            fast = fast.next.next;//fast每次走两步
            slow = slow.next;//slow每次走一步
        }
        return slow;//当fast达到循环的结束条件之后,此时的slow就是中间节点
    }
}

相信各位友友们肯定对这种方法有了深深的感受,这种利用快慢的思想在链表之类的题目,很多都可以实践到,友友们可以学到满满的干货喔!!!!

3.总结

关于今天的分享主要介绍了寻找链表中间节点的两种方法,第一种是遍历思路,先求出链表的长度

然后利用length / 2 来得到链表的中间节点;第二种方法是利用快慢引用法,两个引用一快一慢,当fast达到一定条件时,此时的slow就是所要求的中间节点.

最后希望可以与各位友友们共同努力,共同进步,有什么问题,欢迎各位友友们在评论区批评指正喔!!

小编期待你的精彩发言.

力扣平台上关于链表公共节点的题目有“两个链表的第一个公共节点”,也被称为“相交链表” 。 该题有相关示例,如输入 intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1 ,输出为 Reference of the node with value = 2 。其输入解释为相交节点的值为 2 (注意,如果两个列表相交则不能为 0),从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4],在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点[^1]。 对于该题的解,有以下两种常见思路: - 一种思路是设置两个头指针,分别指向两个链表。循环结束条件是两个指针相等(或者说相遇),相等情况要么同时指向 null(不相交),要么存在公共节点部分(相交)。在循环中,若指针为 null 即指向链表尾部,将指针改为指向另一个链表;若不为 null,则继续遍历当前链表的下一位。最后返回该指针。以下是 Java 代码实现: ```java public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { if (headA == null || headB == null) { return null; } ListNode pA = headA, pB = headB; while (pA != pB) { pA = pA == null ? headB : pA.next; pB = pB == null ? headA : pB.next; } return pA; } } ``` - 另一种思路是先分别遍历两个链表,得到它们的长度和尾节点。如果两个链表相交,那么它们的尾节点一定相同;如果不相交,那么它们的尾节点不相同。若相交,由于公共部分一定在末尾,可将较长的链表先遍历一定的长度,使得两个链表剩余长度相等,之后同时遍历两个链表,直到到第一个相同节点,即为它们的相交起始节点;如果没有到相交节点,返回 null[^3]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值