LeetCode初级算法之链表: 环形链表I、II

环形链表I

给定一个链表,判断链表中是否有环。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
在这里插入图片描述
示例 2:
输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

进阶:
你能用 O(1)(即,常量)内存解决此问题吗?

思路一: 快慢指针法

这个有没有环,可以采用快慢指针法,快的一次走两步,慢的一次走一步,如果最后快的会追上慢的,那么就是有环。

class Solution {
public:
    bool hasCycle(ListNode *head) {

        if (!head || !head->next)
        return false;


        ListNode *fast = head->next;
        ListNode *slow = head;

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

        return true;
    }
};

python实现:

class Solution:
    def hasCycle(self, head:ListNode) -> bool:
        
        # 判断空操作
        if not head or not head.next:
            return False
        # 快慢指针
        slow = head
        fast = head.next
        
        while fast != slow:
            if not fast or not slow:
                return False
            
            fast = fast.next.next if fast.next else None
            slow = slow.next
        
        return True
        

思路二:哈希表法

在遍历的过程中,用set存下元素的地址,如果发现有地址已经在set里面,那么就说明有环,又回去了

class Solution {
public:
    bool hasCycle(ListNode *head) {

       if (!head || !head->next)  return false;

        ListNode *p = head;
        set<ListNode*> s;

        while (p)
        {
            if (s.count(p))
                return true;
            s.insert(p);
            p = p->next;
        }
        return false;
    }
};

Python版本:

class Solution:
    def hasCycle(self, head:ListNode) -> bool:
        
       # 判断空操作
        if not head or not head.next:
            return False
    
        s = set()
        p = head
        while (p):
            if p in s:
                return True
            s.add(p)
            p = p.next
        
        return False  

思路三 - 投机取巧法

这个方法是我的首次a的想法,因为当时没想那么多,直接就跳出了这么个方法,但是这个方法单纯的是解决这个问题,并不是一种思想,也不值得借鉴,但是也反映了解决某个问题的强烈欲望,以完成这个问题为本身,并没有寄希望于举一反三的效果。
就是这样,我直接遍历,虽然我知道,如果有环的话肯定不会停止这个循环,那么我就假设,这里面所有的节点,如果没环的情况下不会超过1万个。所以我设置了一个计数器,如果超过10000了,说明就有环了,没想到竟然能a。

class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode *p = head;
        int cou = 0;
        while (p)
        {
            cou++;
            p = p->next;

            if (cou>100000)
                return true;
        }

        return false;
    }
};

环形链表 II

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

说明:不允许修改给定的链表。

示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:tail connects to node index 1
解释:链表中有一个环,其尾部连接到第二个节点。
在这里插入图片描述
示例 2:
输入:head = [1,2], pos = 0
输出:tail connects to node index 0
解释:链表中有一个环,其尾部连接到第一个节点。
在这里插入图片描述
示例 3:
输入:head = [1], pos = -1
输出:no cycle
解释:链表中没有环。
在这里插入图片描述

思路一:快慢指针法

这个快慢指针法其实在考察一个数学上的问题,有环的情况下先上结论:

  • 如果快指针和慢指针都从head节点出发,快指针一次走两步,慢指针一次走一步,经历若干圈之后,两者一定会从某个节点相遇, 且快指针走过的路程是慢指针走过的路程的两倍。
  • 不仅会相遇,如果这时候快指针回到起始位置,慢指针在相遇位置, 然后,快指针一次走一步,慢指针一次走一步,若干步之后,两者还会相遇,且相遇位置就是入环节点处。

这里使用快慢指针的思路,其实利用了上面的这两个结论。
首先,先解释一下子为什么是这样,结论一不解释, 会相遇这个如果不明白可以想象围着操场追赶,数学证明看LeetCode官方题解。 这里重点解释结论二, 看下面画的草图(A和C是一点,便于区分环):
在这里插入图片描述
就假设第一次相遇, 那么fast走的路程是 OA + AB + BC + CB, slow走的路程是OA + AB。 根据结论一, fast的走的路程应该是slow走的路程的两倍。即 OA + AB + BC + CB = OA + AB + OA + AB , A点和C点是同一点,即AB和CB其实是同一回事。 这样上面就成了 OA = BC。
所以,如果在B点fast和slow相遇的话,fast回到原点,slow不动,那么slow和fast以1步的步伐走的时候,到再相遇,slow走的正是BC,fast走的正是OA。 而A正是入环点。
代码如下:

class Solution:
    def DetectCycle(self, head:ListNode) -> ListNode:
        
        # 判断空操作
        if not head or not head.next:
            return None
        
        # 从起始开始走
        fast = head
        slow = head
        
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            
            if (fast == slow):  # 相遇
                fast = head     # fast 回到起点
                while fast != slow:
                    slow = slow.next
                    fast = fast.next
                return slow
        
        return None   

思路二: 哈希表法

这个只需要在环形链表I上的那个代码简单修改即可,因为这个的思想是我只有一个指针从head开始,每走一个,检查一下是否这个节点再集合里面,如果在,那就直接返回即可,如果不在,那么就假如这个节点,往后走。

class Solution:
    def detectCycle(self, head:ListNode) -> ListNode:
        
       # 判断空操作
        if not head or not head.next:
            return None
    
        s = set()
        p = head
        while (p):
            if p in s:
                return p
            s.add(p)
            p = p.next
        
        return None

思路三: 投机取巧法 – 这个其实不符合规定,思路没想到

这个其实违反了规定,改变了链表本身,但是这个思路我没有想到,所以记录一下,思想是在值上做文章,我依然从head开始遍历每个节点,遍历的同时,将每个链表节点的值类型改变, 那么如果下一个链表的值的类型与改变后的类型相等,那么这个节点就是入环节点。
下面我每遍历一个节点,就把值的部分改成了字典的类型,其实不符合题目要求了。

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        
        # 判断空操作
        if not head or not head.next:
            return None
        
        p = head
        loc = 0
        
        while p:
            if isinstance(p.val, dict):
                return p
            elif isinstance(p.val, int):
                p.val = {loc:p.val}
            
            p = p.next
            loc = loc + 1
        
        return None
发布了66 篇原创文章 · 获赞 67 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/wuzhongqiang/article/details/104139263