弗洛伊德环循环检测算法

我们可以假设有两个指针都指向起点,一个指针是慢指针,每次往后面移动一个,一个指针是快指针,每次往后面移动两个; 如果没有环存在,那么快指针一定先到最后的null处,退出; 如果有环存在,快指针一定会赶上慢指针 如果这个循环是存在的,我们假设快指针在某节点处赶上了慢指针,他距离起始节点(不包含)长度是

我们可以假设有两个指针都指向起点,一个指针是慢指针,每次往后面移动一个,一个指针是快指针,每次往后面移动两个;

如果没有环存在,那么快指针一定先到最后的null处,退出;

如果有环存在,快指针一定会赶上慢指针

如果这个循环是存在的,我们假设快指针在某节点处赶上了慢指针,他距离起始节点(不包含)长度是z,整个环的长度是l,从起始节点(包含)到他们相遇的位置(不包含)长度是y,则有l=y+z。快指针已经走过的节点数量是f:fast,慢指针则是f:fast

那么因为每次快指针走的距离是慢指针两倍,f=2*s

那对于快指针就是先走了x,然后循环一定的圈数c_1然后再加上y赶上了慢指针

f=x + c_1\times l + y

那对慢指针同理,循环圈数可能不一样

s=x + c_2\times l + y

代入到f=2*s

x + c_1\times l + y = 2\times (x + c_2\times l + y)
x+2c_2l-c_1l+y=0
x=(2c_2-c_1)l-y

因为l=y+z

x=(2c_2-c_1)l-(l-z)
x=(2c_2-c_1-1)l+z

2c_2-c_1-1 是个常量,我们假设他为c=2c_2-c_1-1

x=c\times l+z

这意味着x的长度就是不知道多少轮圈循环的长度最后加上z

那对于上个图的例子就是c=0,如果x够长,比循环圈还长就c>0

这有什么用呢,假如我们把慢指针放回最开始的head处,那么他和快指针同时按一个速度(一个节点一个节点)跳转,他们一定会相遇在循环开始的节点处!也就是慢指针走z步肯定会到循环开始的点(不知道z具体是多少的情况下就直接用指针判断相遇)

那么有个例子,假如有个arr数组,判断里面有没有重复的数字用O(1)的空间,对于这个数组,我们可以看到如果有重复,那么一定会有圈出现

func find_duplicate(arr []int){
	slow := 0
	fast := 0
	for {
		slow := arr[slow]
		fast := arr[fast]
		fast = arr[fast]
		if slow == fast{  //一旦相遇说明成环
			break
		}
	}
	slow = 0 // 会到头 找重复的那个值(下标)
	for slow!=fast{
		slow = arr[fast]
		fast = arr[fast]
	}
	return slow
}

那slow可不可以每次加3 加4?

结论:可以

我们可以假设fast是slow的k

环外x,环内l ,经过t次挪动:

slow在(t-x)\%l

fast在(kt-x)\%l

t=r*l也就是tl也的某个整数倍的时候,且有模运算规则(a-b)\%c = (a\%c - b\%c)\%c

slow可化简为(-x)\%l,fast也可化简为(-x)\%l,因此slow与fast相等相遇

LICENSED UNDER CC BY-NC-SA 4.0
Comment