https://leetcode-cn.com/explore/interview/card/top-interview-questions-easy/6/linked-list/46/
这题不要求返回环的入口,算是比较初级的题,然而我自己还是没能想出来。
判断是否成环,主要是采用了“相遇法”。
也就是两个指针同时从head遍历,只不过一个步长为1,一个步长为2,如果成环,则快的那个总会在环中与慢的那个相遇。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
bool hasCycle(struct ListNode *head) {
struct ListNode *slow,*fast;
slow=fast= head;
while(fast&&fast->next){
slow = slow->next;
fast = fast->next->next;
if(slow==fast)
return true;
}
return false;
}
我在思考时,最原始的想法是为每一个node打上“已阅”的标记,这样遍历时如果遇到已经遇到的node,则判断为成环了。
也许可以通过使用hashmap把链表中每一个node的地址保存下来,但是题目要求不使用额外空间,ListNode结构体又无法扩展,所以放弃了。
补充
如果要求返回环的入口?
我们可以通过双指针法来操作,目标是让双指针相遇在环的入口。
好了,画这个图让我仿佛回到了小学的快乐时光。
主要是为了说明,a是head到入口的距离,b是入口到相遇点的距离,黄色是相遇点。再假设环周常是L。
在之前判断是否成环的算法中,慢指针走过的路程是 a+b,快指针走过了a+b+k*L 。
同时,快指针走过的路程是慢指针的2倍。
也就是a+b+k*L = 2*(a+b), 得到 a+b = k*L 。
让p1从头开始,p2从相遇点开始,同时走a+b步,可以知道他们终将都回到相遇点。
而这个过程中,他们必然会在入口相遇,再携手走完b这段路程。
经过上述的思考,我们就可以知道如何找出环路的入口节点了。
摘抄一段代码吧。
//找到环的入口点
Node* findLoopPort(Node *head)
{
//如果head为空,或者为单结点,则不存在环
if(head == NULL || head->next == NULL) return NULL;
Node *slow,*fast;
slow = fast = head;
//先判断是否存在环
while(fast != NULL && fast->next != NULL)
{
fast = fast->next->next;
slow = slow->next;
if(fast == slow)
break;
}
if(fast != slow) return NULL; //不存在环
fast = head; //快指针从头开始走,步长变为1
while(fast != slow) //两者相遇即为入口点
{
fast = fast->next;
slow = slow->next;
}
return fast;
}