链表成环问题

141. 环形链表

>>>>>>>>>>> LeetCode原题传送 >>>>>>>>>>>>
给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。
如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

如果链表中存在环,则返回 true 。 否则,返回 false 。

示例:

3
2
0
4

输入: head = [3, 2, 0, 4], pos = 1
输出:true
解释:链表成环

1

输入: head = [1], pos = -1
输出:false
解释:链表中没有环

提示:

  1. 链表中节点的数目范围是 [0, 10 ^ 4]
  2. -105 <= Node.val <= 105
  3. pos 为 -1 或者链表中的一个 有效索引 。

思路:
本题首先想到的便是基于双指针的解法,设置快慢指针fast、slow,初始时均指向head头结点,快指针每次走两步,慢指针每次前进一步,链表中若存在环,那么必然在进入环之后开启了指针赛跑模式,即:快指针追赶慢指针,总有一个时刻两个指针碰上(指向同一个节点),反之,指针不可能相遇。
具体请看下图:
指针移动过程
链表节点定义:

struct ListNode {
	int val;//节点值域
	ListNode* next;//指向节点的指针
	ListNode(int x) : val(x), next(NULL) {}//有参构造函数,初始化节点值为 x ,next指向nullptr
};

代码:

class Solution {
public:
	bool hasCycle(ListNode* head) {
		if (head == nullptr || head->next == nullptr)//没有或只一个节点
			return false;
		ListNode* fast = head, * slow = head;//初始时快慢指针均指向头结点
		while (fast != nullptr && fast->next != nullptr) {
			fast = fast->next->next;
			slow = slow->next;
			if (fast == slow)
				return true;
		}
		return false;
	}
};
  • 时间复杂度:O(N)
    链表不存在环时,快指针率先到达链表末端。
    链表有环时,快指针比慢指针每次多走一步,初始距离为环的长度,最多进行N轮。(N为链表节点的个数)

  • 空间复杂度:O(1)
    只使用了两个节点指针。

142. 环形链表 II

>>>>>>>>>>> LeetCode原题传送 >>>>>>>>>>>>
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
说明:不允许修改给定的链表。

示例:

3
2
0
4

输入: head = [3, 2, 0, 4], pos = 1
输出:索引为1的链表节点(ListNode
*)
解释:链表成环, 尾部链接到第二个节点。

1

输入: head = [1], pos = -1
输出:返回null
解释:链表中没有环

提示:

  1. 链表中节点的数目范围在范围 [0, 10 ^ 4] 内
  2. -105 <= Node.val <= 105
  3. pos 的值为 -1 或者链表中的一个有效索引

**1. 第一种情况:链表无环,同141题,快指针走到nullptr或下一节点为null。
在这里插入图片描述
2. 第二种情况:链表有环,我们如何寻找环入口,一种简单的方案是使用哈希表unordered_set,记录每一次遇到的节点,第一次出现重复的节点即为环的入口。
代码:

class Solution {
public:
	ListNode* detectCycle(ListNode* head) {
		if (head == nullptr || head->next == nullptr)
			return nullptr;
		ListNode* curr = head;
		unordered_set<ListNode*> set;
		while (curr != nullptr) {
			if (set.find(curr) == set.end()) {
				set.insert(curr);
				curr = curr->next;
			}
			else return curr;
		}
		return nullptr;
	}
};

这里我们着重介绍的是双指针法:
设头结点到环入口(不包括入口节点)需要走 a 步(步长为1),环的长度为 b;
若使用快慢指针,设快指针所走路径和为f,慢指针所走路径和为s,我们有如下等式:
① f = 2s 快指针fast走的步数是慢指针的两倍
② f = s + nb fast 比 slow 多走了n个环的长度,
双指针都走 a 步进入到环内,然后一直绕圈直到重合,那么重合时 fast 一定比 slow 多走环长度的整数倍n
②式来由(此为特殊情况:慢指针没绕完一圈便相遇):
fast快指针所走的长度为 f = a + nb + m;
slow慢指针所走的路径长度为 s = a + m;
两式相减得 f = s + nb;

①②化简得 :f = 2nb , s = nb;快慢指针分别走了 2n 、 n 个环的长度;
走到入口节点的位置为 head + a,也就是head后走 a 步就到达了入口,进入环之后每走一圈又会回到圈入口,第一次相遇后的慢指针已经走了 nb 步 ,那么此时再走 a 步即可抵达入口,这里使用辅助指针fast从head开始走,步长为1,那么走到环入口时 fast 将与 slow 相遇。

代码如下:

