LeetCode 热题 100-----26.环形链表 II

一、前置知识

在做这道题之前,必须先掌握3个核心基础知识点,否则看代码会完全懵,我会用最通俗的语言,结合例子讲透,不跳任何步骤。

1. 什么是单链表?(核心基础)

单链表是一种"链式存储"的数据结构,不是像数组那样连续存储,而是由一个个"节点"通过"指针"连接起来的。

举个生活化的例子:就像一串糖葫芦,每个山楂就是一个"节点",山楂之间的竹签就是"指针",把一个个山楂串起来,就形成了单链表。

每个节点(山楂)都包含两个部分:

  • 数据域(val):存储具体的数据(比如山楂的味道、大小,对应题目中节点的val值);

  • 指针域(next):存储"下一个节点的地址"(相当于竹签,指向后面的那个山楂),如果没有下一个节点,next就指向"空"(null)。

题目中给出的节点定义(Python/C++),本质就是在描述这个"山楂"的结构:

Python版本节点定义:

python 复制代码
# 定义单链表节点类(LeetCode默认提供,不用自己写,但要懂)
class ListNode:
    def __init__(self, x):
        self.val = x  # 数据域:存储节点的值
        self.next = None  # 指针域:初始指向空,后续用来连接下一个节点

C++版本节点定义:

cpp 复制代码
// 定义单链表节点结构体(LeetCode默认提供)
struct ListNode {
    int val;  // 数据域:存储节点的值
    ListNode *next;  // 指针域:指向ListNode类型的指针(存储下一个节点的地址)
    // 构造函数:创建节点时,给val赋值,next默认指向空
    ListNode(int x) : val(x), next(NULL) {}
};

补充:什么是"指针"?(必懂)

指针本质就是"地址",比如你家的门牌号,通过门牌号能找到你家;指针就是通过"节点的地址",找到下一个节点。比如ListNode *next,就是"next存储了下一个ListNode节点的地址",通过这个地址,就能访问到下一个节点的val和next。

2. 什么是环形链表?(题目核心)

正常的单链表,最后一个节点的next是"空"(相当于糖葫芦的最后一个山楂,后面没有其他山楂了);而环形链表,最后一个节点的next不指向空,而是指向链表前面的某个节点(相当于把糖葫芦的最后一个山楂和前面的某个山楂用竹签连起来,形成一个闭环)。

题目中的"入环点":就是环开始的第一个节点(比如糖葫芦闭环时,最后一个山楂连到了第2个山楂,那第2个山楂就是入环点)。

题目中提到的pos:是评测系统内部用来标识环的位置(比如pos=1,就是尾节点连到索引为1的节点),我们写代码时不需要用到pos,因为题目不把pos作为参数传入,只需要通过链表的结构,判断是否有环、找到入环点。

3. 解题必备工具(两种解法会用到)

(1)哈希集合(HashSet/set)

作用:快速判断一个元素是否已经存在(时间复杂度O(1),相当于"查字典",瞬间就能找到有没有这个元素)。

核心特点:不允许存储重复元素(如果尝试往集合里存已经存在的元素,集合不会变化)。

对应代码中的用法:

Python:用set()创建空集合,add()方法添加元素,in关键字判断元素是否存在;

C++:用unordered_set<ListNode*>创建集合(存储的是节点的地址),insert()添加元素,count()方法判断元素是否存在(count返回1表示存在,0表示不存在)。

(2)快慢指针(双指针的一种)

定义:两个指针,一个走得慢,一个走得快,同时从链表头出发。

本题用法:慢指针(slow)每次走1步,快指针(fast)每次走2步。

核心规律(记住即可,后面会讲通俗理解):如果链表有环,快慢指针一定会在环内相遇;相遇后,把慢指针放回链表头,然后快慢指针都每次走1步,再次相遇的节点就是入环点。

通俗理解:就像两个人在环形跑道上跑步,一个慢(每秒1步),一个快(每秒2步),不管一开始站在哪里,快的人总会追上慢的人(因为跑道是环,没有尽头);追上后,让慢的人回到起点,两个人一起每秒走1步,下次相遇的地方,就是跑道的起点(对应入环点)。

4. 补充:Python/C++基础语法(适配)

Python:Optional[ListNode] 表示"这个参数可以是ListNode类型,也可以是None(空)",避免出现"空指针错误";

