(day19)*leetcode 994.腐烂的橘子

描述

在给定的 m x n 网格 grid 中,每个单元格可以有以下三个值之一:

  • 值 0 代表空单元格;
  • 值 1 代表新鲜橘子;
  • 值 2 代表腐烂的橘子。

每分钟,腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。

返回 直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1 。

示例 1:

输入:grid = [[2,1,1],[1,1,0],[0,1,1]]
输出:4

示例 2:

输入:grid = [[2,1,1],[0,1,1],[1,0,1]]
输出:-1
解释:左下角的橘子(第 2 行, 第 0 列)永远不会腐烂,因为腐烂只会发生在 4 个方向上。

示例 3:

输入:grid = [[0,2]]
输出:0
解释:因为 0 分钟时已经没有新鲜橘子了,所以答案就是 0 。

提示:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 10
  • grid[i][j] 仅为 01 或 2

Leecode题解:腐烂的橘子

解题思路:广度优先搜索(BFS)

 首先分别将腐烂的橘子和新鲜的橘子保存在两个集合中;

模拟广度优先搜索的过程,方法是判断在每个腐烂橘子的四个方向上是否有新鲜橘子,如果有就腐烂它。每腐烂一次时间加 1,并剔除新鲜集合里腐烂的橘子;

当橘子全部腐烂时结束循环。

腐烂的橘子

class Solution:
    def orangesRotting(self, grid: List[List[int]]) -> int:
        # 获取网格的行数和列数
        row = len(grid)
        col = len(grid[0])
        
        # 使用集合推导式找到所有腐烂橘子的坐标 (值为2)
        bad = {(i, j) for i in range(row) for j in range(col) if grid[i][j] == 2}
        # 使用集合推导式找到所有新鲜橘子的坐标 (值为1)
        fresh = {(i, j) for i in range(row) for j in range(col) if grid[i][j] == 1}
        # 初始化计数器
        count = 0
        
        # 当还有新鲜橘子时
        while fresh:
            # 如果没有腐烂橘子可以感染新鲜橘子,返回 -1
            if not bad:
                return -1
            # 找到下一轮会腐烂的新橘子
            bad = {(i + di, j + dj) for i, j in bad for di, dj in [(0, 1), (0, -1), (1, 0), (-1, 0)] if (i + di, j + dj) in fresh}
            # 将新腐烂的橘子从新鲜橘子集合中移除
            fresh -= bad
            # 增加轮数计数器
            count += 1
        # 返回所需的轮数
        return count
关键点
  • 集合运算:使用集合存储腐烂和新鲜橘子的坐标,并通过集合运算快速更新和查找。
  • 广度优先搜索(BFS):通过每轮找到当前所有腐烂橘子周围的新鲜橘子,模拟感染过程,直至没有新鲜橘子。
  • 四个方向上的移动:一般使用如下方法实现四个方向的移动:

    # 设初始点为 (i, j)
    for di, dj in [(0, 1), (0, -1), (1, 0), (-1, 0)]: # 上、下、左、右
        i + di, j + dj
    复杂度分析
  • 时间复杂度:O(mn)。
  • 空间复杂度:O(mn)。

 

思路二:队列实现

前面的实现方法不是很普遍,BFS 的标准实现还是用队列。

队列实现 BFS 的方法相对固定,大致分三步:

初始化队列;

最开始的坏橘子全部入队,具体是橘子的坐标和 time;

循环:当队列不为空时,先弹出队首元素,然后将这个元素能够腐烂的橘子全部入队。

class Solution:
    def orangesRotting(self, grid: List[List[int]]) -> int:
        # 获取网格的行数、列数,并初始化计数器
        row, col, count = len(grid), len(grid[0]), 0
        # 定义四个方向的移动向量
        directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
        # 初始化队列
        queue = []
        
        # 遍历网格,找到所有腐烂橘子并将其加入队列,初始化腐烂橘子的轮数为0
        for i in range(row):
            for j in range(col):
                if grid[i][j] == 2:
                    queue.append((i, j, count))
        
        # 开始BFS
        while queue:
            # 取出队列的第一个元素
            i, j, count = queue.pop(0)
            # 遍历四个方向
            for di, dj in directions:
                # 检查新位置是否在网格内且是新鲜橘子
                if 0 <= i + di < row and 0 <= j + dj < col and grid[i + di][j + dj] == 1:
                    # 将新鲜橘子变为腐烂橘子
                    grid[i + di][j + dj] = 2
                    # 将新腐烂的橘子加入队列,并将腐烂轮数加1
                    queue.append((i + di, j + dj, count + 1))
        
        # 检查网格中是否还有新鲜橘子
        for row in grid:
            if 1 in row:
                return -1
        
        # 返回腐烂的轮数
        return count

 

关键点
  • 队列:使用队列实现广度优先搜索,每轮处理队列中的所有腐烂橘子,并将新腐烂的橘子加入队列。
  • 方向向量:定义四个方向的移动向量,方便检查每个腐烂橘子的周围位置。
  • BFS:逐层扩展腐烂橘子的范围,直到所有新鲜橘子都被腐烂或无法继续腐烂。
优化点
  • 当前代码已比较高效,若要进一步优化,可以考虑使用双端队列(deque)替换列表,以提高 pop(0) 操作的性能。

 

数据结构–双端队列

双端队列(Double-ended Queue,简称Deque)是一种具有队列和栈特性的数据结构,可以在队列的两端进行插入和删除操作。双端队列允许从前端和后端同时进行插入和删除操作,因此可以称为“两端都可以进出的队列”。

双端队列的特点包括:

    可以在队列的头部和尾部进行插入和删除操作。
    元素的插入和删除操作可以分别称为入队和出队操作。
    可以实现先进先出(FIFO)和后进先出(LIFO)两种操作方式。
    可以用于实现栈、队列以及其他需要在两端进行插入和删除操作的场景。

双端队列的常见操作包括:

    在队列头部插入元素(头部入队):将元素插入到队列头部。
    在队列尾部插入元素(尾部入队):将元素插入到队列尾部。
    从队列头部删除元素(头部出队):删除队列头部的元素并返回。
    从队列尾部删除元素(尾部出队):删除队列尾部的元素并返回。
    获取队列头部的元素:返回队列头部的元素,但不删除。
    获取队列尾部的元素:返回队列尾部的元素,但不删除。
    判断队列是否为空:判断队列中是否有元素。
    获取队列中的元素个数:返回队列中元素的个数。

双端队列的实现方式有多种,包括使用数组、链表等数据结构。具体的实现方式可以根据不同的需求和场景选择合适的方式。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值