环形链表
LeetCode题号:141,142
所属类型:链表
题目
说明
问题一:给你一个链表的头节点head,判断链表中是否有环。
问题二:给定一个链表的头节点head,返回链表开始入环的第一个节点。如果链表无环,则返回null。
题解
- 链表节点
struct ListNode
{
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
问题一
class Solution
{
public:
bool hasCycle(ListNode *head)
{
if (nullptr == head)
return false;
ListNode* pFast = head;
ListNode* pSlow = head;
while (nullptr != pFast && nullptr != pFast->next)
{
pFast = pFast->next->next;
pSlow = pSlow->next;
if (pFast == pSlow)
return true;
}
return false;
}
};
- 分析:
采用快慢指针的方式,快指针每次移动两步,慢指针每次移动一步,若两个指针能相遇,则表示链表是环形链表。
为什么环形链表两个指针一定能相遇?因为快指针每次移动两步,慢指针每次移动一步,这样快指针每次超过慢指针一个节点,当一个一个节点超过的时候,总能遇到,不会错过(非环形链表pFast会先走到最后一个节点,也就是next为nullptr或者 nullptr结束循环)
问题二
class Solution
{
public:
ListNode *detectCycle(ListNode *head)
{
if (nullptr == head)
return nullptr;
ListNode* pFast = head; // 快指针
ListNode* pSlow = head; // 慢指针
while (nullptr != pFast && nullptr != pFast->next)
{
pFast = pFast->next->next;
pSlow = pSlow->next;
// 快慢指针能相遇,那么说明是环形链表
if (pFast == pSlow)
{
ListNode* pNodeA = pFast; // 相遇节点
ListNode* pNodeB = head; // 头结点
while (pNodeA != pNodeB)
{
pNodeA = pNodeA->next;
pNodeB = pNodeB->next;
}
return pNodeA;
}
}
return nullptr;
}
};
- 分析:
判断链表是否为环形的逻辑如上所示,此时我们来定义几个节点名称 - 头结点 H(Head)
- 环形链表入环第一个节点 C(Circle)
- 快慢指针相遇节点 M(Meet)
再定义几个长度(每次一个节点):
H->C:需要走x次
C->M:需要走y次
M->C:需要走z次
当快慢指针相遇时:2(x+y) = x+y+n(y+z) (备注:n可能为1,2,3…)
快指针行程:x+y+n(y+z)
慢针行程:x+y
由上式继续推导:
x+y = n(y+z)
x = (n-1)(y+z) + z
当 n = 1 时,从H->C 和 从M->C相等
当 n = 2 时,从H->C 和 从M->C再加1圈相等
当 n = 3 时,从H->C 和 从M->C再加2圈相等
…
所以,当一个指针从头节点出发,另外一个指针从相遇节点出发,它们总能在入口节点相遇。
if (pFast == pSlow)
{
ListNode* pNodeA = pFast; // 相遇节点
ListNode* pNodeB = head; // 头结点
while (pNodeA != pNodeB)
{
pNodeA = pNodeA->next;
pNodeB = pNodeB->next;
}
return pNodeA;
}