Skip to content

Commit 3db9b1a

Browse files
committed
feat(algorithms, graphs, topological-sort): possible recipes from supplies
1 parent d960d9b commit 3db9b1a

15 files changed

+198
-0
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# Find All Possible Recipes from Given Supplies
2+
3+
You are given information about n different recipes. Each recipe is listed in the array recipes, and its corresponding
4+
ingredients are provided in the 2D array ingredients. The ith recipe, recipes[i], can be prepared if all the necessary
5+
ingredients listed in ingredients[i] are available. Some ingredients might need to be created from other recipes, meaning
6+
ingredients[i] may contain strings that are also in recipes.
7+
8+
Additionally, you have a string array supplies that contains all the ingredients you initially have, and you have an
9+
infinite supply of each.
10+
11+
Return a list of all the recipes you can create. The answer can be returned in any order.
12+
13+
> Note: It is possible for two recipes to list each other as ingredients. However, if these are the only two recipes
14+
> provided, the expected output is an empty list.
15+
16+
## Constraints
17+
18+
- `n` == `recipes.length` == `ingredients.length`
19+
- 1 <= `n` <= 100
20+
- 1 ≤ `ingredients[i].length`, `supplies.length` ≤ 100
21+
- 1 ≤ `recipes[i].length`, `ingredients[i][j].length`, `supplies[k].length` ≤ 10
22+
- `recipes[i]`, `ingredients[i][j]`, and `supplies[k]` consist only of lowercase English letters.
23+
- All the combined values of `recipes` and `supplies` are unique.
24+
- Each `ingredients[i]` doesn’t contain any duplicate values.
25+
26+
## Examples
27+
28+
![Example 1](images/examples/all_possible_recipes_from_supplies_example_1.png)
29+
![Example 2](images/examples/all_possible_recipes_from_supplies_example_2.png)
30+
31+
## Solution
32+
33+
An optimized approach to solve this problem would be to use the topological sort pattern. We need to understand that the
34+
preparation of recipes depends on the availability of ingredients. Some ingredients are available initially, while others
35+
may need to be prepared using other recipes. This dependency between recipes and their ingredients can be represented as
36+
a directed graph where:
37+
38+
- Each recipe is a node.
39+
- There is a directed edge from node A to node B if the preparation of recipe B requires recipe A.
40+
41+
Topological sort is an algorithm commonly used to order nodes (tasks) in a directed acyclic graph (DAG) such that for
42+
every directed edge u → v, node u comes before node v in the ordering. In our case, this means that if a recipe A depends
43+
on recipe B, then B should come before A in the order of recipes we can prepare.
44+
45+
If there are circular dependencies (like two recipes requiring each other), these will be cycles in the graph, and we
46+
cannot prepare either recipe. The goal of using topological sort is to determine the order in which recipes can be prepared,
47+
starting from those that can be made with the initial supplies and progressing through those that depend on previously
48+
prepared recipes.
49+
50+
Let’s go through the algorithm to see how we will reach the solution:
51+
52+
1. Initialize data structures:
53+
- `recipe_graph`: This is a dictionary where each key is a recipe and its value is a list of recipes that depend on it.
54+
- `recipe_indegrees`: This is a dictionary that stores the number of prerequisites for each recipe.
55+
- `recipes_queue`: This is a queue that processes recipes, which can be immediately made with the initial supplies.
56+
- `possible_recipes`: This is an array that stores all the recipes that can be created.
57+
2. Build the graph and in-degree count by iterating over each ingredient of each recipe:
58+
- If the ingredient is another recipe, add an edge from that ingredient to the current recipe in `recipe_graph` and
59+
increase `recipe_indegrees` for the current recipe.
60+
- If the ingredient is available in `supplies`, skip it since it’s immediately available.
61+
3. Add all recipes with an in-degree of 0 (no prerequisites) to `recipes_queue`.
62+
4. Process the `recipes_queue` using topological sorting:
63+
- While `recipes_queue` is not empty:
64+
- Pop a recipe from the queue.
65+
- Add it to `possible_recipes` since it can now be made.
66+
- For each recipe that depends on this recipe, reduce its in-degree by 1.
67+
- If any recipe’s in-degree becomes 0, add it to `recipes_queue`.
68+
5. Return the `possible_recipes` list, which contains all the recipes that can be created with the given supplies.
69+
70+
![Solution 1](./images/solutions/all_possible_recipes_from_supplies_solution_1.png)
71+
![Solution 2](./images/solutions/all_possible_recipes_from_supplies_solution_2.png)
72+
![Solution 3](./images/solutions/all_possible_recipes_from_supplies_solution_3.png)
73+
![Solution 4](./images/solutions/all_possible_recipes_from_supplies_solution_4.png)
74+
![Solution 5](./images/solutions/all_possible_recipes_from_supplies_solution_5.png)
75+
![Solution 6](./images/solutions/all_possible_recipes_from_supplies_solution_6.png)
76+
![Solution 7](./images/solutions/all_possible_recipes_from_supplies_solution_7.png)
77+
![Solution 8](./images/solutions/all_possible_recipes_from_supplies_solution_8.png)
78+
![Solution 9](./images/solutions/all_possible_recipes_from_supplies_solution_9.png)
79+
![Solution 10](./images/solutions/all_possible_recipes_from_supplies_solution_10.png)
80+
81+
### Time Complexity
82+
83+
The time complexity of this solution is O(v+e), where v is the vertices of the recipe_graph and e is the edges of the
84+
graph. This is because we traverse each vertex and edge in the graph exactly once during the topological sort, and the
85+
dictionary data structure allows us to access vertices and edges in constant time.
86+
87+
### Space Complexity
88+
89+
The space complexity is also O(v+e) as we have to store all the recipes and their respective ingredients.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
from typing import List, DefaultDict
2+
from collections import defaultdict, deque
3+
4+
5+
def find_recipes(
6+
recipes: List[str], ingredients: List[List[str]], supplies: List[str]
7+
) -> List[str]:
8+
# Create a graph to store which recipes depend on which ingredients
9+
recipe_graph: DefaultDict[str, List[str]] = defaultdict(list)
10+
# Create a dictionary to keep track of the number of dependencies (indegree) for each recipe
11+
recipe_in_degrees: DefaultDict[str, int] = defaultdict(int)
12+
13+
# Initialize a queue with the available supplies
14+
recipe_queue = deque(supplies)
15+
16+
# Build the graph and indegree dictionary
17+
for idx, recipe in enumerate(recipes):
18+
recipe_ingredients = ingredients[idx]
19+
20+
for ingredient in recipe_ingredients:
21+
recipe_in_degrees[recipe] += 1
22+
recipe_graph[ingredient].append(recipe)
23+
24+
# List to store the possible recipes that can be made
25+
possible_recipies: List[str] = []
26+
27+
# Process the queue until it's empty
28+
while recipe_queue:
29+
# Get the next item (ingredient or supply) from the queue
30+
ingredient = recipe_queue.popleft()
31+
32+
# Reduce the indegree of all recipes that depend on the current item
33+
for recipe in recipe_graph[ingredient]:
34+
recipe_in_degrees[recipe] -= 1
35+
# If a recipe's indegree reaches 0, all its ingredients are available, so add it to the queue
36+
if recipe_in_degrees[recipe] == 0:
37+
possible_recipies.append(recipe)
38+
recipe_queue.append(recipe)
39+
40+
return possible_recipies
63.2 KB
Loading
74.1 KB
Loading
144 KB
Loading
154 KB
Loading
162 KB
Loading
159 KB
Loading
176 KB
Loading
150 KB
Loading

0 commit comments

Comments
 (0)