Facebook Pixel

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 where recipes[i] is the name of the i-th recipe
  • ingredients: A 2D array where ingredients[i] contains all the ingredients needed to make recipes[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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 recipe B means that B needs A

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:

  1. Which recipes depend on which ingredients (the graph structure)
  2. 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:

  1. g (graph): A dictionary mapping each ingredient to a list of recipes that need it
  2. indeg (in-degree): A dictionary tracking how many ingredients each recipe needs
  3. q (queue): A list that acts as a queue for BFS processing
  4. ans (answer): Stores the recipes we can make

Step-by-step Implementation:

  1. 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 list b
    • Create edges from each ingredient v to recipe a (stored in g[v])
    • Set the in-degree of recipe a to the number of ingredients it needs
  2. Initialize the queue with available supplies:

    q = supplies
    • Start processing with all initially available ingredients
  3. 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)

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 Evaluator

Example 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 and q
  • 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 and q
  • 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, requiring O(E) space
  • The indeg dictionary stores the in-degree for each recipe, using O(|recipes|) space
  • The queue q can contain at most O(V) nodes in the worst case
  • The ans list stores at most O(|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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which algorithm should you use to find a node that is close to the root of the tree?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More