最近在编程刷题网站力扣上刷了两道比较有意思的链表问题,一道是判断链表是否带环,另一道则是找到链表入环的开始节点。通过对此类带环问题的思考与学习,见识到该问题思想的巧妙,遂作此博客陈述其解题思路。
判断是否带环
首先是判断一个链表中是否带环,对于该问题我们可以通过使用快慢双指针的方法来解决。让fast指针的速度是slow指针的2倍:
假设当slow入环时,fast与slow的相距节点数为N:
因为slow:fast=1:2,所以slow与fast同时前进时,每次距离N减1直至两指针相距为0时两指针相遇,即slow与fast的地址值相等,此时便可判断链表带环。而如果fast指针或fast->next指向NULL,则说明链表中不带环。具体代码实现如下:
typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head) {
ListNode* slow, *fast;
slow = fast = head;
while (fast && fast->next)//若fast指针或fast->next指向NULL,则说明链表中不带环
{
slow = slow->next;
fast = fast->next->next;
if (slow == fast)
return true;
}
return false;
}
但此外我们还应思考另一个问题,fast的速度若是slow的3、4乃至5倍时,fast与slow指针最终能否相遇呢?让我们先思考一下slow:fast=1:3的情况,这也就是说两指针距离N每次减2,若N为偶数那fast最终肯定可以追上slow。但N为奇数时第一次fast会到slow的前一个位置,此时,fast与slow的命运便由另一个量决定了,那便是环的节点数C。当fast到slow前面时,二者的距离N便变成了C-1,此时如果C为奇数,N便是偶数,fast就一定可以追上slow;而C为偶数时,fast便会重蹈之前的覆辙,又跑到了slow的前一个节点,最终如牛郎织女般永远无法相见。
但事实真就如此吗?实际不然,让我们回到slow一开始入环的时候:
然后让我们以数学的思维思考一下这个问题,fast与slow无法相遇的情况是当N为奇数同时C为偶数的时候。那么这两个条件是否可以同时存在呢?首先让我们假设slow此时与链表的头节点相距L个节点,并且fast已经绕环走了X圈,于是根据此时fast与slow的步数便有如下等式:
明显此时等式左边为偶数,如果N为奇数C为偶数,那么XC为偶数,C-N为奇数,最终等式右边为奇数。明显等式不成立。所以slow:fast=1:3时两指针最终必定可以相遇,可喜可贺,可喜可贺!
找出环的开始节点
判断了链表是否存在环之后,我们便可以找出环的开始节点。
此时,slow与fast指针指向同一个节点,由于slow:fast=1:2,此时slow走过的节点数必定小于C,然后可以得到他们相遇时的一个等式:
明显L的距离会等于fast与slow相遇节点到环开始节点的距离,因此我们便可以这样完成这道题:
typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) {
ListNode* slow, *fast, *meet;
slow = fast = head;
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if (slow == fast)//存在环
{
meet = slow;
while (head != meet)
{
head = head->next;
meet = meet->next;
}
return meet;
}
}
return NULL;//不存在环
}
可见,这两道链表带环问题对于代码很简单,但思维却较复杂。