C++:nullptr 表示"空指针"(和NULL意思一样,更规范),unordered_set需要导入头文件#include <unordered_set>,using namespace std; 可以简化代码(不用写std::unordered_set)。

二、题目详细解析(能懂的核心要求)

1. 题目核心要求

给定一个单链表的头节点head,返回:

  • 如果链表有环 → 返回"入环点"(环开始的第一个节点);

  • 如果链表无环 → 返回null(空指针)。

2. 关键限制

不允许修改原链表(不能给节点做标记、不能破坏链表的连接结构);

进阶要求:用O(1)空间(就是不用额外的存储,比如哈希集合会占用O(n)空间,不符合进阶要求,快慢指针才符合)。

3. 示例解析(结合前置知识,再看示例就懂了)

示例1:输入head = [3,2,0,-4],pos = 1

链表结构:3 → 2 → 0 → -4 → 2(尾节点-4的next指向索引1的节点2);

入环点:节点2(索引1),所以输出这个节点。

示例2:输入head = [1,2],pos = 0

链表结构:1 → 2 → 1(尾节点2的next指向索引0的节点1);

入环点:节点1(索引0),输出这个节点。

示例3:输入head = [1],pos = -1

链表结构:1 → null(无环),所以输出null。

三、解法一:哈希集合法

1. 核心思路(能懂的通俗版)

我们遍历链表的每一个节点,把"已经访问过的节点"存到哈希集合里,就像我们走路时在走过的地方做标记:

  1. 从链表头(head)开始,每次访问一个节点;

  2. 先检查这个节点"有没有做过标记"(也就是是否在哈希集合里);

  3. 如果有标记 → 说明我们之前已经走过这个节点了,这个节点就是"入环点"(因为只有环,才会走到重复的节点);

  4. 如果没有标记 → 给这个节点做标记(加入哈希集合),继续往前走(访问下一个节点);

  5. 如果走到了"空节点"(next是null) → 说明链表没有环,返回null。

2. 优缺点

✅ 优点:逻辑简单,不用记数学规律,不用推导,100%能懂,适合新手快速解题;

❌ 缺点:需要用哈希集合存储所有访问过的节点,空间复杂度是O(n)(n是链表节点个数),不符合题目进阶要求。

3. Python完整代码(每句带注释,讲清含义)

python 复制代码
# 导入类型注解工具,Optional表示参数可以是ListNode类型,也可以是None(空)
from typing import Optional

# 定义单链表节点类(LeetCode默认提供,这里再写一遍,方便查看)
class ListNode:
    def __init__(self, x):
        self.val = x  # 数据域:存储节点的值
        self.next = None  # 指针域:初始指向空,后续连接下一个节点

# 解题类(LeetCode要求的格式,必须叫Solution)
class Solution:
    # 定义解题函数,参数head是链表的头节点,返回值是入环点(ListNode类型)或None
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        # 1. 创建一个空的哈希集合,用来存储"已经访问过的节点"(存的是节点本身,不是节点值)
        # 为什么存节点?因为节点值可能重复(比如两个节点val都是2),但节点本身(地址)是唯一的
        visited = set()
        
        # 2. 遍历链表:只要当前节点head不是空,就继续遍历
        while head:
            # 3. 检查当前节点是否已经在集合里(是否访问过)
            if head in visited:
                # 若是,说明遇到了重复节点 → 这个节点就是入环点,直接返回
                return head
            # 4. 若没访问过,就把当前节点加入集合(做标记)
            visited.add(head)
            # 5. 移动到下一个节点:让head指向当前节点的next(下一个节点的地址)
            head = head.next
        
        # 6. 循环结束,说明遍历到了空节点 → 链表无环,返回None
        return None

4. Python代码运行过程+调用流程(逐步看)

(1)调用流程(怎么用这个函数)

要调用detectCycle函数,必须先"构造链表"(不管是题目给的示例,还是自定义输入),然后创建Solution对象,调用函数,最后输出结果。

举个例子(示例1):构造链表[3,2,0,-4],pos=1(尾节点连到索引1的节点),调用函数,输出入环点。

(2)运行过程(以示例1为例,逐步拆解)

步骤1:构造链表(手动创建节点,连接成环)

