一、前置知识
在做这道题之前,必须先掌握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. 核心思路(能懂的通俗版)
我们遍历链表的每一个节点,把"已经访问过的节点"存到哈希集合里,就像我们走路时在走过的地方做标记:
-
从链表头(head)开始,每次访问一个节点;
-
先检查这个节点"有没有做过标记"(也就是是否在哈希集合里);
-
如果有标记 → 说明我们之前已经走过这个节点了,这个节点就是"入环点"(因为只有环,才会走到重复的节点);
-
如果没有标记 → 给这个节点做标记(加入哈希集合),继续往前走(访问下一个节点);
-
如果走到了"空节点"(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函数运行过程(逐句执行)
-
初始化visited = set()(空集合);
-
进入循环:head = n1(val=3),head不是空,执行循环体;
-
判断head(n1)是否在visited(空集合)→ 不在,执行visited.add(n1)(集合现在有n1);
-
head = head.next → head变成n2(val=2);
-
再次循环:head(n2)不是空,判断是否在visited(只有n1)→ 不在,add(n2)(集合有n1、n2);
-
head = n3(val=0),判断不在集合,add(n3);head变成n4;
-
head(n4)不在集合,add(n4);head变成n2(因为n4.next = n2);
-
再次循环:head(n2),判断是否在集合(有n2)→ 是,返回n2(入环点);
-
函数结束,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逻辑完全一致)
-
初始化unordered_set<ListNode*> visited(空集合);
-
head = n1(指向val=3的节点),head != nullptr,进入循环;
-
visited.count(head) → 0(不存在),insert(head)(集合加入n1的地址);
-
head = head->next → head指向n2;
-
重复步骤3-4,依次加入n2、n3、n4的地址,head最后指向n2;
-
visited.count(head) → 1(n2的地址已在集合中),返回head(n2的指针);
-
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:进入第一步循环(找相遇点):
-
第一次循环:fast(n1)和fast.next(n2)都非空;
-
slow = n1.next → slow = n2(val=2);
-
fast = n1.next.next → fast = n2.next → fast = n3(val=0);
-
slow(n2) != fast(n3),继续循环;
-
第二次循环:fast(n3)和fast.next(n4)非空;
-
slow = n2.next → n3(val=0);
-
fast = n3.next.next → n4.next → n2(val=2);
-
slow(n3) != fast(n2),继续循环;
-
第三次循环:fast(n2)和fast.next(n3)非空;
-
slow = n3.next → n4(val=-4);
-
fast = n2.next.next → n3.next → n4(val=-4);
-
slow(n4) == fast(n4)→ 相遇,进入第二步;
步骤3:第二步(找入环点):
-
slow = head → slow = n1(val=3);
-
进入循环:slow(n1) != fast(n4),执行:
-
slow = n1.next → n2(val=2);
-
fast = n4.next → n2(val=2);
-
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)直接运行。
六、总结(必看)
-
先掌握前置知识:单链表结构、节点定义、指针、哈希集合、快慢指针,这是看懂题解的基础;
-
新手先学哈希法:逻辑简单,不用记规律,适合快速上手,理解"用集合标记已访问节点"的思路;
-
面试必学快慢指针法:记住两步走(找相遇点→找入环点),O(1)空间,是最优解;
-
代码一定要自己运行一遍:把测试主函数复制到编辑器,运行后观察结果,理解每一步的执行过程,才能真正学会;
-
核心口诀(记牢):快慢相遇证有环,慢针回头同步走,再次相遇是入环!