46 带环链表(Linked List Cycle)

本文介绍了如何通过快慢指针法解决判断链表中是否存在环的问题。具体思路是设置两个指针,一个每次前进一步,另一个前进两步,如果它们相遇则链表有环,否则无环。算法的时间复杂度为O(n),空间复杂度为O(1)。给出了C++版本的源码实现,代码中确保了快指针移动时的有效性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 题目

题目:带环链表(Linked List Cycle)
描述:给定一个链表,判断它是否有环。

  1. 链表长度不超过10000

lintcode题号——102,难度——medium

样例1:

输入:linked list = 21->10->4->5,then tail connects to node index 1(value 10).
输出:true
解释:链表有环。

样例2:

输入:linked list = 21->10->4->5->null
输出:false
解释:链表无环。

2 解决方案

2.1 思路

  找环的问题可以通过以下方式来解决,使用两个指针从链表头开始,慢指针一次走一步,快指针一次走两步,如果链表中有环,则快慢指针终会相遇,如果快指针走到链表尾部依然没有遇到慢指针,则链表不带环。

2.3 时间复杂度

  时间复杂度为O(n)。

2.4 空间复杂度

  空间复杂度为O(1)。

3 源码

细节:

  1. 快、慢指针同时从链表头出发,若能相遇必有环。
  2. 注意快指针移动的时候要先验证有效性。

C++版本:

