环形链表的示意图如下
我们假设定义的单链表如下
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
方法一:哈希表法
这里我们采用set这一关联容器。设置set的下标为ListNode*,遍历该链表的每一个结点,将未出现过的链表结点存储至set中,会出现两种情况:
- 发现指向某一结点的指针已经存储过了,就说明这个链表是环形的。
- 发现指向某一结点的指针是NULL,则说明这条链表有尾巴,不是环形的。
bool IsCycle(ListNode* head){
set<ListNode*> Node;
while(head){
if(Node.find(head)==Node.end()){
Node.insert(head);//如果未找到,则将该指针存储到set容器中
}else return true;//如果找到了,则属于第一种情况
head=head->next;//移动指针指向下一个元素
}
return false;//如果跳出循环,则说明head是NULL,属于第二种情况
}
该算法时间复杂度为 O(n),空间复杂度为 O(n)
方法二:快慢指针法
我们可以思考一个问题:两位运动员在环形跑道上什么情况下会相遇?
是当他们速度一致吗?显然不是,应该是当他们有速度差的时候,快的会逐渐追上慢的。
在这里我们只是将运动员换成了指针,将赛道换成了环形链表。
于是有了下面的代码:
bool IsCycle(ListNode* head){
ListNode *fast, *slow;
//首先要排除该链表为空或只有一个节点,两种情况均不可能是循环链表
//排除的其他原因是链表为空时,slow和fast指针均会失效
//当只有一个节点时,fast则会失效
if(head==NULL || head->next==NULL) return false;
fast=head->next;//从起点开始,fast就比slow快一步
slow=head;
while(fast!=slow){
//如果fast或fast->next为空:则该链表不是循环链表
if(fast==NULL || fast->next==NULL) return false;
//在“跑步”过程中,fast每跑两步,slow只跑一步
fast=fast->next->next;
slow=slow->next;
}
return true;//跳出循环也就意味着fast追上了slow,该链表是循环链表
}
该算法的时间复杂度为 O(n),空间复杂度为 O(1)