方法1:
直接方法:
- 先用快慢指针的方法判断是否成环
- 将slow==fast的节点看作是尾节点(不用置空),这时就可以将问题转化为求头指针为head和fast->next的两个链表的交点了
1.如何用快慢指针的方法判断是否成环?
//返回false为不成环,返回true为成环
struct ListNode* slow = head;//慢指针为slow
struct ListNode* fast = head;//快指针为fast
if (head == NULL)//如果head==NULL,那么不可能成环,直接返回false
{
return false;
}
while (fast && fast->next)//因为快指针一次走两步,古fast和fast->next都不能为空
//也因为fast快指针要比slow慢指针走的快,如果fast都不为空,slow也一定不为空,所以这里不需要加slow的判断语句
{
slow = slow->next;//慢指针一次走一步
fast = fast->next->next;//快指针一次走两步
if (slow == fast)//如果快慢指针相等,意味着他们在成环的链表中相遇了,也就是说链表成环
{
return true;
}
}
return false;
思考fast的速度一定要是slow的两倍吗?
三倍和四倍可以吗?
当slow走到mid时,fast进入圆环
当slow进入圆环时,fast为圆环中的某一节点
假设圆环有C个节点,也就是圆环的周长为C
slow进入圆环时与fast相差X个节点
由于fast和slow的运动方向是一样的,所以fast和slow的相对距离为C-X
假设经过t次,slow和fast会相遇
可得2t-t=C-X
t=C-X
所以fast和slow总会在某一节点相遇
但如果fast不是slow的两倍,而是三倍四倍呢?
我们假设fast是slow的3倍。
3t-t=C-X,2t=C-X,C-X不一定会被2整除,也就是在C-X的距离上,fast和slow不一定会相遇,这时fast就会再次超过slow,如果fast依然领先slowX个节点,他们的相对距离依然为C-X,依然不会相遇,这就会陷入死循环。
4,5,6……倍的情况也依然如此。
所以fast的速度只能是slow的两倍
2.找两个链表交点的方法:
在确保相交的情况下(判断是否相交只要看两链表的的尾节点的地址是否相同就可以了)
之后分别遍历两个链表,计算他们的长度差,假如为num
再让较长的链表先走num步,再与较短的链表同步遍历(这样两链表到达交点的步数就相同 了)
接下来只要两链表的节点地址相同,就找到了他们的交点
对于环形链表,将fast==slow的节点看作是尾节点(不用置为NULL)
这就可以看作求头指针为head和fast->next的两个链表的交点问题了。
int s1 = 0;//记录List1的链表长度
int s2 = 0;//记录List2的链表长度
struct ListNode* List1 = head;
struct ListNode* List2 = fast->next;
while (List1 != fast)//把fast看作尾节点
{
List1 = List1->next;
s1++;
}
while (List2 != fast)
{
List2 = List2->next;
s2++;
}
List1 = head;//将List1看作是长链表,这里不知道head和fast->next谁长谁短,只是假设head为长链表
List2 = fast->next;//将List2看作是短链表
if (s1 < s2)//如果s2>s1说明fast->next才是长链表
{
List1 = fast->next;
List2 = head;
}
int num = s1 - s2 > 0 ? s1 - s2 : s2 - s1;//将s1-s2的绝对值记为num
while (num--)//先将长链表遍历num次
{
List1 = List1->next;
}
while (List1 != List2)//共同遍历,如果相等则找到交点
{
List1 = List1->next;
List2 = List2->next;
}
return List1;
方法1完整代码如下:
struct ListNode* detectCycle(struct ListNode* head) {
//方法1:
struct ListNode* slow = head;//慢指针为slow
struct ListNode* fast = head;//快指针为fast
if (head == NULL)//如果head==NULL,那么不可能成环,直接返回NULL
{
return NULL;
}
while (fast && fast->next)//因为快指针一次走两步,古fast和fast->next都不能为空
//也因为fast快指针要比slow慢指针走的快,如果fast都不为空,slow也一定不为空,所以这里不需要加slow的判断语句
{
slow = slow->next;//慢指针一次走一步
fast = fast->next->next;//快指针一次走两步
if (slow == fast)//如果快慢指针相等,意味着他们在成环的链表中相遇了,也就是说链表成环
{
int s1 = 0;//记录List1的链表长度
int s2 = 0;//记录List2的链表长度
struct ListNode* List1 = head;
struct ListNode* List2 = fast->next;
while (List1 != fast)//把fast看作尾节点
{
List1 = List1->next;
s1++;
}
while (List2 != fast)
{
List2 = List2->next;
s2++;
}
List1 = head;//将List1看作是长链表,这里不知道head和fast->next谁长谁短,只是假设head为长链表
List2 = fast->next;//将List2看作是短链表
if (s1 < s2)//如果s2>s1说明fast->next才是长链表
{
List1 = fast->next;
List2 = head;
}
int num = s1 - s2 > 0 ? s1 - s2 : s2 - s1;//将s1-s2的绝对值记为num
while (num--)//先将长链表遍历num次
{
List1 = List1->next;
}
while (List1 != List2)//共同遍历,如果相等则找到交点
{
List1 = List1->next;
List2 = List2->next;
}
return List1;
}
}
return NULL;
}
方法2:
方法二的代码很简单
但重要的是理解为什么要这样写
struct ListNode* slow = head;//慢指针为slow
struct ListNode* fast = head;//快指针为fast
S为head到交点位置的节点数,C为圆环的节点数也就是周长,X为交点到fast和slow相遇的节点数
在这个过程中,slow的移动距离为S+X,fast的移动距离为S+X+N*C(N为fast和slow相遇前fast绕圆环运动的圈数)
由于fast的速度是slow的两倍
可得
2*(S+X)=S+X+N*C
S+X=N*C
S=N*C-X
S=(N-1)*C+C-X
前面的N-1圈是在slow进入圆环前手进行的,可以忽略
所以
S=C-X
也就是说以fast和head为头结点的链表到达交点的节点数是相同的
fast和head进行同步遍历,他们相等的地方就是交点所在的位置,也就是圆环的开始
方法2:完整代码如下:
struct ListNode* detectCycle(struct ListNode* head) {
struct ListNode* slow = head;//慢指针
struct ListNode* fast = head;//快指针
if (head == NULL)//如果head为NULL,不存在环形,直接返回NULL
{
return NULL;
}
while (fast && fast->next)//和前面的原理相同,因为快指针一次走两步,古fast和fast->next都不能为空
//也因为fast快指针要比slow慢指针走的快,如果fast都不为空,slow也一定不为空,所以这里不需要加slow的判断语句
{
slow = slow->next;//一次一步
fast = fast->next->next;//一次两步
if (slow == fast)//相遇说明存在圆环
{
struct ListNode* meet = fast;//fast的地址赋值给meet
while (meet != head)//相等时就是相交点,圆环的开始
{
meet = meet->next;
head = head->next;
}
return meet;
}
}
return NULL;
}
ps:如果对你有帮助的话麻烦给个免费的赞,求求你了。
pps:本人也是在学习中,如果有什么不对的地方欢迎在评论区指出。