链表--判断链表是否有环

题目1

linker-list-cycle
给定一个链表,判断链表是否有环。

注意:第一次看题的时候没有看懂题目示例中给出的pos是什么意思,以为是要用到的输入。其实pos是用来让机器生成链表的,我们需要做的就是判断链表是否有环即可。

思路

篡改数据

遍历链表,每次遇到一个节点我们就将节点的值修改为另一个值,然后不停的判断是否是我们修改的值,不是就修改,如果是就代表我们之前遇到过,就返回true。
这个思路的缺点是我们需要保证我们修改的值是之前链表中没有的值,如果有那么最后的结果就不对。
这个思路比较取巧,是评论区的大神想出来的。这个代码遍历一遍以后原有的链表中的值都已经被篡改了。
时间复杂度O(n);
空间复杂度O(1);

c++代码
class Solution {
public:
    bool hasCycle(ListNode *head) {
         while(head)
         {
            if (head->val == 2147483646)
                return true;
            else
            {
                head->val = 2147483646;
            }
            head = head->next;
         }
        return false;
    }
};

hash表保存节点法

遍历链表,将遍历过的节点保存在set()中,遍历时判断节点是否存在,如果存在就代表存在循环。使用的python代码
时间复杂度O(n)
空间复杂度O(n)

c++代码
class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode *p;
        set p_set;

        p = head;

        while(p)
        {
            if(p_set.find(p) != p_set.end()) return true;     //set.find()函数如果找不到会返回set.end()
            p_set.insert(p);
            p = p->next;
        }
        return false;
    }
};
python代码
class Solution:
    def hasCycle(self, head: ListNode) -> bool:
        node = set()
        while head:
            if head in node:
                return True
            else:
                node.add(head)
                head = head.next
        
        return False;

快慢指针法

设立两个指针,一个快指针,一个慢指针,快指针每次走两步,慢指针每次走一步,如果链表有循环,那么快慢指针会相遇。
时间复杂度O(n)
空间复杂度O(1)

c++代码
class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(head == NULL || head->next == NULL) return false;
        
        ListNode *fast, *slow;
        fast = head->next;
        slow = head;

        while(slow != fast)
        {
            if(fast == NULL || fast->next == NULL) return false;
            fast = fast->next->next;
            slow = slow->next;
        }

        return true;
    }
};

硬做

如果链表有环那么一直读取链表是会出现死循环的,因此可以一直读取链表,设置一个超时时间,如果超时还没有读到null,就代表链表有环。

题目2

linked-list-cycle-ii
给定一个链表,如果链表有环,返回链表开始入环的第一个节点。如果链表无环,返回null。
说明:不允许修改给定链表。

思路

上面的题目中第一种思路我们采用了篡改链表的方法,这里题目限制了我们不能修改链表,放弃这种思路。

hash表保存节点法

和上面的思路一样,不过这里返回的是第一次重复的节点。
时间复杂度:O(n);
空间复杂度:O(n);

c++代码
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        set lset;
        
        while(head != NULL)
        {
            if(lset.find(head) == lset.end())
            {
                lset.insert(head);
                head = head->next;
            }
            else
            {
                return head;
            }
        }

        return NULL;
    }
};

快慢指针法-Floyd算法

Floyd算法又称为龟兔赛跑算法。算法分为两个阶段,第一个阶段找出列表中是否有环,如果没有环,返回NULL;如果有环,返回快慢指针的相遇节点。第二阶段是根据相遇节点来找到环的入口。

阶段1:
设立两个指针,一个快指针,每次走两步,一个慢指针,每次走一步,直到快指针无法继续往前移动。
如果快指针和慢指针在某个节点相遇,就代表链表有环,同时返回这个相遇节点。
链表如图所示:

阶段1

链表环中的节点编号范围为~,其中为环中节点的数目。
非环节点编号范围为~,其中是环以外节点的数目。
次迭代以后,慢指针指向了编号为0的节点,此时快指针走了步,快指针指向节点h,节点h的编号就应该为,即。
此时,继续迭代步,慢指针显然指向号节点,而快指针也会指向号节点。因为快指针每次走两步,所以会走,从节点走会到标号为0的节点,在走步到达号节点。所以此时快慢指针会同时指向号节点,也就是相遇。
用公式表达为:

因此,如果列表是有环的,快指针和慢指针最后会同时指向同一个节点,因此被称为相遇。

阶段2
给定阶段1找到的相遇点,阶段2会找到环的入口。
指定两个额外的指针:ptr1ptr2ptr1指向链表头部,ptr2指向相遇点。
然后,每次将两个指针将它们往前移动一步,直到它们相遇,它们相遇的点就是环的入口,返回这个节点就是正确的答案。
如图所示:

阶段2

利用已知的条件:慢指针移动1步,快指针移动2步,来说明它们相遇在环的入口处。证明中tortoise代表慢指针,hare代表快指针。




因为,指针从h点出发和从链表的头出发,最后会遍历相同数目的节点后在环的入口处相遇。
时间复杂度:O(n)
空间复杂度:O(1)

c++代码
class Solution {
public:
    ListNode *getCycle(ListNode *head){
        if(head == NULL) return NULL;

        ListNode *turtle, *hare;
        turtle = hare = head;

        while(hare->next != NULL && hare->next->next != NULL)
        {
            turtle = turtle->next;
            hare = hare->next->next;
            if(turtle == hare) return turtle;
        }

        return NULL;
    }

    ListNode *detectCycle(ListNode *head) {
        ListNode *cycle;
        
        cycle = getCycle(head);
        if(cycle == NULL) return NULL;
        else
        {
             while(cycle != head)   
             {
                 cycle = cycle->next;
                 head = head->next;
             }
        }

        return cycle;
    }
};

你可能感兴趣的:(链表--判断链表是否有环)