环形链表之:检测链表中是否存在环。

检测链表中是否存在环

例:给定一个链表,判断链表中是否有环。为了表示给定链表中的环,我们使用整数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;		

欢迎在文末留言或私信交流。

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页