LeetCode 142. 环形链表 II | Python

142. 环形链表 II


题目来源:力扣(LeetCode)https://leetcode-cn.com/problems/linked-list-cycle-ii/

题目


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

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos-1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。

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

进阶:

  • 你是否可以不用额外空间解决此题?

示例 1:

示例 1

输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。

示例 2:

示例 2

输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。

示例 3:

示例 3

输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。

提示:

  • 链表中节点的数目范围在范围 [0, 104]
  • -105 <= Node.val <= 105
  • pos 的值为 -1 或者链表中的一个有效索引

解题思路


思路:哈希表、双指针

在解决本题前,可以先尝试站内第 141 题 环形链表

不过这里与第 141 题不同的地方在于:

  • 第 141 题,只需要求得是否有环,而本题需要求得环的入口节点。

现在,仔细看本题的要求:

  • 给定一个链表,返回链表开始入环的第一个节点。
  • 若链表无环,返回 None。
哈希表

在这里,我们可以遍历链表,然后用哈希集合去记录遍历的节点。如果在遍历的过程中,发现当前遍历的节点存在于哈希集合中,那么我们就能够判断该链表有环,并且,当前节点就是链表开始入环的第一个节点。

具体的代码示例如下。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        # 定义哈希集合
        signed = set()
        # 遍历链表
        while head:
            # 判断是否存在于集合中
            # 存在返回当前节点
            if head in signed:
                return head
            # 不存在则将节点放入集合中,继续遍历
            signed.add(head)
            head = head.next
        # 链表无环,返回 None
        return None
复杂度分析
  • 时间复杂度: O ( N ) O(N) O(N)。其中 N N N 为链表节点的数量,遍历需访问所有节点。

  • 空间复杂度: O ( N ) O(N) O(N) N N N 为链表节点的数量,遍历时,需将所有节点都存放到哈希集合中。

双指针

这道题,我们也可以使用双指针的方法来解决。

首先我们先定义快慢指针 fast、slow,同时指向链表头部,然后 fast 指针每次移动两步,而 slow 指针每次移动一步。如果有环,那么两指针会在环中相遇。在这里我们先看下这个过程中,fast 指针与 slow 指针之间的关系,先看下图:

图示

在这里,从链表头部到入环点的长度为 x,环中整圈长度为 y,而 a 则是入环点到两指针相遇点之间的长度。也就是说,slow 指针经入环点进入之后,再走长度 a 的距离可与 fast 指针相遇,此时 slow 指针走过的距离为 x + a。假设此时 fast 在环中已经先走了 n 圈,再走了 a 的距离与 slow 指针相遇,那么此时 fast 指针走过的距离为 x + ny + a(其中 n 未知)。

因为 fast 指针每次移动两步,slow 指针每次移动一步。那么 fast 指针走过的距离始终是 slow 指针走过距离的两倍。结合上面的式子,可得:2 * (x+a) = x + n*y + a​

对上面的式子进行化简,可得 x = n*y - a,这里可能看不出什么,将式子再变化一下得:x = (n-1) * y + y - a

由上图,我们可以知道,y-a,就是两指针相遇点到入环点的距离。y 是环一圈的长度,(n-1)*y 表示走了 n-1 圈。也就说指针走了 n-1 圈后,再走 y-a 的距离,可以回到入环点,而此时走过的距离为 x。前面也说过 x 就是链表头部到入环点的长度。

那么此时我们再定义一个指针 extra 指向链表头部,当 extra 到达入环点时,走过的距离为 x,而环中的 slow 指针也会到达入环点,此时两指针相遇。

具体的代码实现如下。

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        # 定义快慢指针指向链表头部
        slow = head
        fast = head

        # 先求第一次相遇点
        while True:
            if not fast or not fast.next:
                return
            slow = slow.next
            fast = fast.next.next
            if slow == fast:
                break
        
        # 定义额外的指针 extra 指向链表头部
        # 根据前面的分析,extra 与 slow 指针相遇会到达入环点
        extra = head
        while extra != slow:
            extra = extra.next
            slow = slow.next
        
        return extra
复杂度分析
  • 时间复杂度: O ( N ) O(N) O(N),其中 N N N 为链表的长度。第一次相遇时,slow 指针走过的距离为 x + a < x + y,第二次 slow 指针走过的距离为 x < x+y,其中 x 表示链表头部到入环的长度,y 表示环的长度, a 则是入环点到相遇点的长度。x + y 也就是链表的长度。所以总体时间复杂度为线性复杂度。
  • 空间复杂度: O ( 1 ) O(1) O(1),只定义了三个指针,使用了常数大小的额外空间。

欢迎关注


公众号 【书所集录


如有错误,烦请指出,欢迎指点交流。

你好!对于LeetCode上的问题994.腐烂的橘子,你可以使用Python来解决。下面是一个示例代码: ```python from collections import deque def orangesRotting(grid): # 记录网格的行数和列数 row, col = len(grid), len(grid[0]) # 定义四个方向:上、下、左、右 directions = [(-1, 0), (1, 0), (0, -1), (0, 1)] # 使用队列来保存腐烂的橘子的位置 queue = deque() # 记录新鲜橘子的数量 fresh_count = 0 # 遍历整个网格,初始化队列和新鲜橘子的数量 for i in range(row): for j in range(col): if grid[i][j] == 2: # 腐烂的橘子 queue.append((i, j)) elif grid[i][j] == 1: # 新鲜橘子 fresh_count += 1 # 如果新鲜橘子的数量为0,直接返回0 if fresh_count == 0: return 0 # 初始化分钟数 minutes = 0 # 开始进行BFS,直到队列为空 while queue: # 记录当前分钟数下,队列中的元素数量 size = len(queue) # 遍历当前分钟数下的所有腐烂的橘子 for _ in range(size): x, y = queue.popleft() # 遍历四个方向 for dx, dy in directions: nx, ny = x + dx, y + dy # 判断新位置是否在网格内,并且是新鲜橘子 if 0 <= nx < row and 0 <= ny < col and grid[nx][ny] == 1: # 将新鲜橘子变为腐烂状态 grid[nx][ny] = 2 # 将新鲜橘子的位置加入队列 queue.append((nx, ny)) # 新鲜橘子的数量减1 fresh_count -= 1 # 如果当前分钟数下,没有新鲜橘子了,结束循环 if fresh_count == 0: break # 每遍历完一层,分钟数加1 minutes += 1 # 如果最后还有新鲜橘子,返回-1,否则返回分钟数 return -1 if fresh_count > 0 else minutes ``` 你可以将给定的网格作为参数传递给`orangesRotting`函数来测试它。请注意,该代码使用了BFS算法来遍历橘子,并计算腐烂的分钟数。希望能对你有所帮助!如果有任何疑问,请随时问我。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值