例题描述
给定一个链表,返回链表开始入环的第一个结点。 如果链表无环,则返回 NULL
。
为了表示给定链表中的环,我们使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0
开始)。 如果 pos 是 -1
,则在该链表中没有环。
【说明:不允许修改给定的链表】
示例 1:
- 输入:
head = [3,2,0,-4], pos = 1
- 输出:
tail connects to node index 1
- 解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
- 输入:
head = [1,2], pos = 0
- 输出:
tail connects to node index 0
- 解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
- 输入:
head = [1], pos = -1
- 输出:
no cycle
- 解释:链表中没有环。
解法一
哈希思想,使用STL
提供的unordered_set
进行节点地址去重,遍历链表,每次都在set中查询有没有添加过该节点(的地址),如果有,说明是入环点。如果遍历完都没有发现该入环点,即该链表无环,返回空。
代码实现
ListNode* EntryNodeOfLoop(ListNode* pHead) {
while (pHead) {
if (us.find(pHead) == us.end()) {
us.insert(pHead);
pHead = pHead->next;
} else return pHead;
}
return nullptr;
}
此解法弊端在于需要额外创建空间存储节点,空间复杂度O(N)
。
那么下面这种解法即可优化至O(1)
。
解法二
使用快慢指针法,找到相遇点。另外创建2指针,一指针从入口出发,一指针从相遇点出发,两指针相遇点就是入口点。
这其实是一个数学问题:
假设此带环链表抽象成为一个线性结构。题设如下:
l
为从头结点到相遇点的距离
c
为从入环点到相遇点的距离
n
为某个常数,仅代表圈数。(n >= 1
)
- 快指针直到相遇共走过的路程为:
l+c+(n*r)
。(n代表圈数,大小由步距决定)
慢指针直到相遇共走过的路程为:l+c
。 - 慢指针入环之前,快指针进入环内空转等待,有可能转满一圈(环很大,结点很多)。也有可能会转很多圈(环小),影响不大。
- 相遇时,因为步距不同,说明走过的距离并不相等,而是二倍关系,即
l+c+(n*r) =2(l+c)
,化简后为l=(n-1)*r+(r-c)
。
(此时慢指针不可能跑满一整个环,那必定是只存在一个环周期的计算) (n-1)*r
项可以忽略,因为在圈内转的圈数没有实际意义,重点还是在于相遇点。所以当一指针从相遇点开始,一指针再次从首结点开始,共同步进,步距相同时,一定会在入环点相遇,因为环外点要走r-c
个单位才可以到达入环点,环内点不可能会路过入环点再走一圈,所以环内点走的距离也是r-c
,二者刚好在入环点相遇。
代码实现一
ListNode *detectCycle(ListNode *head) {
if(!head){
return NULL;
}
ListNode *p,*q;
p = q = head;
int flag = 0,c = 0;
while(p && p->next){
p = p->next->next;
q = q->next;
c++;
if(p == q){
flag = 1;
break;
}
}
if(!flag){
return NULL;
}
p = head;
while(c--){
while(p != q){
p = p->next;
q = q->next;
}
}
return p;
}
代码实现二
ListNode *detectCycle2(ListNode *head) {
if (head == NULL) {
return NULL;
}
struct ListNode *fast = head;
struct ListNode *slow = head;
while (1) {
fast = fast->next;
if (fast == NULL) {
return NULL; // return空代表无环,不存在的情况
}
fast = fast->next;
if (fast == NULL) {
return NULL;
}
slow = slow->next;
if (fast == slow) {
break;
}
}
struct ListNode *n1 = head;
struct ListNode *n2 = slow;
while (n1 != n2) {
n1 = n1->next;
n2 = n2->next;
}
return n1;
}