链表成环问题

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)
    使用了常数级空间:两个指针

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

nepu_bin

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

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

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

打赏作者

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

抵扣说明:

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

余额充值