题目描述
先确认输入、输出、限制条件,再判断能不能把暴力解法优化掉。
思路解析动画文字版
关键转折:别盯着某一个橘子算。把「所有已经烂掉的橘子」一起当 BFS 的起点同时入队,像水波一样一圈圈往外漫:第一圈是 1 分钟后被染的、第二圈是 2 分钟后被染的……漫到最后一圈所用的圈数,就是答案分钟数。
第 0 分钟 · 所有烂橘子一起入队当源点:把当前全部 1 个烂橘子一次性放进队列,它们都算「第 0 分钟」的源头。9 个绿色新鲜橘子等着被传染,2 个灰色空格会挡住腐烂的去路。下面开始一分钟一分钟地往外漫。
第 1 分钟 · 传染 (1,0):烂橘子 (0,0) 把相邻的新鲜橘子 (1,0) 传染。这是 (1,0) 第一次被碰到 —— 多源 BFS 第一次碰到就是最早,所以它就在第 1 分钟烂掉,分钟数定死不再变。
第 1 分钟 · (1,0) 烂掉 → 入队:(1,0) 染成腐烂、标记腐烂分钟 1,并加进队列尾。下一分钟轮到它,再去传染它自己的新鲜邻居 —— 队列严格按分钟(层)推进,保证每个橘子记的都是最早被传染的时刻。
第 1 分钟 · 传染 (0,1):烂橘子 (0,0) 把相邻的新鲜橘子 (0,1) 传染。这是 (0,1) 第一次被碰到 —— 多源 BFS 第一次碰到就是最早,所以它就在第 1 分钟烂掉,分钟数定死不再变。
第 1 分钟 · (0,1) 烂掉 → 入队:(0,1) 染成腐烂、标记腐烂分钟 1,并加进队列尾。下一分钟轮到它,再去传染它自己的新鲜邻居 —— 队列严格按分钟(层)推进,保证每个橘子记的都是最早被传染的时刻。
⏱ 第 1 分钟结束 · 这一圈全染完:第 1 分钟这一圈相邻的新鲜橘子都传染完了,时钟走到 1。队列里现在装着刚烂的橘子,它们将在下一分钟继续往外漫一圈。
第 2 分钟 · 传染 (1,1):烂橘子 (1,0) 把相邻的新鲜橘子 (1,1) 传染。这是 (1,1) 第一次被碰到 —— 多源 BFS 第一次碰到就是最早,所以它就在第 2 分钟烂掉,分钟数定死不再变。
第 2 分钟 · (1,1) 烂掉 → 入队:(1,1) 染成腐烂、标记腐烂分钟 2,并加进队列尾。下一分钟轮到它,再去传染它自己的新鲜邻居 —— 队列严格按分钟(层)推进,保证每个橘子记的都是最早被传染的时刻。
第 2 分钟 · 传染 (0,2):烂橘子 (0,1) 把相邻的新鲜橘子 (0,2) 传染。这是 (0,2) 第一次被碰到 —— 多源 BFS 第一次碰到就是最早,所以它就在第 2 分钟烂掉,分钟数定死不再变。
第 2 分钟 · (0,2) 烂掉 → 入队:(0,2) 染成腐烂、标记腐烂分钟 2,并加进队列尾。下一分钟轮到它,再去传染它自己的新鲜邻居 —— 队列严格按分钟(层)推进,保证每个橘子记的都是最早被传染的时刻。
⏱ 第 2 分钟结束 · 这一圈全染完:第 2 分钟这一圈相邻的新鲜橘子都传染完了,时钟走到 2。队列里现在装着刚烂的橘子,它们将在下一分钟继续往外漫一圈。
第 3 分钟 · 传染 (2,1):烂橘子 (1,1) 把相邻的新鲜橘子 (2,1) 传染。这是 (2,1) 第一次被碰到 —— 多源 BFS 第一次碰到就是最早,所以它就在第 3 分钟烂掉,分钟数定死不再变。
第 3 分钟 · (2,1) 烂掉 → 入队:(2,1) 染成腐烂、标记腐烂分钟 3,并加进队列尾。下一分钟轮到它,再去传染它自己的新鲜邻居 —— 队列严格按分钟(层)推进,保证每个橘子记的都是最早被传染的时刻。
第 3 分钟 · 传染 (0,3):烂橘子 (0,2) 把相邻的新鲜橘子 (0,3) 传染。这是 (0,3) 第一次被碰到 —— 多源 BFS 第一次碰到就是最早,所以它就在第 3 分钟烂掉,分钟数定死不再变。
第 3 分钟 · (0,3) 烂掉 → 入队:(0,3) 染成腐烂、标记腐烂分钟 3,并加进队列尾。下一分钟轮到它,再去传染它自己的新鲜邻居 —— 队列严格按分钟(层)推进,保证每个橘子记的都是最早被传染的时刻。
⏱ 第 3 分钟结束 · 这一圈全染完:第 3 分钟这一圈相邻的新鲜橘子都传染完了,时钟走到 3。队列里现在装着刚烂的橘子,它们将在下一分钟继续往外漫一圈。
第 4 分钟 · 传染 (2,2):烂橘子 (2,1) 把相邻的新鲜橘子 (2,2) 传染。这是 (2,2) 第一次被碰到 —— 多源 BFS 第一次碰到就是最早,所以它就在第 4 分钟烂掉,分钟数定死不再变。
第 4 分钟 · (2,2) 烂掉 → 入队:(2,2) 染成腐烂、标记腐烂分钟 4,并加进队列尾。下一分钟轮到它,再去传染它自己的新鲜邻居 —— 队列严格按分钟(层)推进,保证每个橘子记的都是最早被传染的时刻。
第 4 分钟 · 传染 (1,3):烂橘子 (0,3) 把相邻的新鲜橘子 (1,3) 传染。这是 (1,3) 第一次被碰到 —— 多源 BFS 第一次碰到就是最早,所以它就在第 4 分钟烂掉,分钟数定死不再变。
第 4 分钟 · (1,3) 烂掉 → 入队:(1,3) 染成腐烂、标记腐烂分钟 4,并加进队列尾。下一分钟轮到它,再去传染它自己的新鲜邻居 —— 队列严格按分钟(层)推进,保证每个橘子记的都是最早被传染的时刻。
⏱ 第 4 分钟结束 · 这一圈全染完:第 4 分钟这一圈相邻的新鲜橘子都传染完了,时钟走到 4。队列里现在装着刚烂的橘子,它们将在下一分钟继续往外漫一圈。
第 5 分钟 · 传染 (2,3):烂橘子 (2,2) 把相邻的新鲜橘子 (2,3) 传染。这是 (2,3) 第一次被碰到 —— 多源 BFS 第一次碰到就是最早,所以它就在第 5 分钟烂掉,分钟数定死不再变。
第 5 分钟 · (2,3) 烂掉 → 入队:(2,3) 染成腐烂、标记腐烂分钟 5,并加进队列尾。下一分钟轮到它,再去传染它自己的新鲜邻居 —— 队列严格按分钟(层)推进,保证每个橘子记的都是最早被传染的时刻。
⏱ 第 5 分钟结束 · 这一圈全染完:第 5 分钟这一圈相邻的新鲜橘子都传染完了,时钟走到 5。队列里现在装着刚烂的橘子,它们将在下一分钟继续往外漫一圈。
⏱ 漫完 · 所有新鲜橘子都烂了:队列空了,水波漫遍每个能到的橘子,最后一个在第 5 分钟烂掉。最大的腐烂分钟数 5 就是答案。如果此刻还剩任何一个新鲜橘子,就该返回 -1,本例全烂完没有死角。
边界:一开始就没有新鲜橘子(全是空格或全是烂橘子)→ 0 分钟;有新鲜橘子被空格完全围死、腐烂漫不到它 → 返回 -1;全是新鲜橘子但一个烂橘子都没有 → 也返回 -1。
高频追问:问:为什么所有烂橘子要同时入队?答:腐烂是同时发生的,多源 BFS 一圈代表一分钟,谁先碰到谁就先烂,自动得到最早时刻。问:怎么判 -1?答:BFS 漫完后还有新鲜计数没归零,说明那些橘子被空格围死,永远烂不到。
参考代码
from collections import dequedef orangesRotting(grid): R, C = len(grid), len(grid[0]) q, fresh = deque(), 0 for i in range(R): for j in range(C): if grid[i][j] == 2: q.append((i, j)) elif grid[i][j] == 1: fresh += 1 minutes = 0 dirs = ((1,0),(-1,0),(0,1),(0,-1)) while q and fresh: for _ in range(len(q)): i, j = q.popleft() for di, dj in dirs: ni, nj = i + di, j + dj if 0 <= ni < R and 0 <= nj < C \ and grid[ni][nj] == 1: grid[ni][nj] = 2 fresh -= 1 q.append((ni, nj)) minutes += 1 return -1 if fresh else minutes复杂度
易错点
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
墙与门
LeetCode 286 · 中等 · 沿着 图 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题