We build a directed graph with vertex set as the union of recipes and ingredients. An edge represents
. A topological order of recipes from this graph will give us the recipes we can make.
We use in-degree approach. Say we start with supplies as the sources, therefore vertices with in-degree = 0. Then the new sources found at the end of 1st iteration are the recipes we were able to make using the supplies and they have become our new supplies. We keep peeling layers of sources and accumulate can-be-made recipes which is also a topological order of a subset of recipes. If a recipe never becomes a source, it cannot be made.

Time and space is linear in the size of the graph. Say is the set of recipes,
is the set of ingredients, and
is the max number of ingredients needed for a recipe. Then, the size of vertex set is
and the size of edge set is
.
So, time and space complexity:
class Solution:
def build_graph(self, recipes, ingredients) -> Dict[str, Set[str]]:
# V: { recipes U ingredients }
# E: { (u, v) : u is-required-for v }
adj_list = defaultdict(set)
for i, r in enumerate(recipes):
for ing in ingredients[i]:
adj_list[ing].add(r)
return adj_list
def build_indegree_map(self, adj_list) -> Dict[str, int]:
inmap = defaultdict(int)
for u in adj_list:
for v in adj_list[u]:
inmap[v] += 1
return inmap
def findAllRecipes(self, recipes: List[str], ingredients: List[List[str]], supplies: List[str]) -> List[str]:
adj_list = self.build_graph( recipes, ingredients )
inmap = self.build_indegree_map( adj_list )
curr_sources = supplies
topo_order = []
while curr_sources:
new_sources = []
for u in curr_sources:
for v in adj_list[u]:
inmap[v] -= 1
if inmap[v] == 0:
new_sources.append(v)
topo_order.extend(new_sources)
curr_sources = new_sources
return topo_order
Leave a comment