python 复制代码
# 构造示例1的链表
n1 = ListNode(3)  # 第一个节点,val=3,next初始为None
n2 = ListNode(2)  # 第二个节点,val=2,next初始为None
n3 = ListNode(0)  # 第三个节点,val=0,next初始为None
n4 = ListNode(-4) # 第四个节点,val=-4,next初始为None

# 连接节点,形成链表:3→2→0→-4
n1.next = n2
n2.next = n3
n3.next = n4
# 构造环:尾节点n4的next指向n2(pos=1),形成3→2→0→-4→2的环
n4.next = n2

# 创建Solution对象,调用detectCycle函数,传入头节点n1
s = Solution()
result = s.detectCycle(n1)
# 输出结果:入环点的val(n2的val是2)
print("入环点的val为:", result.val)  # 输出:入环点的val为:2

步骤2:detectCycle函数运行过程(逐句执行)

  1. 初始化visited = set()(空集合);

  2. 进入循环:head = n1(val=3),head不是空,执行循环体;

  3. 判断head(n1)是否在visited(空集合)→ 不在,执行visited.add(n1)(集合现在有n1);

  4. head = head.next → head变成n2(val=2);

  5. 再次循环:head(n2)不是空,判断是否在visited(只有n1)→ 不在,add(n2)(集合有n1、n2);

  6. head = n3(val=0),判断不在集合,add(n3);head变成n4;

  7. head(n4)不在集合,add(n4);head变成n2(因为n4.next = n2);

  8. 再次循环:head(n2),判断是否在集合(有n2)→ 是,返回n2(入环点);

  9. 函数结束,result = n2,输出result.val → 2。

5. C++完整代码(每句带注释,讲清含义)

cpp 复制代码
#include <iostream>  // 用于输入输出(测试主函数会用到)
#include <unordered_set>  // 导入哈希集合的头文件(必须有,否则用不了unordered_set)
using namespace std;  // 简化代码,不用写std::unordered_set、std::cout等

// 定义单链表节点结构体(LeetCode默认提供,再写一遍方便查看)
struct ListNode {
    int val;  // 数据域:存储节点的值
    ListNode *next;  // 指针域:指向ListNode类型的指针(存储下一个节点的地址)
    // 构造函数:创建节点时,给val赋值,next默认指向nullptr(空指针)
    ListNode(int x) : val(x), next(nullptr) {}
};

// 解题类(LeetCode要求的格式)
class Solution {
public:
    // 解题函数:参数head是链表头节点的指针,返回值是入环点的指针(或nullptr)
    ListNode *detectCycle(ListNode *head) {
        // 1. 创建哈希集合,存储"已经访问过的节点的地址"(ListNode*表示指针类型)
        unordered_set<ListNode*> visited;
        
        // 2. 遍历链表:只要head不是空指针,就继续遍历
        while (head != nullptr) {
            // 3. 检查当前节点(head指向的节点)是否在集合中
            // count(head):返回1表示存在,0表示不存在
            if (visited.count(head)) {
                // 存在 → 是入环点,返回当前节点的指针
                return head;
            }
            // 4. 不存在 → 把当前节点的地址加入集合(做标记)
            visited.insert(head);
            // 5. 移动到下一个节点:让head指向当前节点的next(下一个节点的地址)
            head = head->next;
        }
        
        // 6. 遍历到空指针 → 无环,返回nullptr
        return nullptr;
    }
};

6. C++代码运行过程+调用流程(逐步看)

(1)调用流程(构造链表→调用函数→输出结果)

C++和Python一样,需要先构造链表,再调用detectCycle函数,最后输出结果(注意C++中指针的输出方式,不能直接输出指针,要输出入环点的val)。

(2)运行过程(以示例1为例,逐步拆解)

步骤1:构造链表(手动创建节点,连接成环)

