力扣 2115 从给定原材料中找到所有可以做出的菜
题目描述
给定一个字符串数组 recipes(食谱)、一个二维字符串数组 ingredients(每个食谱所需的食材列表)以及一个字符串数组 supplies(当前拥有的所有食材)。每道菜做好后也可以作为其他菜的食材。返回所有可以做出的食谱列表。
解题方法
思路分析
本题的核心在于处理食谱与食材之间的依赖关系。一道菜依赖若干种食材,而做好的菜又可以作为其他菜的食材,这种"前置条件满足后才能解锁"的特性决定了可以使用拓扑排序来解决。
将每种食材和食谱都看作图上的节点,从食材向需要它的食谱连有向边,同时记录每个食谱的入度(即所需食材的种类数)。初始时,所有 supplies 中的食材都是可用的,将它们作为 BFS 的起点。每当一个食谱的所有前置食材都备齐(入度减为 0),该食谱便可被做出,并作为新的可用节点去解锁更多食谱。
算法步骤
- 建图与初始化入度:遍历每个食谱,将其每种食材与食谱建立有向边,并记录每个食谱的入度。
- BFS 拓扑遍历:将已有食材入队,依次出队并减少依赖它的食谱的入度,当某个食谱入度为 0 时将其入队并加入答案。
- 返回结果:所有能做出的食谱即为答案。
代码注释与解析
class Solution:
def findAllRecipes(self, recipes: List[str], ingredients: List[List[str]], supplies: List[str]) -> List[str]:
# 邻接表:记录每种食材可以供应哪些食谱
g = defaultdict(list)
# 入度表:记录每个食谱还缺少多少种食材
deg = {}
for r, ing in zip(recipes, ingredients):
for s in ing:
# 建立连接:从食材 s 指向食谱 r
g[s].append(r)
# 初始化该食谱的入度 = 所需食材数量
deg[r] = len(ing)
ans = []
# 原料是不需要先前条件的,直接作为 BFS 起点
q = deque(supplies)
while q:
# 遍历当前可用食材所指向的所有食谱
for r in g[q.popleft()]:
# 满足该食谱的一种食材需求
deg[r] -= 1
# 当所有食材都已备齐
if deg[r] == 0:
# 该食谱变为可用资源,入队
q.append(r)
# 记录答案
ans.append(r)
return ans代码要点说明
- 邻接表
g:以食材/食谱为键,值为依赖该食材的食谱列表,实现了从食材到食谱的快速查找。 - 入度表
deg:记录每个食谱当前尚未满足的食材种类数,初始值为食谱所需的食材总数。 - 队列
q:存储当前可用的所有资源(包括初始原料和已做出的菜),是整个拓扑排序的驱动核心。 - BFS 过程:每次从队列中取出一个可用资源,将其所有出边的目标节点(食谱)的入度减 1,当入度归零时意味着该食谱可以做出。
复杂度分析
- 时间复杂度:O(N + M),其中 N 为食谱数量,M 为所有食谱的食材总数(即
ingredients中所有子数组的长度之和)。建图需遍历所有食材,BFS 中每条有向边恰好被处理一次。 - 空间复杂度:O(N + M),邻接表
g存储 M 条边,入度表deg存储 N 个食谱的入度,队列q最多存储 N 个元素。
力扣 2115 从给定原材料中找到所有可以做出的菜
https://lincodex.cn/index.php/archives/40/