LeetCode刷题之HOT100之环形链表Ⅱ

2024/6/19 今天是学校的毕业典礼,线上直播看了一会儿。起床来实验室,路上细雨就开始变大,看来我不能在细雨中呼喊了,在暴雨中淋成落汤鸡。到实验室,放下包,打开电脑,去二楼接水,看看我的栀子花。流水账到此,听着《寂寞的季节》,敲着键盘。

1、题目描述

在这里插入图片描述

2、逻辑分析

昨天才做数组,一下就到了链表,当年还用c++写链表,还有指针。回归主题,环形链表求起始点。我得想法就是:遍历链表,存在哈希集合里面,如果再次遇到,那就表示遇到了环的第一个节点。那么开始敲代码。

3、代码演示

public ListNode detectCycle(ListNode head) {
        // 创建一个HashSet,用于存储已经遍历过的节点  
        // 如果某个节点在HashSet中已经存在,说明存在环
        Set<ListNode> check = new HashSet<ListNode>();
        // pos是一个指针,用于遍历链表
        ListNode pos = head;
        // 当pos不为null时,继续遍历
        while(pos != null){
            // 如果check集合中已经包含了pos节点,说明存在环,返回该节点
            if(check.contains(pos)){
                return pos;
            }else{
                // 如果check集合中没有pos节点,将其加入集合
                check.add(pos);
            }
            // 移动pos到下一个节点  
            pos = pos.next;
            
        }
        // 没有环,返回null
        return null;   
    }

以上是哈希表解法。时间复杂度是O(n),空间复杂度也为O(n)。

哈希表解法简单粗暴,有没有一种方法难一些,不好理解呢?那当然有啦,谈到链表怎么能忘了双指针呢。是的,另一种解法就是快慢双指针。大致思路是:

Floyd’s Cycle-Finding Algorithm(快慢指针法)

  1. 初始化:定义两个指针,一个快指针(fast)和一个慢指针(slow),都指向链表的头节点(head)。

  2. 移动指针:
    在循环中,每次迭代时:
    慢指针(slow)向前移动一步(slow = slow.next)。
    快指针(fast)向前移动两步(fast = fast.next.next),但需要先检查fastfast.next都不为null,以防止在链表末尾时抛出空指针异常。

  3. 检查相遇:
    如果在某个时刻快指针和慢指针相遇(即它们指向同一个节点),说明链表中存在环。
    如果快指针到达链表末尾(即fast或fast.nextnull),说明链表中不存在环,算法结束。

  4. 找到环的入口:
    一旦确定存在环,为了找到环的入口,我们使用另一个指针(ptr)从头节点开始遍历链表。
    同时,让慢指针(slow)继续在环中移动。
    ptrslow相遇时,它们所指向的节点即为环的入口节点。

为什么是这样呢?我解释不了,反正先记住就可以啦,下面是代码演示:

public ListNode detectCycle(ListNode head) {
        // 如果头节点为空,说明链表为空,不存在环,直接返回null 
        if(head == null){
            return null;
        }

        // 定义快慢指针,初始时都指向头节点
        ListNode fast = head, slow = head;

        // 当快指针不为null时,继续循环
        while(fast != null){
            // 慢指针每次移动一步
            slow = slow.next;
            // 检查快指针的下一个节点是否为null,如果是,说明快指针已经到达链表末尾,不存在环,返回null  
            if(fast.next != null){
                // 快指针每次移动两步
                fast = fast.next.next;
            }else{
                return null;
            }

            // 如果快慢指针相遇(即指向同一个节点),说明存在环
            if(fast == slow){
                // 定义一个指针ptr,从头节点开始重新遍历
                ListNode ptr = head;
                // 当ptr和slow不相遇时,继续遍历  
                // 当它们相遇时,ptr所指的节点就是环的入口节点
                while(ptr != slow){
                    ptr = ptr.next;
                    slow = slow.next;
                }
                // 返回环的入口节点 
                return ptr;
            }
        }
        // 如果快指针为null,说明链表已经被遍历完,不存在环,返回null
        return null;
    }

4、复杂度分析

  • 时间复杂度O(n)。其中 n为链表中节点的数目。在最初判断快慢指针是否相遇时,slow
    指针走过的距离不会超过链表的总长度;随后寻找入环点时,走过的距离也不会超过链表的总长度。因此,总的执行时间为 O(n)+O(n)=O(n)
  • 空间复杂度O(1)。这里只用到了三个指针,所以是常数级。
  • okok,结束啦,拜拜!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值