单链表的求环问题也是面试算法中常常考察的问题,链表的求环的问题可通过以下两种方法解决
先定义单链表的数据结构:
struct ListNode
{
int val;
struct ListNode* next;
ListNode(int x):val(x){}
};
方法一:使用集合set
单链表是否存在环可以转化成一个通过遍历各个节点时是否遇到相同的结点地址的问题,如果遇到,就返回改地址,遍历结束时仍然没有出现重复的结点地址,则说明改单链表不存在环。但是此方法空间复杂度是O(n),实现如下:
ListNode* detect_cycle(ListNode* head) {
std::set<ListNode*> node_set;
while (head)
{
if (node_set.find(head->val) != node_set.end) {
return head;
}
node_set.insert(head);
head = head->next;
}
return NULL;
}
方法二:快慢指针法
这个方法是本节内容希望重点介绍的方法,是一个非常巧妙的方法,可以使得空间复杂度降低到O(1)。这里我们定义一个慢指针slow和一个快指针fast,遍历单链表时,slow指针每次向前移动一步,fast每次向前移动两次,经过若干次移动后,如果slow与fast相遇,则说明链表存在环,若fast指针率先取得null值,则说明链表不存在环。这里还有一个问题,存在环的话,如何确定环开始的结点?
以下图为例,这里我们做一个分析:
这里a段距离=c段距离,可知:如果从head结点和meet结点同时出发,使用相同的步长,一定可以到达环的初始节点。
因此,本题也就可以描述清楚了,具体实现代码如下:
ListNode* detect_cycle(ListNode* head) {
ListNode* slow = head;
ListNode* fast = head;
ListNode* meet = NULL; // 相遇指针
while (fast)
{
slow = slow->next; // slow每次走一步
fast = fast->next;
if (!fast)
{
return NULL;
}
fast = fast->next; // fast每次走两步
if (slow == fast)
{
meet = slow;
}
}
if (!meet)
{
// 如果meet指针没有被重新赋值,说明fast与slow没有相遇,即链表无环
return NULL;
}
while (head && meet)
{
if (head == meet)
{
return head;
}
head = head->next;
meet = meet->next;
}
return NULL;
}