题目
有一个单向链表,链中可能有“环”,如何用程序判断呢?
方法1(最低效)
最常想到的是:首先从头结点开始遍历整个链表,每遍历一个,就和之前遍历过的比较,,,
这相当于每次遍历了两遍,无疑是扩大了链表“ 遍历 ”带来的劣势,其时间复杂度为O(n^2),由于没有额外的存储空间,故空间复杂度为O(1)。
这也太低效了吧。
方法2(次之)
首先创建一个以节点ID为key的hashSet集合,用来存储曾经遍历过的节点。
这种方式相当于“ 用空间换时间 ”,其使用额外的存储空间来代替重复遍历链表带来的时间上的“低效”。
时间复杂度O(n),但空间复杂度也是O(n)。
有没有办法让时间复杂度不变同时空间复杂度降低呢?
双指针法!
正如我前文所说,双指针法在诸如链表等的算法中简直是游刃有余。
首先创建两个指针p1和p2,让他们同时指向这个链表的头结点。然后开始一个大循环,在循环体中,让p1每次向后移动一个节点,而p2每次移动两个节点,然后比较两个指针所指向节点是否相等,如果相等,则链表有环!
bool isExistLoop(ListNode* pHead) {
ListNode* fast;//慢指针,每次前进一个节点
ListNode* slow;//快指针,每次前进2个节点
slow = fast = pHead ; //两个指针均指向链表头节点
//当没有到达链表结尾,则继续前进
//快指针一次两个节点,慢指针一次一个节点,要求不能在尾节点处相遇,故而有下面while语句中的条件
while (slow != NULL && fast -> next != NULL) {
slow = slow -> next ; //慢指针前进一个节点
fast = fast -> next -> next ; //快指针前进两个节点
if (slow == fast) //若两个指针相遇,且均不为NULL则存在环
return true ;
}
//到达末尾仍然没有相遇,则不存在环
return false ;
}
思路解读:
(1)定义两个指针分别为 slow,fast,并且将指针均指向链表头节点。
(2)规定,slow 指针每次前进 1 个节点,fast 指针每次前进两个节点。
(3)当 slow 与 fast 相等,且二者均不为空,则链表存在环。
若链表中存在环,则快慢指针必然能在环中相遇。这就好比在环形跑道中进行龟兔赛跑。由于兔子速度大于乌龟速度,则必然会出现兔子与乌龟再次相遇情况。因此,当出现快慢指针相等,且二者不为NULL时(即二者不在尾节点处相遇),则表明链表存在环。
相似扩展
如何求出入环节点?
上面我们判断了是否有环,那么,在有环处一定两指针是相遇的。
因为快慢指针的性质,我们知道,快指针比慢指针快一步:这样,当两者再次相遇时,p2比p1整整多走了一圈。
也就是说,从链表头到入环节点的位置,恰等于从首次相遇点回到入环点的距离!
我们把p1放在相遇位置,把p2放在头结点位置,这次,让他们以相同步率移动,则再次相遇的节点,就是要求的“入环节点”!