算法通关村第一关——链表黄金挑战笔记

该部分主要针对链表中环的问题进行总结,提出自己的理解和白银挑战笔记联动,进一步深化对双指针的理解,强化知识点,同时巩固链表寻找倒数第K节点模板,完成对应的变式训练

链表中有关环的问题分成两类,第一类是判断链表中是否存在环结构,第二类是找到链表环结构的入口。两类问题相辅相成,第一类问题是第二类问题的基础,第二类问题反过来加强巩固第一类问题。

1.判断链表中是否存在环结构

这种题目有两种方法解决,分别是双指针(空间复杂度O(1))和集合(空间复杂度O(n)),前者带有一定的思考性存在,但是了解之后记住就行!后者emmm太无脑了,不推荐!(工作中力推!)

集合

这种方法的思想很简单:遍历链表、存储节点、查询节点!遍历过程中,如果节点存在于集合,那么存在环,如果不存在于集合,则存入集合。遍历到表尾还没判断有环,大胆点,没环!直接上代码!

    public static boolean hasCycleByMap(ListNode head) {
        HashSet<ListNode> set = new HashSet<>();
        ListNode pNode = head;
        while(pNode != null){
            if(set.contains(pNode)){
                return true;
            }else{
                set.add(pNode);
            }
            pNode = pNode.next;
        }
        return false;
    }

双指针

这种方法的思想,我称其为“相对运动”,试想一下操场上有兔子和乌龟比赛跑步,兔子贪睡,乌龟勤快,若干时间后,乌龟绕操场爬完一圈后总能遇到还在起点贪睡的兔子。

在这里,我们设置快慢指针fast和slow,fast步幅每次比slow多走一步(为什么我不说fast走2步,slow走1步呢?事实上,fast走3步,slow走2步未尝不可!)那么“相对来看”,slow似乎是静止的,fast始终移动,此时fast相当于乌龟,slow相当于兔子,那么在环结构里,fast肯定能跑完一圈再次遇到slow。(这也就是笨鸟先飞吧!)

我是笨鸟,我先上代码!双指针判断链表是否存在环结构是个模板!

我的理解:注意哈,fast走两步的代码逻辑是fast = fast.next.next,因此:fast存在,才能访问fast.next;fast.next存在,才能访问fast.next.next,于是乎while判断条件顺利得出(特别提醒:先有爸妈,才有你,先有fast才有fast.next)

    public static boolean hasCycleByTwoPoint(ListNode head) {
        if(head == null){
            return false;
        }
        ListNode fast = head, slow = head;
        while(fast != null && fast.next != null ){
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow){
               return true;
            }
        }
       return false;
    }

2.寻找链表环结构的入口

这种题目有三种方法解决,分别是两组双指针(空间复杂度O(1))、三组双指针(空间复杂度O(1))和集合(空间复杂度O(n))。

第一种方法思考性强,但是了解之后代码精简;第二种方法,属于是双指针变式应用和寻找链表第K节点模板的变式应用,十分推荐练习巩固思路!最后一种方法嘛,我的评价:******(工作中力推!******:我是谁?

集合

万事开头难嘛,我们先从简单的来,还是那句话:韩信带净化!特殊情况特殊记!遍历链表、存储节点、查询节点!方法思想同上,唯一一点区别:如果节点存在于集合,那么该节点就是环结构的入口!直接上代码!

    public static ListNode detectCycleByMap(ListNode head) {
        HashSet<ListNode> set = new HashSet<>();
        ListNode pNode = head;
        while(pNode != null){
            if(set.contains(pNode)){
                return pNode;
            }else{
                set.add(pNode);
            }
            pNode = pNode.next;
        }
        return null;
    }

两组双指针

这种思想如下:第一组双指针判断环存在!第二组双指针找到环结构入口!当第一组双指针判断环存在的时候,fast==slow,我们记当前节点为Z,紧接着第二组双指针,分别从表头和Z节点开始遍历,首次相等的时候就是环结构的入口!

第二组双指针为什么行?答案如下:

简单情形:fast在第二圈和slow相遇

fast = a+b+c+b

slow = a+b

fast = 2slow

联立方程,求解得:a = c

复杂情形:fast在第n圈和slow相遇

fast = a+n(b+c)+b

slow = a+b

fast = 2slow

联立方程,求解得:a = (n-1)(b+c) + c

为什么slow还是a+b,不可能多跑几圈嘛?答:不可能!且仅能是a+b(自己分析一下,以b = c 即slow跑了半圈为例)

如何理解a = (n-1)(b+c) + c,理解就是第二组双指针,slow在环结构中打转(n-1)圈,然后ptr和slow再运动c长度相遇在环结构入口!

了解清楚为什么之后,直接上代码!(是不是十分清晰,paper tiger!)

    public static ListNode detectCycleByTwoPoint(ListNode head) {
        //第一次双指针判断是否存在环
        ListNode slow = head, fast = head;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
            //存在环
            if(slow == fast){
                //第二次双指针找到环入口
                ListNode ptr = head;
                while(ptr != slow){
                    ptr = ptr.next;
                    slow = slow.next;
                }
                return ptr;
            }
        }
        return null;
    }

 写到这里我不得不补充一下!链表判断环结构是否存在是个模板啊!

三组双指针

这种思想如下:第一组双指针判断环存在!第二组双指针求环结构的长度K!第三组双指针寻找链表倒数第K节点!

这么一看,这道题确实很简单,两个模板,一个简单遍历。真的很简单嘛?答:未必!

《链表白银挑战笔记中》我们给出了寻找链表倒数第K节点模板,还记得while循环的判断条件嘛?不记得了?那我给你看一下模板代码!(稍等我找找....2000 Years Later)


    public static ListNode getKthFromEnd(ListNode head, int k) {
        ListNode slow = head, fast = head;
        while(fast != null && k > 0){
            fast = fast.next;
            k--;
        }
        while(fast != null){
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }

while循环条件是fast != null,当然有问题!环结构中fast不可能为null,这个while岂不是陷入死循环了?难道模板失灵了吗?答:并非如此!

按照模板fast会先走K步,然后slow和fast同步移动,当走到蓝色位置(倒数K-1)时,再走一步就会出现slow和fast指向同一个位置的情况,这个节点就是倒数第K个节点,模板中while循环条件也应该修改为while(fast != slow),纸上谈兵?NO!直接上代码!

    public static ListNode detectCycleByThreePoint(ListNode head){
        //第一组双指针判断环
        ListNode slow = head, fast= head;
        boolean existCircle = false;
        int len = 1;
        while(fast != null && fast.next != null){
            slow = slow.next;
            fast = fast.next.next;
            //存在环
            if(slow == fast){
                existCircle = true;
                break;
            }
        }

        if(existCircle){
            //第二组双指针求环长
            ListNode pNode = slow;
            while(pNode.next != slow){
                len++;
                pNode = pNode.next;
            }
            //第三组双指针求链表倒数k节点
            slow = head;
            fast = head;
            while(fast != null && len > 0){
                fast = fast.next;
                len--;
            }
            while(fast != slow){
                fast = fast.next;
                slow = slow.next;
            }
            return slow;
        }else{
            return null;
        }
    }

纸上得来终觉浅,绝知此事要躬行!Ok,《算法通关村第一关——链表黄金挑战笔记》结束,喜欢的朋友三联加关注!关注鱼市带给你不一样的算法小感悟!(幻听)

再次,感谢鱼骨头教官的学习路线!鱼皮的宣传!小y的陪伴(终于想好了!)!ok,拜拜,第二关第一幕见!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值