一、单链表问题
1️⃣给一个单链表,判断是否存在环。
2️⃣如果存在环,找出环的入口点。
3️⃣如果存在环,求出环上节点的个数。
4️⃣如果存在环,求出链表的长度。
5️⃣如果存在环,求出环上距离任意一个节点最远的点(对面节点)。
6️⃣如何判断两个无环链表是否相交。如果相交,求出第一个相交的节点。
二、判断时候有环(链表头指针为head)
1️⃣“快慢指针”法。就是有两个指针 fast 和 slow,开始的时候两个指针都指向链表头head,然后在每一步操作中 slow 向前走一步即:slow = slow->next
,而 fast 每一步向前走两步即:fast = fast->next->next
。由于 fast 要比 slow 移动的快,如果有环,fast 一定会先进入环,而 slow 后进入环。当两个指针都进入环之后,经过一定步数的操作之后,二者一定能够在环上相遇,并且此时 slow 还没有绕环一圈(相当于田径跑道上套圈的逻辑)。如图:
当 slow 刚进入环时每个指针可能处于上面的情况,接下来 slow 和 fast 分别向前走即:
if (slow != NULL && fast->next != NULL) {
slow = slow -> next ;
fast = fast -> next -> next ;
}
也就是说,slow 每次向前走一步,fast 向前追了两步,因此每一步操作后 fast 到 slow 的距离缩短了1步,这样继续下去就会使得两者之间的距离逐渐缩小:...、5、4、3、2、1、0 -> 相遇。又因为在同一个环中 fast 和 slow 之间的距离不会大于换的长度,因此到二者相遇的时候 slow 一定还没有走完一周(或者正好走完以后,这种情况出现在开始的时候 fast 和 slow 都在环的入口处)。
typedef struct node{
char data ;
node * next ;
}Node;
bool exitLoop(Node *head)
{
Node *fast, *slow ;
slow = fast = head ;
while (slow != NULL && fast -> next != NULL)
{
slow = slow -> next ;
fast = fast -> next -> next ;
if (slow == fast)
return true ;
}
return false ;
}
2️⃣使用 Set 做标记,一次遍历,当发现这个节点被标记了,那么说明走到了重复的点,也就说明有环了。
三、找出环的入口点
从上面的分析知道,当 fast 和 slow 相遇时,slow 还没有走完链表,假设 fast 已经在环内循环了n(1<= n)圈。此时 slow 走了s步,则 fast 走了2s步,又由于 fast 走过的步数为s + n*r
(s + 在环上多走的n圈),则有等式:
①2*s = s + n*r
--->②s = n*r
。
假设起点到入口点的距离是a,入口点到相遇点的距离是x,则有:
③a + x = s = n*r
--->④a = n*r - x
。
结合④以及上图可以看出,从链表起点 head 开始到入口点的距离a,与从入口点到 slow 和 fast 的相遇点的距离x相等。【分析】现让一指针p1从链表起点 head 开始遍历,指针p2从相遇点开始遍历,且p1和p2移动步长均为1。则当p1移动a步即到达环的入口点,由④可知,此时p2也已移动a步即nr - x步。由于p2是从相遇点开始移动,故p2移动nr步是回到了相遇点,再退x步则是到了环的入口点。也即,当p1移动a步第一次到达环的入口点时,p2也恰好到达了该入口点。
因此可以用指针(ptr1,prt2),同时从 head 与 slow 和 fast 的相遇点出发,每一次操作走一步,直到 ptr1 == ptr2,此时的位置也就是入口点。
Node* findLoopStart(Node *head)
{
Node *fast, *slow ;
slow = fast = head ;
while (slow != NULL && fast -> next != NULL)
{
slow = slow -> next ;
fast = fast -> next -> next ;
if (slow == fast) break ;
}
if (slow == NULL || fast -> next == NULL) return NULL ; //没有环,返回NULL值
Node * ptr1 = head ; //链表开始点
Node * ptr2 = slow ; //相遇点
while (ptr1 != ptr2)
{
ptr1 = ptr1 -> next ;
ptr2 = ptr2 -> next ;
}
return ptr1 ; //找到入口点
}
四、求环上节点的个数
1️⃣记录下相遇节点存入临时变量 tempPtr,然后让 slow (或者 fast 也行)继续向前走slow = slow -> next
,一直到slow == tempPtr
,此时经过的步数就是环上节点的个数。
2️⃣从相遇点开始 slow 和 fast 继续按照原来的方式向前走slow = slow -> next; fast = fast -> next -> next;
直到二者再次相遇,此时经过的步数就是环上节点的个数 。
对于第二种思路,可以这样想,结合上面的分析,fast 和 slow 每一次操作都会使得两者之间的距离较少1。可以把两者相遇的时候看做两者之间的距离正好是整个环的长度r。因此,当再次相遇的时候所经过的步数正好是环上节点的数目。
五、求出链表的长度
链表长度L = 起点到入口点的距离 + 环的长度r
六、求出环上距离任意一个节点最远的点(对面节点)
如图,点1和4、点2和5、点3和6分别互为“对面节点”,也就是环上最远的点,要求是怎么求出环上任意一个点的最远点。对于环上任意的一个点 ptr0,要找到它的“对面点”,可以这样思考:同样使用快慢指针法,让 slow 和 fast 都指向 ptr0,每一步都执行与上面相同的操作(slow 每次跳一步,fast 每次跳两步),当fast = ptr0
或者fast = prt0->next
的时候 slow 所指向的节点就是 ptr0 的”对面节点“。
如图,把环从 ptr0 处展开,展开后可以是无限长的(如上在6后重复前面的内容)。由于 slow 移动的距离永远是 fast 的一半,因此当 fast 遍历完整个环长度r个节点的时候 slow 正好遍历了r/2个节点,也就是说此时正好指向距离 ptr0 最远的点。
网友评论