单链表环问题的证明

Mar 2, 2016

首先声明一下节点的定义。

struct Node
{
	int val;
	Node* next;
public:
	Node(int v) :val(v), next(NULL)
	{}
};

本文的符号图示如下:
这里写图片描述
1、判断链表是否带环
判断链表是否带环,可以在头结点设两个指针,一个叫fast,一个叫slow,fast一下走两步,而slow一下走一步。如果链表中存在环的话,那么fast和slow必定会在环中相遇。若链表中没有环的话,那么fast必定先于slow指针先到达链表的尾节点。
建立数学模型证明如下:
令环长度为k,环节点编号为0,1,2,…,k-1。首先来看两节点初始都在节点0的时候的情况,时间t的时候两个指针的位置节点分别为 t % k t\%k t%k 2 t % k 2t\%k 2t%k,节点相遇的时候有
t % k = 2 t % k (1) t\%k=2t\%k \tag{1} t%k=2t%k(1)
这个模型肯定有解,比如t等于k的整数倍这个时候正好相遇在起点。现在主要讨论除此之外还会在别的点相遇吗?这时的问题转化为等式2有解吗?
t % k = 2 t % k (2.1) t\%k=2t\%k \tag{2.1} t%k=2t%k(2.1)
0 < t % k < k (2.2) 0<t\%k<k \tag{2.2} 0<t%k<k(2.2)
将(2_1)转化为 t % k = ( t % k + t % k ) % k t\%k=(t\%k+t\%k)\%k t%k=(t%k+t%k)%k可见,等式右边不等于等式左边的,只有在两边都是零的时候才成立。
现在看看两个指针在刚开始的时候不是同时进入环的情况,因此将模型修改为
t % k = ( 2 t % k + b ) % k (3) t\%k=(2t\%k+b)\%k \tag{3} t%k=(2t%k+b)%k(3)
其中 0 ≤ b < k 0\leq b<k 0b<k,表示慢指针进入环的时候快指针所在的位置,这里假设环的入口节点编号为0。对等式3求解可得 ( t + b ) % k = 0 (t+b)\%k=0 (t+b)%k=0。综上可知若存在环,两个指针必定相遇,并且在慢指针到达节点k-b的时候相遇。
代码如下:

class Solution
{
public:
	bool HasCirclaInList(Node * pHead)
	{
		Node* pFast = pHead, *pSlow = pHead;
		while (true)
		{
			if (pFast->next != NULL && pFast->next->next != NULL)
				pFast = pFast->next->next;
			else
				return false;
			pSlow = pSlow->next;
			if (pFast == pSlow)
				return true;
		}
		return NULL;
	}
};

2、求环长
当知道两个指针在环内的交点后,找一个指针在环内转一圈所走的步数即是这个环的长度。
这里有一个问题:可否用第一次相遇以前快指针走的步数减去满指针走的步数呢?答案是否定的,因为满指针进入的时候快指针可能已经转了不止一圈了,并且如果第一次相遇点编号大于k/2的时候,快指针在满指针进入环以后要在第二次赶上满指针的时候才能相遇。

class Solution
{
public:
	int LengthOfCircleInList(Node* pHead)
	{
		Node* pFast = pHead, *pSlow = pHead;
		bool flag = false;
		while (true)
		{
			if (pFast->next != NULL && pFast->next->next != NULL)
				pFast = pFast->next->next;
			else
				break;
			pSlow = pSlow->next;
			if (pFast == pSlow)
			{
				flag = true;
				break;
			}
		}
		if (flag)
		{
			int length = 1;
			Node* pNode = pSlow;
			while (pSlow->next != pNode)
			{
				pSlow = pSlow->next;
				length++;
			}
			return length;
		}
		else
			return 0;
	}
};

3、求环的入口点
求解思路,找到第一次相遇点以后,将pSlow重新指向pHead,同时将pFast速度降为1,等到再次相遇的节点就是环的入口节点。
数学证明如下:
假设相遇点节点编号为c,有
t=a+c
2t=a+c+Nk
所以(a+c)%k=0,即证。

class Solution
{
public:
	Node* EntranceOfCircleInList(Node* pHead)
	{
		Node* pFast = pHead, *pSlow = pHead;
		bool flag = false;
		while (true)
		{
			if (pFast->next != NULL && pFast->next->next != NULL)
				pFast = pFast->next->next;
			else
				break;
			pSlow = pSlow->next;
			if (pFast == pSlow)
			{
				flag = true;
				break;
			}
		}
		if (flag)
		{
			pSlow = pHead;
			while (pSlow != pFast)
			{
				pSlow = pSlow->next;
				pFast = pFast->next;
			}
			return pSlow;
		}
		else
			return NULL;
	}
};
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值