class Solution {
public:
	ListNode* detectCycle(ListNode* head) {
		if (head == nullptr || head->next == nullptr)
			return nullptr;
		ListNode* fast = head, * slow = head;
		while (fast != nullptr && fast->next != nullptr) {
			fast = fast->next->next;
			slow = slow->next;
			if (fast == slow) {//第一次相遇
				fast = head;//将fast指针放到head处并设置步长为1前进
				while (fast != slow) {
					fast = fast->next;
					slow = slow->next;
				}
				//再次相遇即为环入口
				return fast;
			}
		}
		return nullptr;
	}
};
  • 时间复杂度:O(N)
    指针所走步数与环的大小相关

  • 空间复杂度:O(1)
    使用了常数级空间:两个指针

<think>嗯,用户想了解链表的概念以及检测和处理方法。首先,我需要回忆链表的基本定义。链表指的是链表的某个节点的next指针指向了链表中之前的一个节点,导致链表一个闭,无法正常到达末尾的null节点。这点我记得没错,但需要确认是否有更准确的说法。 接下来是检测方法。常见的检测方法有哈希表法和快慢指针法。哈希表法的思路是遍历链表,记录每个访问过的节点,如果遇到重复的节点就说明存在。这种方法的时间复杂度是O(n),但空间复杂度也是O(n),因为需要存储所有节点。而快慢指针法则用两个指针,一个每次走两步,一个每次走一步,如果存在,它们最终会相遇。这种方法的空间复杂度是O(1),更高效。需要详细解释这两种方法的步骤,并指出它们的优缺点。 用户可能还关心如何处理,比如如何找到的入口节点。根据快慢指针相遇后,将其中一个指针移回链表头,然后两个指针以相同速度前进,再次相遇的节点就是的入口。这部分需要结合数学证明,可能用户需要了解推导过程,但可能不需要太深入,所以简要说明即可。 另外,用户提供的引用中有链表II的问题,说明可能涉及到LeetCode题目,需要结合实际问题举例。例如,引用[2]提到的链表II需要找到的入口,这可能要详细说明算法步骤。 还要考虑代码实现的问题。用户提供的引用[3]和[5]中有链表节点的定义和交换节点的例子,可能需要给出检测的代码示例,比如快慢指针的实现,以及找到入口的代码。 需要检查是否覆盖了所有用户的问题:定义、检测方法、处理步骤。是否有遗漏?比如,如何处理存在链表,是否需要断开?或者应用场景是什么?但用户的问题主要集中在检测,可能不需要深入处理后的操作,但简要提及可能有帮助。 另外,引用[1]提到链表用于集合表示,这可能与问题无关,但引用[5]中的节点交换步骤可能相关,但不确定。可能需要忽略无关引用,专注于与相关的引用。 最后,生相关问题时要围绕链表,例如检测方法的时间复杂度、如何找到的长度、实际应用中的处理等。确保问题有逻辑关联,帮助用户进一步学习。</think>### 链表的定义及检测方法 #### 1. 链表的定义 链表是指链表中某个节点的`next`指针指向链表中**其之前的某个节点**,导致链表遍历时无法到达末尾的`null`,形一个闭合循。例如: $$A \rightarrow B \rightarrow C \rightarrow D \rightarrow B$$ 此时节点`D`的`next`指针指向`B`,形`B→C→D→B`[^2]。 --- #### 2. 检测链表的方法 ##### (1) 哈希表法 - **步骤**:遍历链表,将访问过的节点存入哈希表。若遇到节点已存在于表中,则存在。 - **时间复杂度**:$O(n)$ - **空间复杂度**:$O(n)$(需存储所有节点) - **代码示例**(Python): ```python def hasCycle(head): visited = set() while head: if head in visited: return True visited.add(head) head = head.next return False ``` ##### (2) 快慢指针法(Floyd判圈算法) - **步骤**: 1. 快指针每次移动两步,慢指针每次移动一步。 2. 若两指针相遇,则存在;若快指针遇到`null`,则无。 - **数学原理**:假设外有$a$个节点,内有$b$个节点。相遇时,慢指针走了$s$步,快指针走了$2s$步。此时$2s - s = nb$($n$为整数),即$s = nb$。进一步可推导出入口的位置[^2]。 - **时间复杂度**:$O(n)$ - **空间复杂度**:$O(1)$ - **代码示例**(Python): ```python def hasCycle(head): slow = fast = head while fast and fast.next: slow = slow.next fast = fast.next.next if slow == fast: return True return False ``` --- #### 3. 处理链表中的 若需要**找到的入口节点**(如LeetCode 142题): 1. 使用快慢指针确认存在。 2. 将其中一个指针重置到链表头,两指针以相同速度移动。 3. 再次相遇的节点即为入口。 ```python def detectCycle(head): slow = fast = head while fast and fast.next: slow = slow.next fast = fast.next.next if slow == fast: slow = head # 重置慢指针 while slow != fast: slow = slow.next fast = fast.next return slow return None ``` --- #### 4. 应用场景 - **内存管理**:检测循引用。 - **图算法**:判断图中是否存在路(如DFS遍历时标记访问状态)。 - **数学问题**:如判断伪随机数序列的周期性[^1]。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nepu_bin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值