Given a linked list, determine if it has a cycle in it.
Follow up:
Can you solve it without using extra space?
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
bool hasCycle(struct ListNode *head) {
struct ListNode *fp, *sp;
if( head == NULL || head->next == NULL ) return false;
fp = head->next;
sp = head;
while( fp->next != NULL && fp->next->next != NULL)
{
if(fp == sp ) return true;
fp = fp->next->next;
sp = sp->next;
}
return false;
}
第一种方法,将所有的遍历过的节点用某个结构存储起来,然后每遍历一个节点,都在这个结构中查找是否遍历过,如果找到有重复,则说明该链表存在循环;如果直到遍历结束,则说明链表不存在循环。
这个结构我们可以使用hash来做,hash中存储的值为节点的内存地址,这样查找的操作所需时间为O(1),遍历操作需要O(n),hash表的存储空间需要额外的O(n)。所以整个算法的时间复杂度为O(n),空间复杂度为O(n)。
第二种方法,比较的特别,是使用反转指针的方法,每过一个节点就把该节点的指针反向。
当有环的时候,最后指针会定位到链表的头部,如果到最后,都没有再到头部,那说明链表不存在循环。
这个方法会破坏掉链表,所以如果要求是不能破坏链表的话,我们最后就还需要反转一下,再将链表恢复。
这个方法使用的空间复杂度为O(1),其实是使用了3个指针,用于进行反转。同时,时间复杂度为O(n)。
第三种方法,可能大家已经知道了,同时也是面试官大多想要得到的答案,就是快慢指针。
快指针pf(f就是fast的缩写)每次移动2个节点,慢指针ps(s为slow的缩写)每次移动1个节点,如果快指针能够追上慢指针,那就说明其中有一个环,否则不存在环。
这个方法的时间复杂度为O(n),空间复杂度为O(1),实际使用两个指针。
参考:百度百科
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode *fp, *sp;
if( !head || !head->next) return NULL;
fp = head;
sp = head;
while(fp->next && fp->next->next)
{
fp = fp->next->next;
sp = sp->next;
if(fp == sp)
{
sp=head;
while(fp!=sp)
{
fp = fp->next;
sp = sp->next;
}
return fp;
}
}
return NULL;
}
不能像I那样是的fp和sp不在同一起点,不好找环起点,故I的算法也需要改进。
改后的I:
bool hasCycle(struct ListNode *head) {
struct ListNode *fp, *sp;
if( head == NULL || head->next == NULL ) return false;
fp = head;
sp = head;
while( fp->next != NULL && fp->next->next != NULL)
{
fp = fp->next->next;
sp = sp->next;
if(fp == sp ) return true;
}
return false;
}