可视化图解算法:链表中环的入口节点(环形链表 II)

1. 题目

描述

给一个长度为n链表,若其中包含环,请找出该链表的环的入口结点,否则,返回null。

数据范围:n≤10000,1<=结点值<=10000

要求:空间复杂度 O(1),时间复杂度 O(n)

例如,输入{1,2},{3,4,5}时,对应的环形链表如下图所示:

可以看到环的入口结点的结点值为3,所以返回结点值为3的结点。

输入描述:

输入分为2段,第一段是入环前的链表部分,第二段是链表环的部分,后台会根据第二段是否为空将这两段组装成一个无环或者有环单链表

返回值描述:

返回链表的环的入口结点即可,我们后台程序会打印这个结点对应的结点值;若没有,则返回对应编程语言的空结点即可。

示例1

输入: {1,2},{3,4,5}

返回值:3

说明: 返回环形链表入口结点,我们后台程序会打印该环形链表入口结点对应的结点值,即3

示例2

输入: {1},{}

返回值:

"null"

说明:没有环,返回对应编程语言的空结点,后台程序会打印"null"

示例3

输入:

{},{2}

返回值:

2

说明:环的部分只有一个结点,所以返回该环形链表入口结点,后台程序打印该结点对应的结点值,即2

2. 解题思路

如果要求解链表中环的入口节点(开始入环的第一个节点),则该链表肯定为环形链表。环形链表的判断参考上一篇文章《可视化图解算法:判断链表中是否有环(环形链表)》。

环的入口节点同样可以使用快慢指针来判断。

假如链表结构如下图所示,环入口为3节点:

这时我们可以采用以下步骤来查找入口节点。

步骤一:定义快慢指针。

步骤二:移动快慢指针。

移动快慢指针,如果不是环形链表直接返回。否则继续移动移动,直到快慢指针相遇(第一次相遇)。

步骤三: 更改快指针fast的指向。

让fast重新指向链表的头节点,慢指针保存不动。再重新移动快慢指针。

步骤四 :快慢指针第二次相遇,相遇节点为环入口节点。

如果文字描述的不太清楚,你可以参考视频的详细讲解。

3. 编码实现

3.1 Python编码实现

ini 复制代码
class ListNode:
    def __init__(self, x):
        self.val = x  # 链表的数值域
        self.next = None  # 链表的指针域
​
​
# 从链表节点尾部添加节点
def insert_node(node, value):
    if node is None:
        print("node is None")
        return
    # 创建一个新节点
    new_node = ListNode(value)
    cur = node
    # 找到链表的末尾节点
    while cur.next is not None:
        cur = cur.next
    # 末尾节点的next指针域连接新节点
    cur.next = new_node
​
​
# 打印链表(从链表头结点开始打印链表的值)
def print_node(node):
    cur = node
    # 遍历每一个节点
    while cur is not None:
        print(cur.val, end="\t")
        cur = cur.next  # 更改指针变量的指向
    print()
​
​
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
#
# @param pHead ListNode类
# @return ListNode类
#
class Solution:
    def EntryNodeOfLoop(self, pHead):
        # write code here
        # 1. 定义快慢指针
        fast = pHead
        slow = pHead
​
        # 2.  移动快慢指针
        # while 循环结束有3个条件:fast == nil(fast.next == nil) 或者fast == slow
        while fast is not None and fast.next is not None:
            fast = fast.next.next
            slow = slow.next
            if fast == slow:
                break  # 快慢指针第1次相遇:存在环
​
        # 3.  是否有环判断: 若是快指针指向null 或 快指针的下一个节点为null,则不存在环
        if fast is None or fast.next is None:
            return None
​
        # 4.  更改快指针的指向:存在环,fast指向链表的头节点
        fast = pHead
        while fast != slow:
            fast = fast.next  # 快指针每次移动1个节点
            slow = slow.next
​
        # 5.  快慢指针第2次相遇:相遇节点为环入口
        return fast
​
​
if __name__ == '__main__':
    head = ListNode(1)
    head.next = ListNode(2)
    head.next.next = ListNode(3)
    head.next.next.next = ListNode(4)
    head.next.next.next.next = ListNode(5)
​
    head.next.next.next.next.next = head.next.next
​
    s = Solution()
    node = s.EntryNodeOfLoop(head)
    print(node.val)
​

3.2 Java编码实现

ini 复制代码
package LL07;
​
​
public class Main {
    //定义链表节点
    static class ListNode {
        private int val;  //链表的数值域
        private ListNode next; //链表的指针域
​
        public ListNode(int data) {
            this.val = data;
            this.next = null;
        }
    }
​
    //添加链表节点
    private static void insertNode(ListNode node, int data) {
        if (node == null) {
            return;
        }
        //创建一个新节点
        ListNode newNode = new ListNode(data);
        ListNode cur = node;
        //找到链表的末尾节点
        while (cur.next != null) {
            cur = cur.next;
        }
        //末尾节点的next指针域连接新节点
        cur.next = newNode;
    }
​
    //打印链表(从头节点开始打印链表的每一个节点)
    private static void printNode(ListNode node) {
        ListNode cur = node;
        //遍历每一个节点
        while (cur != null) {
            System.out.print(cur.val + "\t");
            cur = cur.next; //更改指针变量的指向
        }
        System.out.println();
    }
​
​
    public static class Solution {
        /**
         * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
         *
         * @param pHead ListNode类
         * @return ListNode类
         */
        public ListNode EntryNodeOfLoop(ListNode pHead) {
            // write code here
            // 1. 定义快慢指针
            ListNode fast = pHead;
            ListNode slow = pHead;
​
            // 2. 移动快慢指针
            //while循环结束有3个条件:fast == null(fast.next == null)或者 fast==slow
            while (fast != null && fast.next != null) {
                fast = fast.next.next;//快指针每次移动2个节点
                slow = slow.next;
                if (fast == slow) {
                    break; // 快慢指针第1次相遇:存在环
                }
            }
​
            // 3. 是否有环判断: 若是快指针指向null 或 快指针的下一个节点为null,则不存在环
            if (fast == null || fast.next == null) {
                return null;
            }
​
            // 4. 更改快指针的指向:存在环,fast指向链表的头节点
            fast = pHead;
            while (fast != slow) {
                fast = fast.next; //快指针每次移动1个节点
                slow = slow.next;
            }
            // 5. 快慢指针第2次相遇:相遇节点为环入口
            return fast;
        }
    }
​
    public static void main(String[] args) {
        ListNode head = new ListNode(1);
        head.next = new ListNode(2);
        head.next.next = new ListNode(3);
        head.next.next.next = new ListNode(4);
        head.next.next.next.next = new ListNode(5);
​
        head.next.next.next.next.next = head.next.next;
​
        Solution solution = new Solution();
        ListNode node = solution.EntryNodeOfLoop(head);
        System.out.println(node.val);
    }
}
​

