检测链表中是否存在环
例:给定一个链表,判断链表中是否有环。为了表示给定链表中的环,我们使用整数pos来表示链表尾连接到链表中的位置(索引从0开始)。如果pos是-1,则在该链表中没有环。
例1:
——输入:head=[3,2,0,-4],pos=1
------输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
例2:
——输入:head=[1,2],pos=0
------输出:true
解释:链表中有一个环,其尾部连接到第一个节点。
例3:
——输入:head=[1],pos=-1
------输出:false
解释:链表中没有环。
定义链表
struct ListNode
{
int val;
ListNode* next;
ListNdoe(int x):val(x),next(NULL){
}
}
解法一:使用set容器属性,由于该容器中不包含重复元素,可以根据容器的size大小进行判断,这是该解法的主要思想。
代码:
bool hasCycle(ListNode* head)
{
if (head == NULL)
return false;
set<ListNode*> s;/*set里面不会存在重复元素,元素唯一*/
/*若有环,就有重复元素插入到set中,则插入set后的尺寸大小不变*/
int SetSize = 0;
ListNode* dummy = new ListNode(-1);
dummy = head;
while (dummy != NULL)
{
s.insert(dummy);
if (SetSize==s.size())/*上一次的size和本次的size大小一样,说明存在相同元素,则该链表有环*/
{
return true;
}
dummy = dummy->next;
SetSize = s.size();
}
return false;
}
解法二:使用双指针,fast 快指针和 slow 慢指针。假设链表没有环,则快指针到链表的末尾时,必存在 fast==NULL 或 fast->next‘==NULL。
详细分析:当链表没有环时,fast走到末尾,存在NULL,注意此刻整个链表已经遍历完成。那么此时返回false,反之返回true.
见代码
bool hasCycle(ListNode *head) {
if (head == NULL)
return false;
ListNode* dummy = new ListNode(-1);
dummy = head;
ListNode* fast = dummy->next;
ListNode* slow = dummy;
while(fast!=slow)
{
if(fast==NULL||fast->next==NULL)
return false;
fast = fast->next->next;
slow = slow->next;
}
return true;
}
鉴于以上两种方法可以检测链表中是否存在环,其实方法还有很多,例如给链表置空,给链表设置一个标志等方法。
问题进阶
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 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
解释:链表中没有环。
类似于链表的这种问题,一般都是换汤不换药,接着刚刚的方法往下。
基于解法一
在解法一中我们已经求出链表中是否存在环,进阶中的问题最大的不一样就是,需要返回链表,这个好办,详细看代码:
ListNode *detectCycle(ListNode *head) {
if(head==NULL)
return NULL;
ListNode* dummy = new ListNode(-1);
dummy = head;
/*set容器*/
set<ListNode*> s;
int size=0;
while(dummy!=NULL)
{
s.insert(dummy);
if(size==s.size())
{
return dummy;/*有环*//*返回的是环的入口*/
}
dummy=dummy->next;
size=s.size();
}
return NULL;/*没有环返回null*/
基于解法二:
先分析一波:
同样在上面解法二的基础上,由于快指针 fast 和慢指针 slow 相遇,则说明该链表中存在环,那么怎么判断呢?因为此时两个指针可能是在环内相遇,从解法二中可以看出,fast 的步长是 slow 的步长速度两倍,那么,fast绕链表一圈,等与slow绕链表半圈,假设链表头节点到环的入口距离即非环的部分长度是x,从环起点到相遇点的长度是y,环长是c,
那么:慢指针走过的长度= x + (n1 * c)+ y,由于快指针是慢指针的两倍,则快指针走过的长度 = 2*(x + (n1 * c)+ y)。
快指针比慢指针多走的路程必定是环长的整数倍。根据上面的两个关系式可得:
2*(x + (n1 * c)+ y)- (x + (n1 * c)+ y)=x+n1c+y=n2c;
有x+y=(n2-n1)*c,这个数学公式表示:非环部分的长度+环起点到相遇点之间的长度就是环的整数倍。
在数据结构上的意义是什么?现在我们知道两个指针都在离环起点距离是y的那个相遇点,而现在x+y是环长度的整数倍,这意味着他们从相遇点再走x距离就刚刚走了很多圈,这意味着他们如果从相遇点再走x就到了起点。
那怎么才能再走x步呢?答:让一个指针从头部开始走,另一个指针从相遇点走,等这两个指针相遇那就走了x步此时就是环的起点。
步骤:
快慢指针,快指针速度是慢指针的两倍
# 1. 环前的长度为n,假设环长度大于环前的长度;
# 2. 慢指针走n步,到达环口,快指针走了2n步;
# 3. 相遇1:慢指针走过n+m, 快指针走过2n+2m,环的长度相当于n+m(n+2m-m),两个指针都位于距环口m处;
# 4. 重新定义两个指针,指针1指向头节点,指针2指向相遇点,以相同的速度遍历;
# 5. 相遇2:指针1走n步,指针2走n步,刚好位于入口。
ListNode *detectCycle(ListNode *head) {
if (head == NULL)
return NULL;
ListNode* dummy = new ListNode(-1);
dummy = head;
ListNode* fast = dummy;
ListNode* slow = dummy;
while(true)/*让快慢指针相遇*/
{
if(fast==NULL||fast->next==NULL)
{
return NULL;
}
slow = slow->next;
fast = fast->next->next;
if (fast == slow) break;
}
fast = dummy;/*重新走x的距离*/
while (fast != slow)
{
fast = fast->next;
slow = slow->next;
}
return fast;
欢迎在文末留言或私信交流。