2115. Find All Possible Recipes from Given Supplies
Problem Description
You are given information about n
different recipes and need to determine which ones you can make.
Input:
recipes
: An array of strings whererecipes[i]
is the name of the i-th recipeingredients
: A 2D array whereingredients[i]
contains all the ingredients needed to makerecipes[i]
supplies
: An array of strings representing the ingredients you initially have (with infinite quantity of each)
Key Points:
- To create a recipe, you must have ALL its required ingredients
- A recipe can be an ingredient for another recipe (recipes can depend on other recipes)
- Once you create a recipe, it becomes available as an ingredient for other recipes
- Two recipes may contain each other in their ingredients (circular dependencies possible)
Output: Return a list of all recipes you can create. The order doesn't matter.
Example:
If you have recipes = ["bread", "sandwich"]
, ingredients = [["flour", "water"], ["bread", "meat"]]
, and supplies = ["flour", "water", "meat"]
:
- You can make "bread" because you have both "flour" and "water"
- Once "bread" is made, you can make "sandwich" because you now have "bread" and "meat"
- Return
["bread", "sandwich"]
The solution uses topological sorting to handle the dependency relationships between recipes. It treats the problem as a directed graph where edges point from ingredients to recipes that need them, tracking the in-degree (number of required ingredients) for each recipe. When an ingredient becomes available, it reduces the in-degree of all recipes that need it. When a recipe's in-degree reaches 0, all its ingredients are available and it can be created.
Intuition
The key insight is recognizing this as a dependency resolution problem. Each recipe depends on certain ingredients (or other recipes), and we can only make a recipe when all its dependencies are satisfied.
Think of it like a chain reaction: we start with our initial supplies, and whenever we have an ingredient available, it might "unlock" new recipes. When a recipe becomes unlocked (all its ingredients are available), it becomes a new ingredient that might unlock even more recipes.
This naturally leads us to think about graph theory. We can model this as a directed graph where:
- Each ingredient/recipe is a node
- An edge from ingredient
A
to recipeB
means thatB
needsA
The problem becomes: starting from our initial supplies, which recipes can we eventually reach by following the dependency chains?
The challenge is handling the dependencies efficiently. We need to track:
- Which recipes depend on which ingredients (the graph structure)
- How many ingredients each recipe still needs (the "waiting count")
This is exactly what topological sorting with in-degrees does. The in-degree of a recipe represents how many ingredients it's still waiting for. When we process an available ingredient, we decrease the in-degree of all recipes that need it. When a recipe's in-degree hits 0, all its dependencies are satisfied and we can make it.
The beauty of this approach is that it handles complex dependency chains naturally. A recipe that depends on other recipes will have a higher initial in-degree, and will only become available after those prerequisite recipes are made first. The queue-based processing ensures we explore all possible recipes level by level, like a breadth-first traversal through the dependency graph.
Learn more about Graph and Topological Sort patterns.
Solution Approach
The solution implements a topological sorting algorithm using BFS (Breadth-First Search) to resolve recipe dependencies.
Data Structures Used:
g
(graph): A dictionary mapping each ingredient to a list of recipes that need itindeg
(in-degree): A dictionary tracking how many ingredients each recipe needsq
(queue): A list that acts as a queue for BFS processingans
(answer): Stores the recipes we can make
Step-by-step Implementation:
-
Build the dependency graph:
for a, b in zip(recipes, ingredients): for v in b: g[v].append(a) indeg[a] += len(b)
- For each recipe
a
and its ingredients listb
- Create edges from each ingredient
v
to recipea
(stored ing[v]
) - Set the in-degree of recipe
a
to the number of ingredients it needs
- For each recipe
-
Initialize the queue with available supplies:
q = supplies
- Start processing with all initially available ingredients
-
Process the queue (BFS):
for i in q: for j in g[i]: indeg[j] -= 1 if indeg[j] == 0: ans.append(j) q.append(j)
- For each available ingredient
i
in the queue - Check all recipes
j
that need this ingredient - Decrease their in-degree by 1 (one dependency satisfied)
- If in-degree reaches 0, the recipe can be made:
- Add it to the answer list
- Add it to the queue (it becomes a new available ingredient)
- For each available ingredient
The algorithm naturally handles complex dependencies: recipes that depend on other recipes will only become available after their prerequisite recipes are made. The queue ensures we process all possibilities in the correct order, making recipes as soon as their dependencies are satisfied.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through a small example to see how the topological sorting approach works.
Given:
recipes = ["cake", "frosting", "decorated_cake"]
ingredients = [["flour", "eggs"], ["sugar", "butter"], ["cake", "frosting"]]
supplies = ["flour", "eggs", "sugar", "butter"]
Step 1: Build the dependency graph
Create the graph g
(ingredient β recipes that need it):
"flour" β ["cake"]
"eggs" β ["cake"]
"sugar" β ["frosting"]
"butter" β ["frosting"]
"cake" β ["decorated_cake"]
"frosting" β ["decorated_cake"]
Set up in-degrees indeg
(number of ingredients each recipe needs):
"cake": 2
(needs flour and eggs)"frosting": 2
(needs sugar and butter)"decorated_cake": 2
(needs cake and frosting)
Step 2: Initialize queue with supplies
q = ["flour", "eggs", "sugar", "butter"]
ans = []
Step 3: Process the queue
Process "flour"
:
- Check recipes needing flour:
["cake"]
- Decrease
indeg["cake"]
from 2 to 1 - Not zero yet, so cake can't be made
Process "eggs"
:
- Check recipes needing eggs:
["cake"]
- Decrease
indeg["cake"]
from 1 to 0 - In-degree is 0! Add "cake" to
ans
andq
ans = ["cake"]
,q = ["flour", "eggs", "sugar", "butter", "cake"]
Process "sugar"
:
- Check recipes needing sugar:
["frosting"]
- Decrease
indeg["frosting"]
from 2 to 1 - Not zero yet
Process "butter"
:
- Check recipes needing butter:
["frosting"]
- Decrease
indeg["frosting"]
from 1 to 0 - In-degree is 0! Add "frosting" to
ans
andq
ans = ["cake", "frosting"]
,q = [..., "cake", "frosting"]
Process "cake"
:
- Check recipes needing cake:
["decorated_cake"]
- Decrease
indeg["decorated_cake"]
from 2 to 1 - Not zero yet
Process "frosting"
:
- Check recipes needing frosting:
["decorated_cake"]
- Decrease
indeg["decorated_cake"]
from 1 to 0 - In-degree is 0! Add "decorated_cake" to
ans
ans = ["cake", "frosting", "decorated_cake"]
Final Result: ["cake", "frosting", "decorated_cake"]
The algorithm correctly identifies that we can make all three recipes, handling the dependency chain where "decorated_cake" depends on intermediate recipes "cake" and "frosting".
Solution Implementation
1class Solution:
2 def findAllRecipes(
3 self, recipes: List[str], ingredients: List[List[str]], supplies: List[str]
4 ) -> List[str]:
5 # Build adjacency list: ingredient/supply -> list of recipes that need it
6 ingredient_to_recipes = defaultdict(list)
7
8 # Track in-degree (number of ingredients needed) for each recipe
9 recipe_indegree = defaultdict(int)
10
11 # Build the graph relationships
12 for recipe, recipe_ingredients in zip(recipes, ingredients):
13 for ingredient in recipe_ingredients:
14 # This ingredient is needed by this recipe
15 ingredient_to_recipes[ingredient].append(recipe)
16 # Count total ingredients needed for this recipe
17 recipe_indegree[recipe] = len(recipe_ingredients)
18
19 # Initialize queue with available supplies (these have indegree 0)
20 available_items = supplies.copy()
21 makeable_recipes = []
22
23 # Process items using BFS/topological sort
24 for current_item in available_items:
25 # For each recipe that needs this item
26 for dependent_recipe in ingredient_to_recipes[current_item]:
27 # Decrease the count of needed ingredients
28 recipe_indegree[dependent_recipe] -= 1
29
30 # If all ingredients are now available (indegree becomes 0)
31 if recipe_indegree[dependent_recipe] == 0:
32 # This recipe can be made
33 makeable_recipes.append(dependent_recipe)
34 # Add it to available items for further processing
35 available_items.append(dependent_recipe)
36
37 return makeable_recipes
38
1class Solution {
2 public List<String> findAllRecipes(
3 String[] recipes, List<List<String>> ingredients, String[] supplies) {
4
5 // Build adjacency list: ingredient -> list of recipes that need it
6 Map<String, List<String>> ingredientToRecipes = new HashMap<>();
7
8 // Track in-degree (number of dependencies) for each recipe
9 Map<String, Integer> recipeDependencyCount = new HashMap<>();
10
11 // Build the graph and calculate in-degrees
12 for (int recipeIndex = 0; recipeIndex < recipes.length; recipeIndex++) {
13 String currentRecipe = recipes[recipeIndex];
14 List<String> currentIngredients = ingredients.get(recipeIndex);
15
16 // For each ingredient, add the current recipe as dependent
17 for (String ingredient : currentIngredients) {
18 ingredientToRecipes
19 .computeIfAbsent(ingredient, k -> new ArrayList<>())
20 .add(currentRecipe);
21 }
22
23 // Set the dependency count for this recipe
24 recipeDependencyCount.put(currentRecipe, currentIngredients.size());
25 }
26
27 // Initialize queue with all available supplies (nodes with in-degree 0)
28 Deque<String> availableItems = new ArrayDeque<>();
29 for (String supply : supplies) {
30 availableItems.offer(supply);
31 }
32
33 // Result list to store recipes that can be made
34 List<String> makeableRecipes = new ArrayList<>();
35
36 // Process items using BFS (Kahn's algorithm)
37 while (!availableItems.isEmpty()) {
38 int currentLevelSize = availableItems.size();
39
40 // Process all items at current level
41 for (int i = 0; i < currentLevelSize; i++) {
42 String currentItem = availableItems.pollFirst();
43
44 // Check all recipes that depend on this item
45 List<String> dependentRecipes = ingredientToRecipes.getOrDefault(
46 currentItem, Collections.emptyList());
47
48 for (String recipe : dependentRecipes) {
49 // Decrease dependency count for this recipe
50 int newCount = recipeDependencyCount.get(recipe) - 1;
51 recipeDependencyCount.put(recipe, newCount);
52
53 // If all dependencies satisfied, recipe can be made
54 if (newCount == 0) {
55 makeableRecipes.add(recipe);
56 // Recipe becomes available for other recipes to use
57 availableItems.offer(recipe);
58 }
59 }
60 }
61 }
62
63 return makeableRecipes;
64 }
65}
66
1class Solution {
2public:
3 vector<string> findAllRecipes(vector<string>& recipes, vector<vector<string>>& ingredients, vector<string>& supplies) {
4 // Build adjacency list: ingredient -> list of recipes that need it
5 unordered_map<string, vector<string>> ingredientToRecipes;
6 // Track in-degree (number of ingredients needed) for each recipe
7 unordered_map<string, int> inDegree;
8
9 // Build the graph and calculate in-degrees
10 for (int i = 0; i < recipes.size(); ++i) {
11 for (const string& ingredient : ingredients[i]) {
12 // Add edge from ingredient to recipe
13 ingredientToRecipes[ingredient].push_back(recipes[i]);
14 }
15 // Set in-degree as the number of ingredients needed for this recipe
16 inDegree[recipes[i]] = ingredients[i].size();
17 }
18
19 // Initialize queue with all available supplies
20 queue<string> availableItems;
21 for (const string& supply : supplies) {
22 availableItems.push(supply);
23 }
24
25 // Store recipes that can be created
26 vector<string> creatableRecipes;
27
28 // Process using topological sort (Kahn's algorithm)
29 while (!availableItems.empty()) {
30 int currentLevelSize = availableItems.size();
31
32 // Process all items at current level
33 for (int i = 0; i < currentLevelSize; ++i) {
34 string currentItem = availableItems.front();
35 availableItems.pop();
36
37 // Check all recipes that need this ingredient
38 for (const string& recipe : ingredientToRecipes[currentItem]) {
39 // Decrease the in-degree (one less ingredient needed)
40 inDegree[recipe]--;
41
42 // If all ingredients are available, recipe can be created
43 if (inDegree[recipe] == 0) {
44 creatableRecipes.push_back(recipe);
45 // This recipe can now be used as an ingredient
46 availableItems.push(recipe);
47 }
48 }
49 }
50 }
51
52 return creatableRecipes;
53 }
54};
55
1function findAllRecipes(recipes: string[], ingredients: string[][], supplies: string[]): string[] {
2 // Build adjacency list: ingredient -> list of recipes that need it
3 const ingredientToRecipes: Map<string, string[]> = new Map();
4
5 // Track in-degree (number of ingredients needed) for each recipe
6 const inDegree: Map<string, number> = new Map();
7
8 // Build the graph and calculate in-degrees
9 for (let i = 0; i < recipes.length; i++) {
10 // Set in-degree as the number of ingredients needed for this recipe
11 inDegree.set(recipes[i], ingredients[i].length);
12
13 // For each ingredient of the current recipe
14 for (const ingredient of ingredients[i]) {
15 // Add edge from ingredient to recipe
16 if (!ingredientToRecipes.has(ingredient)) {
17 ingredientToRecipes.set(ingredient, []);
18 }
19 ingredientToRecipes.get(ingredient)!.push(recipes[i]);
20 }
21 }
22
23 // Initialize queue with all available supplies
24 const availableItems: string[] = [];
25 for (const supply of supplies) {
26 availableItems.push(supply);
27 }
28
29 // Store recipes that can be created
30 const creatableRecipes: string[] = [];
31
32 // Process using topological sort (Kahn's algorithm)
33 while (availableItems.length > 0) {
34 // Get and remove the first available item
35 const currentItem = availableItems.shift()!;
36
37 // Check all recipes that need this ingredient
38 const dependentRecipes = ingredientToRecipes.get(currentItem) || [];
39 for (const recipe of dependentRecipes) {
40 // Decrease the in-degree (one less ingredient needed)
41 inDegree.set(recipe, inDegree.get(recipe)! - 1);
42
43 // If all ingredients are available, recipe can be created
44 if (inDegree.get(recipe) === 0) {
45 creatableRecipes.push(recipe);
46 // This recipe can now be used as an ingredient for other recipes
47 availableItems.push(recipe);
48 }
49 }
50 }
51
52 return creatableRecipes;
53}
54
Time and Space Complexity
Time Complexity: O(V + E)
where V
is the total number of unique nodes (recipes + ingredients + supplies) and E
is the total number of edges (dependencies between ingredients/supplies and recipes).
- Building the graph takes
O(E)
time as we iterate through all recipes and their ingredients once - The BFS/topological sort processes each node at most once and each edge at most once, taking
O(V + E)
time - Each recipe is added to the answer list at most once
Space Complexity: O(V + E)
- The graph
g
stores all edges, requiringO(E)
space - The
indeg
dictionary stores the in-degree for each recipe, usingO(|recipes|)
space - The queue
q
can contain at mostO(V)
nodes in the worst case - The
ans
list stores at mostO(|recipes|)
recipes - Overall space is dominated by the graph structure and queue, giving us
O(V + E)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Not Handling Circular Dependencies Properly
A critical pitfall is assuming that circular dependencies will cause infinite loops or errors. While the problem mentions circular dependencies are possible, the topological sort approach naturally handles them - recipes involved in cycles will never have their in-degree reach 0, so they simply won't be added to the result.
Example of circular dependency:
- Recipe A needs ingredient B
- Recipe B needs ingredient A
- Neither can be made unless one is already in supplies
2. Modifying the Original Supplies List
Using supplies.copy()
is crucial. If you directly use available_items = supplies
and then append to it, you're modifying the original input list, which can cause issues if the caller expects the input to remain unchanged.
Incorrect approach:
available_items = supplies # Wrong - creates reference, not copy # Later appending to available_items modifies supplies
Correct approach:
available_items = supplies.copy() # Creates independent copy
3. Using Wrong Data Structure for Queue Processing
The code uses a list and iterates through it while appending new items during iteration. This works in Python because the for loop internally handles the growing list, but it's not obvious behavior.
Potential confusion:
for current_item in available_items: # ... available_items.append(dependent_recipe) # Modifying during iteration
Clearer alternative using deque:
from collections import deque available_items = deque(supplies) while available_items: current_item = available_items.popleft() # ... process and append new items
4. Missing Edge Case: Recipe as Its Own Ingredient
If a recipe lists itself as an ingredient (self-dependency), it can never be made unless it's already in supplies. The current approach handles this correctly by treating it like any other dependency, but developers might overlook testing this case.
5. Assuming All Recipes Must Be Makeable
The problem asks for recipes you CAN make, not all recipes. Some recipes might be impossible to make due to missing base ingredients or circular dependencies. The solution correctly returns only makeable recipes, but developers might incorrectly try to force all recipes into the result.
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
https assets algo monster cover_photos graph svg Graph Fundamentals Tree with 0 cycle At this point you should be pretty familiar with trees A tree is a special kind of graph a connected acyclic cycle less graph A graph may contain cycle s and nodes could be disconnected A tree
Topological Sort Topological Order Prereq Breadth First Search Review problems graph_bfs Directed Graph Before we get started on topological order let's talk about directed graphs first A graph is directed when its edges have directions these edges are also called arcs Suppose that v w are vertices in a directed
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Donβt Miss This!