3.3 Golang编码实现

go 复制代码
package main
​
import "fmt"
​
// ListNode 定义链表节点
type ListNode struct {
    Val  int       //链表的数值域
    Next *ListNode //链表的指针域
}
​
/**
 * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 *
 *
 * @param pHead ListNode类
 * @return ListNode类
 */
func EntryNodeOfLoop(pHead *ListNode) *ListNode {
    // write code here
    // 1. 定义快慢指针
    fast := pHead
    slow := pHead
​
    // 2. 移动快慢指针
    //for循环结束有3个条件:fast==nil(fast.next==nil)或者fast==slow
    for fast != nil && fast.Next != nil {
        fast = fast.Next.Next //快指针每次移动2个节点
        slow = slow.Next
        if fast == slow {
            break // 快慢指针第1次相遇:存在环
        }
    }
​
    // 3. 是否有环判断: 若是快指针指向null 或 快指针的下一个节点为null,则不存在环
    if fast == nil || fast.Next == nil {
        return nil
    }
​
    // 4. 更改快指针的指向:存在环,fast指向链表的头节点
    fast = pHead
    for fast != slow {
        fast = fast.Next //快指针每次移动一个节点
        slow = slow.Next
    }
​
    // 5. 快慢指针第2次相遇:相遇节点为环入口
    return fast
}
func main() {
    head := &ListNode{Val: 1}
    head.Next = &ListNode{Val: 2}
    head.Next.Next = &ListNode{Val: 3}
    head.Next.Next.Next = &ListNode{Val: 4}
    head.Next.Next.Next.Next = &ListNode{Val: 5}
​
    head.Next.Next.Next.Next.Next = head.Next.Next
​
    node := EntryNodeOfLoop(head)
    fmt.Println(node.Val)
}
​
// Insert 从链表节点尾部添加节点
func (ln *ListNode) Insert(val int) {
    if ln == nil {
        return
    }
    //创建一个新节点
    newNode := &ListNode{Val: val}
    cur := ln
    //找到链表的末尾节点
    for cur.Next != nil {
        cur = cur.Next
    }
    //末尾节点的next指针域连接新节点
    cur.Next = newNode
}
​
// Print 从链表头结点开始打印链表的值
func (ln *ListNode) Print() {
    if ln == nil {
        return
    }
    cur := ln
    //遍历每一个节点
    for cur != nil {
        fmt.Print(cur.Val, "\t")
        cur = cur.Next //更改指针变量的指向
    }
    fmt.Println()
}
​

如果上面的代码理解的不是很清楚,你可以参考视频的详细讲解。

4.小结

环入口节点的查找可以采用以下步骤:

  • 定义快慢指针;
  • 移动快慢指针;
  • 更改快指针fast的指向
  • 快慢指针第二次相遇,相遇节点为环入口节点。

更多算法视频讲解,你可以从以下地址找到:

对于链表的相关操作,我们总结了一套【可视化+图解】方法,依据此方法来解决链表相关问题,链表操作变得易于理解,写出来的代码可读性高也不容易出错。具体也可以参考视频详细讲解。

今日佳句:穷则变,变则通,通则久。

相关推荐
贫道绝缘子28 分钟前
Leetcode-132.Palindrome Partitioning II [C++][Java]
java·c++·算法·leetcode
spatial_coder2 小时前
deepseek GRPO算法保姆级讲解(数学原理+源码解析+案例实战)
人工智能·算法
earthzhang20212 小时前
《Python深度学习》第四讲:计算机视觉中的深度学习
人工智能·python·深度学习·算法·计算机视觉·numpy·1024程序员节
小咖拉眯3 小时前
蓝桥杯进制问题秒破解法|冲击省一题单(二)
java·数据结构·算法·蓝桥杯
一只_程序媛3 小时前
【leetcode hot 100 146】LRU缓存
算法·leetcode·缓存
float_六七3 小时前
五大基础算法——贪心算法
算法·贪心算法
kkk16222453 小时前
C# Winform 实现换肤,并自定义皮肤功能
java·算法·c#
এ旧栎4 小时前
蓝桥与力扣刷题(蓝桥 星期计算)
java·数据结构·算法·leetcode·职场和发展·蓝桥杯·规律
mit6.8245 小时前
[Sum] C++STL oj常用API
c++·算法·leetcode
槐月初叁5 小时前
C++洛谷基础练习题及解答
开发语言·c++·算法