cpp 复制代码
int main() {
    // 1. 构造示例1的链表节点(用new创建,分配内存,返回节点指针)
    ListNode* n1 = new ListNode(3);  // n1是指针,指向val=3的节点
    ListNode* n2 = new ListNode(2);  // n2指向val=2的节点
    ListNode* n3 = new ListNode(0);  // n3指向val=0的节点
    ListNode* n4 = new ListNode(-4); // n4指向val=-4的节点
    
    // 2. 连接节点,形成链表:3→2→0→-4
    n1->next = n2;  // n1的next指针,指向n2(n2的地址)
    n2->next = n3;
    n3->next = n4;
    // 3. 构造环:n4的next指向n2(pos=1)
    n4->next = n2;
    
    // 4. 创建Solution对象,调用detectCycle函数,传入头节点n1
    Solution s;
    ListNode* result = s.detectCycle(n1);
    
    // 5. 输出结果:判断是否有环,有环则输出入环点的val,无环则输出提示
    if (result != nullptr) {
        cout << "入环点的val为:" << result->val << endl;  // 输出:2
    } else {
        cout << "链表无环,返回null" << endl;
    }
    
    // 注意:C++中new创建的节点,需要手动释放内存(避免内存泄漏)
    // 这里简单释放(实际解题中可忽略,LeetCode会自动处理)
    delete n1;
    delete n2;
    delete n3;
    delete n4;
    
    return 0;
}

步骤2:detectCycle函数运行过程(和Python逻辑完全一致)

  1. 初始化unordered_set<ListNode*> visited(空集合);

  2. head = n1(指向val=3的节点),head != nullptr,进入循环;

  3. visited.count(head) → 0(不存在),insert(head)(集合加入n1的地址);

  4. head = head->next → head指向n2;

  5. 重复步骤3-4,依次加入n2、n3、n4的地址,head最后指向n2;

  6. visited.count(head) → 1(n2的地址已在集合中),返回head(n2的指针);

  7. main函数中,result指向n2,输出result->val → 2。

四、解法二:快慢指针法(最优解,面试必考,O(1)空间)

1. 核心思路(通俗版,不用数学推导,记住规律即可)

用两个指针(慢指针slow、快指针fast),分两步解决问题,全程只用两个指针,不占用额外存储(空间O(1)):

第一步:判断链表是否有环(找快慢指针的相遇点)

  • 快慢指针同时从链表头(head)出发;

  • 慢指针(slow)每次走1步(slow = slow.next);

  • 快指针(fast)每次走2步(fast = fast.next.next);

  • 如果链表无环:快指针会先走到空节点(fast或fast.next是null),循环结束,返回null;

  • 如果链表有环:快慢指针一定会在环内相遇(就像环形跑道上,快的追上慢的)。

第二步:找到入环点(关键规律,记住就好)

  • 当快慢指针相遇后,把慢指针放回链表头(head)

  • 然后,让快慢指针都每次走1步(不再是1步和2步);

  • 当两个指针再次相遇时,这个相遇的节点,就是入环点

补充:为什么这个规律成立?(不用推导,记结论即可)

简单通俗理解:假设入环点到相遇点的距离是k,链表头到入环点的距离是m,环的长度是L。快慢指针相遇时,快指针走的路程是慢指针的2倍,推导后会发现:m = L - k,所以当慢指针从head走m步,快指针从相遇点走m步(每次1步),都会走到入环点,刚好相遇。

2. 优缺点

✅ 优点:空间复杂度O(1)(只用两个指针),时间复杂度O(n),满足题目进阶要求,是面试高频考点;

❌ 缺点:需要记住规律,逻辑比哈希法稍复杂一点。

3. Python完整代码(每句带注释,讲清含义)

python 复制代码
from typing import Optional

# 定义单链表节点类(LeetCode默认提供)
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        # 1. 初始化快慢指针,都指向链表头节点(刚开始都在起点)
        slow = head  # 慢指针:每次走1步
        fast = head  # 快指针:每次走2步
        
        # 2. 第一步:找快慢指针的相遇点,判断是否有环
        # 循环条件:fast和fast.next都不能是空(否则快指针走到头了,无环)
        while fast and fast.next:
            # 慢指针走1步:指向当前节点的下一个节点
            slow = slow.next
            # 快指针走2步:先指向当前节点的下一个,再指向那个节点的下一个
            fast = fast.next.next
            
            # 3. 快慢指针相遇 → 链表有环,进入第二步找入环点
            if slow == fast:
                # 第二步:把慢指针放回链表头
                slow = head
                # 让快慢指针都每次走1步,直到再次相遇
                while slow != fast:
                    slow = slow.next
                    fast = fast.next
                # 再次相遇的节点 → 入环点,返回
                return slow
        
        # 4. 循环结束 → 快指针走到空,无环,返回None
        return None