/**
* Definition of singly-linked-list:
* class ListNode {
* public:
*     int val;
*     ListNode *next;
*     ListNode(int val) {
*        this->val = val;
*        this->next = NULL;
*     }
* }
*/
/**
* @param head: The first node of linked list.
* @return: True if it has a cycle, or false
*/
bool hasCycle(ListNode * head) {
    // write your code here
    if (head == nullptr)
    {
        return false;
    }

    ListNode * slow = head;
    ListNode * fast = head;
    while (fast != nullptr && fast->next != nullptr)
    {
        slow = slow->next;
        fast = fast->next->next;

        if (slow == fast)
        {
            return true; // 快慢指针能够相遇,则必定有环
        }
    }

    return false;
}
<think>好的,用户现在问的是如何判断链表是否有。我之前刚回答了关于用两个栈模拟队列的问题,现在需要切换到这个新问题。首先,我得回忆一下链表检测的常见方法,最经典的就是快慢指针法。不过,用户可能需要更详细的解释,包括其他方法,比如哈希表法,或者可能的应用场景。 用户可能的需求是什么呢?他们可能是在准备面试题,或者在学习数据结构的基础知识。所以需要给出清晰的步骤,可能的时间复杂度分析,以及不同方法的优缺点比较。另外,用户可能对实现细节感兴趣,比如具体的代码示例,或者如何证明快慢指针的正确性。 然后,我需要考虑用户可能的背景。如果是初学者,可能需要更通俗的解释,避免使用太多专业术语。但根据之前的回答,用户可能已经有一定的数据结构基础,所以可以适当深入,但保持易懂。 接下来,我需要组织回答的结构。通常,先介绍问题,然后列出不同的解决方法,再详细说明每个方法的步骤,时间复杂度,空间复杂度,以及可能的代码示例。最后总结哪种方法更优,适用场景等。 对于快慢指针法,需要解释为什么两个指针会相遇。比如,快指针每次走两步,慢指针走一步,如果有,快指针会追上慢指针。可能需要数学证明,比如相对速度的问题,这样用户才能理解为什么这样可行。 哈希表法则是遍历链表,记录每个节点,如果遇到重复的节点,说明有。这种方法虽然容易理解,但空间复杂度较高,O(n)。需要比较两者的优缺点,快慢指针的空间复杂度是O(1),但时间复杂度可能稍高吗?其实两者都是O(n),但快慢指针不需要额外空间,所以更优。 可能还需要提到边界情况,比如链表为空,或者只有一个节点的情况如何处理。代码示例中是否需要处理这些情况?比如在Python中,检查节点是否存在,或者循的条件设置。 另外,用户可能会问,如果链表入口在哪里,或者如何找到的长度。不过当前问题只是判断是否有,所以可能需要分清楚,但如果有余力,可以稍微提一下,但不要偏离主题。 还有,是否需要图示辅助说明?虽然在文本中无法画图,但可以用文字描述,比如快慢指针在中的移动过程,或者哈希表存储节点的过程。 最后,确保回答符合用户之前设定的格式要求,比如使用中文,结构清晰,分步骤说明,可能包括代码示例。同时检查LaTeX的数学公式是否正确,比如时间复杂度用$O(n)$表示。 现在总结一下,回答的结构应该是: 1. 问题介绍:什么是链表,为什么需要检测。 2. 方法一:快慢指针法(Floyd判圈算法) - 步骤 - 时间复杂度、空间复杂度 - 正确性证明(简要) - 代码示例 3. 方法二:哈希表法 - 步骤 - 复杂度分析 - 代码示例 4. 方法比较:优缺点,适用场景 5. 应用场景:比如检测链表结构正确性,内存管理中的循引用检测等 6. 常见问题解答:比如为什么快指针走两步,能否走更多步?哈希表法的节点比较问题等 需要确保语言简洁明了,避免冗长,但覆盖关键点。此外,代码示例要正确,符合Python或其他常见语言的语法,并注释关键步骤。 可能还需要注意,在快慢指针法中,循的条件是fast和fast.next都不为空,否则会出现空指针异常。这点在代码中要体现出来,比如检查fast.next是否存在再移动两步。 另外,哈希表法中,节点是否可以作为键存储?在Python中,对象是可以作为字典的键的,因为每个节点的内存地址不同,所以即使值相同,不同的节点也会被识别为不同的键。这一点可能需要说明,避免用户误解。 最后,测试用例的举例,比如构造一个有链表,让用户更直观地理解过程。例如,链表1->2->3->4->2(形成),如何被检测到。 总的来说,需要将各个部分组织得逻辑清晰,逐步引导用户理解问题及其解决方案,同时提供足够的技术细节和实际代码,帮助用户掌握实现方法。</think>### **判断链表是否有的方法详解** --- #### **1. 问题定义** 链表中的 **** 是指某个节点的 `next` 指针指向了链表中已经遍历过的节点,导致链表尾部形成闭。判断链表是否有是数据结构中的经典问题,常用于检测链表结构的正确性。 --- #### **2. 解决方法** ##### **方法一:快慢指针(Floyd 判圈算法)** **核心思想**:使用两个指针,快指针(每次走两步)和慢指针(每次走一步)。若链表,快指针最终会追上慢指针。 **步骤**: 1. 初始化 `slow` 和 `fast` 指针均指向头节点。 2. 循移动指针: - `slow = slow.next` - `fast = fast.next.next` 3. **终止条件**: - 若 `fast` 或 `fast.next` 为 `null`,说明链表。 - 若 `slow == fast`,说明存在。 **数学证明**: - 假设的周长为 $C$,快慢指针进入后的距离差为 $C - 1$。 - 快指针每步比慢指针多走 $1$,经过 $C - 1$ 步后追上慢指针。 **时间复杂度**:$O(n)$(无时遍历一次链表,有时最多循 $2n$ 步)。 **空间复杂度**:$O(1)$(仅需两个指针)。 **代码示例(Python)**: ```python class ListNode: def __init__(self, x): self.val = x self.next = None def has_cycle(head: ListNode) -> bool: if not head or not head.next: return False slow = head fast = head.next while fast and fast.next: if slow == fast: return True slow = slow.next fast = fast.next.next return False ``` --- ##### **方法二:哈希表法** **核心思想**:记录已访问的节点,若重复访问则存在。 **步骤**: 1. 初始化哈希表(如字典或集合)。 2. 遍历链表,每访问一个节点: - 若节点已在哈希表中,说明有。 - 否则将节点存入哈希表。 3. 遍历到 `null` 时无。 **时间复杂度**:$O(n)$。 **空间复杂度**:$O(n)$(需存储所有节点)。 **代码示例(Python)**: ```python def has_cycle_hash(head: ListNode) -> bool: visited = set() current = head while current: if current in visited: return True visited.add(current) current = current.next return False ``` --- #### **3. 方法对比** | 方法 | 时间复杂度 | 空间复杂度 | 适用场景 | |--------------|------------|------------|------------------------| | 快慢指针 | $O(n)$ | $O(1)$ | 内存受限或长链表 | | 哈希表 | $O(n)$ | $O(n)$ | 需要快速实现,无内存限制 | --- #### **4. 应用场景** - **链表结构验证**:确保单向链表(如文件系统目录树检测)。 - **内存泄漏检测**:循引用可能导致内存无法释放。 - **算法题**:如 LeetCode 的链表问题([#141](https://leetcode.com/problems/linked-list-cycle/))。 --- #### **5. 常见问题** **Q1:为什么快指针走两步?能否走三步或更多?** - **答**:走两步可确保在有限步内相遇。若快指针走 $k$ 步,需满足 $k - 1$ 与长互质,否则可能无法相遇。两步是效率与实现的平衡选择。 **Q2:如何找到的入口?** - **答**:快慢指针相遇后,将慢指针重置到链表头,两指针同速移动,再次相遇点即为入口(数学证明见 Floyd 算法扩展)。 --- #### **总结** - **推荐快慢指针**:空间复杂度低,适合大规模数据。 - **哈希表法**:直观易实现,但需额外内存。 - **关键点**:理解快慢指针的追赶逻辑,掌握时间复杂度分析。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值