LeetCode | 141 linked list cycle

https://github.com/dolphinmind/datastructure/tree/datastructure-linkedlist

分析

  • 在环形链表问题中,通过调整快指针和慢指针以不同的速度移动来找到环的入口点,这个过程类似于寻找链表的中间结点,当它们在环内相遇,也就相当于找到了环的"中间位置"

1. 双指针法

数学推理
基本假设

  • 假设环的长度为(C)
  • 假设从链表的头部到环的入口点的距离为(A)
  • 假设从环的入口点到快慢指针第一次相遇点的距离为(B)
  • 假设从快慢指针第一次相遇点回到环的入口点的距离为(C-B)

证明过程

  1. 慢指针的移动
  • 慢指针每次移动一步
  • 当慢指针进入环后,它会沿着环移动,直到与快指针相遇
  1. 快指针的移动
  • 快指针每次移动两步
  • 当快指针进入环后,它也会沿着环移动,直到与慢指针相遇
  1. 相遇点分析
  • 当快指针和慢指针相遇时,假设慢指针走了(x)步,那么快指针走了(2x)步
  • 慢指针从链表头部到相遇点的距离为(A+B+rC),其中(r)是慢指针在环内绕圈的次数
  • 快指针从链表头部到相遇点的距离为(A+B+kC),其中(k)是快指针在环内绕圈的次数
  • 因为快指针的速度是慢指针的两倍,所以快指针走的距离是慢指针的两倍,即2(A+B+rC) = A+B+kC
  1. 求解方程
  • 从上述方程可以得出A + B + rC = |k-r|C => A + B = C + hC
  • 这意味着慢指针走过的距离 (A+B+rC)是环长度(C)的整数倍
  • 由于 (A+B+rC)是慢指针从链表头部到相遇点的距离,而(|k-r|C)是快指针在环内绕圈的总长度,这意味着快指针和慢指针在环内相遇

这时可以发现快指针离环入口只需要走(C-B) + hC 距离,与链表的头部到环入口距离一致,将快指针回填到链表头部,类似于尾对齐法,即找到了相交点

2. 哈希法

使用哈希法来解决环形链表问题时,哈希冲突的概率非常小

  1. 节点引用的唯一性:在Java中,每个对象都有一个唯一的内存地址,这通常作为对象的默认hashCode值。因此,只要两个对象不是同一个实例,它们的内存地址通常是不同的,这减少了哈希冲突的可能性
  2. 现代哈希表的设计:现代的哈希表设计通常能够很好地处理冲突。例如,Java的HashSet使用了HashMap作为底层数据结构,它能够高效地处理哈希冲突。即使发生冲突,也会通过链表或红黑树的方式存储冲突的元素
  3. 动态调整大小:当哈希表中的元素数量增加到一定程度时,哈希表会自动进行扩容,以减少冲突的发生。这意味着随着元素数量的增长,哈希表的大小也会相应地增大,从而降低冲突率
  4. 链表节点的数量限制:在实际应用中,链表的长度通常是有限的。对于大多数应用场景来说,链表的长度远小于哈希表的容量,这也降低了冲突的概率

在Java中

  • 内存地址的分布:Java虚拟机,对象的内存地址通常是随机分配的,这有助于减少哈希冲突
  • 哈希表的负载因子:HashSet通常有一个负载因子(默认为0.75),当哈希表的负载超过这个比例时,哈希表会自动扩容,从而降低冲突率
  • 哈希函数的质量:Java的Object类的hashCode方法通常会产生较为均匀分布的哈希值,这也有助于减少冲突

主类

package com.github.dolphinmind.linkedlist;

import com.github.dolphinmind.linkedlist.uitls.ListNode;

/**
 * @author dolphinmind
 * @ClassName LinkedListCycle
 * @description 141. 环形链表
 * @date 2024/8/4 slow的速度v = 1
 *                fast的速度v = 2
 *                在没有环的情况下,fast会最先到达末尾,从而返回false
 *                在有环的情况下,  fast在未进入环之前的情况和上述类似,fast进入环之后,fast开始在环内转圈
 *                直到slow也进入圈内,两者的相对速度为v = 1,此时fast指针反追击slow,直到fast追击到slow为止
 *                刚刚在思考的时候,突然想起链表相交的问题:发觉160链表相交问题与当前141环形链表问题有一定的相似性
 *
 *                假设 headA = 1->2->3->4
 *                    headB = 5->6->7->8->9->4
 *
 *                    slow  = 1->2->3->4->5->6->7->8->9->4
 *                    fast  = 5->6->7->8->9->4->1->2->3->4
 *
 *                  head = 1->2->3->4->5->6->7->8->4
 *                  slow = 1->2->3->4->5->6->7->8->4->5->6
 *                  fast = 1->3->5->7->4->6->8->5->7->4->6
 */

public class LinkedListCycle {

    /**
     * 双指针法
     * @param head
     * @return
     */
    public boolean hasCycleTowPointer(ListNode<?> head) {

        if (head == null || head.next == null) {
            return false;
        }

        ListNode<?> slow = head;
        ListNode<?> fast = head;

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

            if (slow == fast) {
                return true;
            }
        }

        return false;

    }



}


posted @ 2024-08-04 16:27  Neking  阅读(9)  评论(0编辑  收藏  举报