4. Python代码运行过程+调用流程(以示例1为例)

(1)调用流程(和哈希法一致,先构造链表,再调用函数)

python 复制代码
# 构造示例1的链表(和哈希法一样)
n1 = ListNode(3)
n2 = ListNode(2)
n3 = ListNode(0)
n4 = ListNode(-4)
n1.next = n2
n2.next = n3
n3.next = n4
n4.next = n2  # 构造环

# 调用函数
s = Solution()
result = s.detectCycle(n1)
print("入环点的val为:", result.val)  # 输出:2

(2)运行过程(逐步拆解,重点看两步流程)

步骤1:初始化slow = n1,fast = n1(都指向val=3的节点);

步骤2:进入第一步循环(找相遇点):

  1. 第一次循环:fast(n1)和fast.next(n2)都非空;

  2. slow = n1.next → slow = n2(val=2);

  3. fast = n1.next.next → fast = n2.next → fast = n3(val=0);

  4. slow(n2) != fast(n3),继续循环;

  5. 第二次循环:fast(n3)和fast.next(n4)非空;

  6. slow = n2.next → n3(val=0);

  7. fast = n3.next.next → n4.next → n2(val=2);

  8. slow(n3) != fast(n2),继续循环;

  9. 第三次循环:fast(n2)和fast.next(n3)非空;

  10. slow = n3.next → n4(val=-4);

  11. fast = n2.next.next → n3.next → n4(val=-4);

  12. slow(n4) == fast(n4)→ 相遇,进入第二步;

步骤3:第二步(找入环点):

  1. slow = head → slow = n1(val=3);

  2. 进入循环:slow(n1) != fast(n4),执行:

  3. slow = n1.next → n2(val=2);

  4. fast = n4.next → n2(val=2);

  5. slow(n2) == fast(n2)→ 再次相遇,返回n2(入环点);

步骤4:输出result.val → 2,完成。

5. C++完整代码(每句带注释,讲清含义)

cpp 复制代码
#include <iostream>
using namespace std;

// 定义单链表节点结构体
struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(nullptr) {}
};

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        // 1. 初始化快慢指针,都指向头节点
        ListNode* slow = head;  // 慢指针,每次走1步
        ListNode* fast = head;  // 快指针,每次走2步
        
        // 2. 第一步:找相遇点,判断是否有环
        // 循环条件:fast和fast->next都不能是空(否则无环)
        while (fast != nullptr && fast->next != nullptr) {
            // 慢指针走1步
            slow = slow->next;
            // 快指针走2步
            fast = fast->next->next;
            
            // 3. 快慢指针相遇 → 有环,进入第二步找入环点
            if (slow == fast) {
                // 慢指针放回链表头
                slow = head;
                // 快慢指针都走1步,直到再次相遇
                while (slow != fast) {
                    slow = slow->next;
                    fast = fast->next;
                }
                // 再次相遇 → 入环点,返回
                return slow;
            }
        }
        
        // 4. 无环,返回空指针
        return nullptr;
    }
};

6. C++代码运行过程+调用流程(以示例1为例)

(1)调用流程(构造链表→调用函数→输出结果)

cpp 复制代码
int main() {
    // 构造示例1的链表
    ListNode* n1 = new ListNode(3);
    ListNode* n2 = new ListNode(2);
    ListNode* n3 = new ListNode(0);
    ListNode* n4 = new ListNode(-4);
    
    n1->next = n2;
    n2->next = n3;
    n3->next = n4;
    n4->next = n2;  // 构造环
    
    // 调用函数
    Solution s;
    ListNode* result = s.detectCycle(n1);
    
    // 输出结果
    if (result != nullptr) {
        cout << "入环点的val为:" << result->val << endl;  // 输出:2
    } else {
        cout << "链表无环,返回null" << endl;
    }
    
    // 释放内存
    delete n1;
    delete n2;
    delete n3;
    delete n4;
    
    return 0;
}

(2)运行过程(和Python完全一致)

第一步:快慢指针从n1出发,slow走1步、fast走2步,直到在n4相遇;

第二步:slow放回n1,快慢指针都走1步,第一次循环后,slow到n2、fast到n2,相遇,返回n2;

main函数输出result->val → 2。

