algorithm-base/animation-simulation/链表篇/leetcode142环形链表2.md
2021-07-16 00:06:52 +08:00

10 KiB
Raw Blame History

如果阅读时,发现错误,或者动画不可以显示的问题可以添加我微信好友 tan45du_one ,备注 github + 题目 + 问题 向我反馈

感谢支持,该仓库会一直维护,希望对各位有一丢丢帮助。

另外希望手机阅读的同学可以来我的 公众号:袁厨的算法小屋 两个平台同步,想要和题友一起刷题,互相监督的同学,可以在我的小屋点击刷题小队进入。

142. 环形链表 II

题目描述:

今天给大家介绍比较有特点的题目,也是一个特别经典的题目,判断链表中有没有环,并返回环的入口。

我们可以先做一下这个题目,就是如何判断链表中是否有环呢?下图则为链表存在环的情况。

image-20201027175552961

判断链表是否有环是很简单的一个问题,我们只需利用之前的快慢指针即可,大家想一下,指针只要进入环内就只能在环中循环,那么我们可以利用快慢指针,虽然慢指针的速度小于快指针但是,总会进入环中,当两个指针都处于环中时,因为移动速度不同,两个指针必会相遇。

我们可以这样假设,两个孩子在操场顺时针跑步,一个跑的快,一个跑的慢,跑的快的那个孩子总会追上跑的慢的孩子。

代码请参考【动画模拟】leetcode 141 环形链表

判断链表是不是含有环很简单,但是我们想找到环的入口可能就没有那么容易了。(入口则为下图绿色节点)

然后我们返回的则为绿色节点的索引则返回2。

image-20201027180921770

HashSet

我们可以利用HashSet来做之前的文章说过HashSet是一个不允许有重复元素的集合。所以我们通过HashSet来保存链表节点对链表进行遍历如果链表不存在环则每个节点都会被存入环中但是当链表中存在环时则会发重复存储链表节点的情况所以当我们发现HashSet中含有某节点时说明该节点为环的入口返回即可。

下图中,存储顺序为 0123456**2 **因为2已经存在则返回。

image-20201027182649669

Java Code:

public class Solution {
    public ListNode detectCycle(ListNode head) {
         if (head == null) {
             return head;
         }
         if (head.next == null) {
             return head.next;
         }
         //创建新的HashSet用于保存节点
         HashSet<ListNode> hash = new HashSet<ListNode>();
         //遍历链表
         while (head != null) {
             //判断哈希表中是否含有某节点,没有则保存,含有则返回该节点
             if (hash.contains(head)) {
                 return head;
             }
             //不含有,则进行保存,并移动指针
             hash.add(head);
             head = head.next;
         } 
        return head;
    }
}

C++ Code:

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if (head == nullptr) return head;
        if (head->next == nullptr) return head->next;
        //创建新的HashSet用于保存节点
        set<ListNode *> hash;
        //遍历链表
        while (head != nullptr) {
            //判断哈希表中是否含有某节点,没有则保存,含有则返回该节点
            if (hash.count(head)) {
                return head;
            }
            //不含有,则进行保存,并移动指针
            hash.insert(head);
            head = head->next;
        }
        return head;
    }
};

JS Code:

var detectCycle = function(head) {
    if (head === null) return head;
    if (head.next === null) return head.next;
    //创建新的HashSet用于保存节点
    let hash = new Set();
    //遍历链表
    while (head !== null) {
        //判断哈希表中是否含有某节点,没有则保存,含有则返回该节点
        if (hash.has(head)) {
            return head;
        }
        //不含有,则进行保存,并移动指针
        hash.add(head);
        head = head.next;
    }
    return head;
};

Python Code:

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        if head is None:
            return head
        if head.next is None:
            return head.next
        # 创建新的HashSet用于保存节点
        hash = set()
        while head is not None:
            # 判断哈希表中是否含有某节点,没有则保存,含有则返回该节点
            if head in hash:
                return head
            # 不含有,则进行保存,并移动指针
            hash.add(head)
            head = head.next
        return head

快慢指针

这个方法是比较巧妙的方法,但是不容易想到,也不太容易理解,利用快慢指针判断是否有环很容易,但是判断环的入口就没有那么容易,之前说过快慢指针肯定会在环内相遇,见下图。

image-20201027184755943

上图黄色节点为快慢指针相遇的节点,此时

快指针走的距离:a+(b+c)n+bn代表圈数。

很容易理解b+c为环的长度a为直线距离b为绕了n圈之后又走了一段距离才相遇所以相遇时走的总路程为a+(b+c)n+b合并同类项得a+(n+1)b+nc。

慢指针走的距离:a+(b+c)m+bm代表圈数。

然后我们设快指针得速度是慢指针的2倍含义为相同时间内快指针走过的距离是慢指针的2倍。

**a+(n+1)b+nc=2[a+(m+1)b+mc]整理得a+b=(n-2m)(b+c)**那么我们可以从这个等式上面发现什么呢?

b+c为一圈的长度。也就是说a+b等于n-2m个环的长度。为了便于理解我们看一种特殊情况当n-2m等于1那么a+b=b+c整理得a=c。此时我们只需重新释放两个指针一个从head释放一个从相遇点释放速度相同因为a=c所以他俩必会在环入口处相遇则求得入口节点索引。

算法流程:

1.设置快慢指针快指针速度为慢指针的2倍。

2.找出相遇点。

3.在head处和相遇点同时释放相同速度且速度为1的指针两指针必会在环入口处相遇。

环形链表2

题目代码

Java Code:

public class Solution {
    public ListNode detectCycle(ListNode head) {
       //快慢指针
        ListNode fast = head;
        ListNode slow = head;
        //设置循环条件
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            //相遇
            if (fast == slow) {
                //设置一个新的指针从头节点出发慢指针速度为1所以可以使用慢指针从相遇点出发
                ListNode newptr = head;
                while (newptr != slow) {
                    slow = slow.next;
                    newptr = newptr.next;
                }
                //在环入口相遇
                return slow;
            }
        } 
        return null;
    }
}

C++ Code:

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        //快慢指针
        ListNode * fast = head;
        ListNode * slow = head;
        //设置循环条件
        while (fast != nullptr && fast->next != nullptr) {
            fast = fast->next->next;
            slow = slow->next;
            //相遇
            if (fast == slow) {
                //设置一个新的指针从头节点出发慢指针速度为1所以可以使用慢指针从相遇点出发
                ListNode * newnode = head;
                while (newnode != slow) {
                    slow = slow->next;
                    newnode = newnode->next;
                }
                //在环入口相遇
                return slow;
            }
        } 
        return nullptr;
    }
};

JS Code:

var detectCycle = function(head) {
    //快慢指针
    let fast = head;
    let slow = head;
    //设置循环条件
    while (fast && fast.next) {
        fast = fast.next.next;
        slow = slow.next;
        //相遇
        if (fast == slow) {
            let newptr = head;
            //设置一个新的指针从头节点出发慢指针速度为1所以可以使用慢指针从相遇点出发
            while (newptr != slow) {
                slow = slow.next;
                newptr = newptr.next;
            }
            //在环入口相遇
            return slow;
        }
    }
    return null;
};

Python Code:

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        # 快慢指针
        fast = head
        slow = head
        # 设置循环条件
        while fast is not None and fast.next is not None:
            fast = fast.next.next
            slow = slow.next
            # 相遇
            if fast is slow:
                # 设置一个新的指针从头节点出发慢指针速度为1所以可以使用慢指针从相遇点出发
                newptr = head
                while newptr is not slow:
                    slow = slow.next
                    newptr = newptr.next
                # 在环入口相遇
                return slow