五、完整测试主函数(含题目示例+自定义输入,可直接运行)

不管是哈希法还是快慢指针法,测试主函数都可以直接使用,下面分别给出Python和C++的完整测试代码(包含题目3个示例+3个自定义案例),可以直接复制运行,观察结果。

1. Python完整测试代码(含所有案例)

python 复制代码
from typing import Optional

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

# 解法1:哈希集合法(可替换成解法2的函数)
class Solution:
    def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]:
        visited = set()
        while head:
            if head in visited:
                return head
            visited.add(head)
            head = head.next
        return None

# 测试主函数:包含6个案例(3个题目示例+3个自定义)
def test_detectCycle():
    # 案例1:题目示例1 → 有环,入环点val=2
    print("=== 案例1:题目示例1 ===")
    n1 = ListNode(3)
    n2 = ListNode(2)
    n3 = ListNode(0)
    n4 = ListNode(-4)
    n1.next = n2
    n2.next = n3
    n3.next = n4
    n4.next = n2
    s = Solution()
    res1 = s.detectCycle(n1)
    print("输入:head = [3,2,0,-4], pos = 1")
    print("输出:入环点val =", res1.val if res1 else "null")  # 预期输出:2
    
    # 案例2:题目示例2 → 有环,入环点val=1
    print("\n=== 案例2:题目示例2 ===")
    m1 = ListNode(1)
    m2 = ListNode(2)
    m1.next = m2
    m2.next = m1
    res2 = s.detectCycle(m1)
    print("输入:head = [1,2], pos = 0")
    print("输出:入环点val =", res2.val if res2 else "null")  # 预期输出:1
    
    # 案例3:题目示例3 → 无环,返回null
    print("\n=== 案例3:题目示例3 ===")
    k1 = ListNode(1)
    res3 = s.detectCycle(k1)
    print("输入:head = [1], pos = -1")
    print("输出:", res3 if res3 else "null")  # 预期输出:null
    
    # 自定义案例4:空链表 → 无环,返回null
    print("\n=== 自定义案例4:空链表 ===")
    res4 = s.detectCycle(None)
    print("输入:head = null")
    print("输出:", res4 if res4 else "null")  # 预期输出:null
    
    # 自定义案例5:只有2个节点,环在自身 → 入环点val=5
    print("\n=== 自定义案例5:环在自身 ===")
    p1 = ListNode(5)
    p1.next = p1  # 自己指向自己,形成环
    res5 = s.detectCycle(p1)
    print("输入:head = [5], pos = 0(自身成环)")
    print("输出:入环点val =", res5.val if res5 else "null")  # 预期输出:5
    
    # 自定义案例6:3个节点,入环点在中间 → 入环点val=7
    print("\n=== 自定义案例6:入环点在中间 ===")
    q1 = ListNode(6)
    q2 = ListNode(7)
    q3 = ListNode(8)
    q1.next = q2
    q2.next = q3
    q3.next = q2
    res6 = s.detectCycle(q1)
    print("输入:head = [6,7,8], pos = 1")
    print("输出:入环点val =", res6.val if res6 else "null")  # 预期输出:7

# 调用测试函数,执行所有案例
test_detectCycle()

运行结果:所有案例都会输出预期结果,可以复制到Python编辑器(如PyCharm、IDLE)直接运行,观察每一步输出。

2. C++完整测试代码(含所有案例)

cpp 复制代码
#include <iostream>
#include <unordered_set>
using namespace std;

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(nullptr) {}
};

// 解法1:哈希集合法(可替换成解法2的函数)
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        unordered_set<ListNode*> visited;
        while (head != nullptr) {
            if (visited.count(head)) {
                return head;
            }
            visited.insert(head);
            head = head->next;
        }
        return nullptr;
    }
};

// 测试主函数
int main() {
    Solution s;
    ListNode* res = nullptr;
    
    // 案例1:题目示例1 → 入环点val=2
    cout << "=== 案例1:题目示例1 ===" << endl;
    ListNode* n1 = new ListNode(3);
    ListNode* n2 = new ListNode(2);
    ListNode* n3 = new ListNode(0);
    ListNode* n4 = new ListNode(-4);
    n1->next = n2;
    n2->next = n3;
    n3->next = n4;
    n4->next = n2;
    res = s.detectCycle(n1);
    cout << "输入:head = [3,2,0,-4], pos = 1" << endl;
    cout << "输出:入环点val = " << (res ? to_string(res->val) : "null") << endl;
    // 释放内存
    delete n1; delete n2; delete n3; delete n4;
    
    // 案例2:题目示例2 → 入环点val=1
    cout << "\n=== 案例2:题目示例2 ===" << endl;
    ListNode* m1 = new ListNode(1);
    ListNode* m2 = new ListNode(2);
    m1->next = m2;
    m2->next = m1;
    res = s.detectCycle(m1);
    cout << "输入:head = [1,2], pos = 0" << endl;
    cout << "输出:入环点val = " << (res ? to_string(res->val) : "null") << endl;
    delete m1; delete m2;
    
    // 案例3:题目示例3 → 无环
    cout << "\n=== 案例3:题目示例3 ===" << endl;
    ListNode* k1 = new ListNode(1);
    res = s.detectCycle(k1);
    cout << "输入:head = [1], pos = -1" << endl;
    cout << "输出:" << (res ? to_string(res->val) : "null") << endl;
    delete k1;
    
    // 自定义案例4:空链表
    cout << "\n=== 自定义案例4:空链表 ===" << endl;
    res = s.detectCycle(nullptr);
    cout << "输入:head = null" << endl;
    cout << "输出:" << (res ? to_string(res->val) : "null") << endl;
    
    // 自定义案例5:自身成环
    cout << "\n=== 自定义案例5:环在自身 ===" << endl;
    ListNode* p1 = new ListNode(5);
    p1->next = p1;
    res = s.detectCycle(p1);
    cout << "输入:head = [5], pos = 0(自身成环)" << endl;
    cout << "输出:入环点val = " << (res ? to_string(res->val) : "null") << endl;
    delete p1;
    
    // 自定义案例6:入环点在中间
    cout << "\n=== 自定义案例6:入环点在中间 ===" << endl;
    ListNode* q1 = new ListNode(6);
    ListNode* q2 = new ListNode(7);
    ListNode* q3 = new ListNode(8);
    q1->next = q2;
    q2->next = q3;
    q3->next = q2;
    res = s.detectCycle(q1);
    cout << "输入:head = [6,7,8], pos = 1" << endl;
    cout << "输出:入环点val = " << (res ? to_string(res->val) : "null") << endl;
    delete q1; delete q2; delete q3;
    
    return 0;
}

运行结果:和Python一致,所有案例输出预期结果,可复制到C++编辑器(如Dev-C++、VS)直接运行。

六、总结(必看)

  1. 先掌握前置知识:单链表结构、节点定义、指针、哈希集合、快慢指针,这是看懂题解的基础;

  2. 新手先学哈希法:逻辑简单,不用记规律,适合快速上手,理解"用集合标记已访问节点"的思路;

  3. 面试必学快慢指针法:记住两步走(找相遇点→找入环点),O(1)空间,是最优解;

  4. 代码一定要自己运行一遍:把测试主函数复制到编辑器,运行后观察结果,理解每一步的执行过程,才能真正学会;

  5. 核心口诀(记牢):快慢相遇证有环,慢针回头同步走,再次相遇是入环!

相关推荐
壹号用户1 小时前
用队列实现栈
数据结构·算法
做人求其滴1 小时前
面试经典 150 题 380 274
c++·算法·面试·职场和发展·力扣
daad7772 小时前
记一组无人机IMU传感器数据
算法
计算机安禾2 小时前
【c++面向对象编程】第42篇:模板特化与偏特化:为特定类型定制实现
开发语言·c++·算法
小O的算法实验室2 小时前
2026年KBS,流形感知强化学习差分进化算法+不规则3D无人机路径规划,深度解析+性能实测
算法·智能算法·智能算法改进
玖釉-2 小时前
C++ 中的循环语句详解:while、do...while、for、嵌套循环与循环控制
开发语言·c++·算法
不做无法实现的梦~2 小时前
运动控制系统复习一览-----常考题目总结版本
算法
小短腿的代码世界2 小时前
信号路由风暴:Qt算法交易系统的高频信号分发架构
qt·算法·架构
阿文的代码库2 小时前
一文读懂GROUP BY 1,2 VS GROUP BY column_1, column_2 的区别
算法