Find All Possible Recipes from Given Supplies solution and explanation
9 more videos on this problem
Find All Possible Recipes from Given Supplies - Leetcode 2115 - PythonNeetCodeIO · 19:55 · 12K views
Find All Possible Recipes from Given Supplies | Brute Force | Optimal | Leetcode 2115codestorywithMIK · 37:22 · 10K views
FIND ALL RECIPES FROM GIVEN SUPPLIES | LEETCODE 2115 | PYTHON TOPOLOGICAL SORT SOLUTIONCracking FAANG · 17:20 · 6K views
Find All Possible Recipes from Given Supplies | Leetcode 2115Techdose · 20:00 · 4K views
2115. Find All Possible Recipes from Given Supplies | Topological SortAryan Mittal · 11:53 · 3K views
Find All Possible Recipes from Given Supplies | LeetCode 2115 | Topological Sort | Python SolutionMad Dev Canvas · 14:02 · 2K views
2115. Find All Possible Recipes from Given Supplies (Leetcode Medium)Programming Live with Larry · 6:14 · 2K views
LEETCODE - 2115. Find All Possible Recipes from Given Supplies | Python | DFS | Recursioncodegreen · 19:01 · 796 views
leetcode 2115 Find All Possible Recipes from Given SuppliesMath Geeks · 4:35 · 669 views
Leetcode 2115. Find All Possible Recipes from Given Supplies (topological sort)LetsCode · 11:41 · 140 views
The problem
You have information about n different recipes. You are given a string array recipes and a 2D string array ingredients. The ith recipe has the name recipes[i], and you can create it if you have all the needed ingredients from ingredients[i]. Ingredients to a recipe may need to be created from other recipes, i.e., ingredients[i] may contain a string that is in recipes.
You are also given a string array supplies containing all the ingredients that you initially have, and you have an infinite supply of all of them.
Return a list of all the recipes that you can create. You may return the answer in any order.
Note that two recipes may contain each other in their ingredients.
Example 1:
Input: recipes = ["bread"], ingredients = [["yeast","flour"]], supplies = ["yeast","flour","corn"] Output: ["bread"] Explanation: We can create "bread" since we have the ingredients "yeast" and "flour".
Example 2:
Input: recipes = ["bread","sandwich"], ingredients = [["yeast","flour"],["bread","meat"]], supplies = ["yeast","flour","meat"] Output: ["bread","sandwich"] Explanation: We can create "bread" since we have the ingredients "yeast" and "flour". We can create "sandwich" since we have the ingredient "meat" and can create the ingredient "bread".
Example 3:
Input: recipes = ["bread","sandwich","burger"], ingredients = [["yeast","flour"],["bread","meat"],["sandwich","meat","bread"]], supplies = ["yeast","flour","meat"] Output: ["bread","sandwich","burger"] Explanation: We can create "bread" since we have the ingredients "yeast" and "flour". We can create "sandwich" since we have the ingredient "meat" and can create the ingredient "bread". We can create "burger" since we have the ingredient "meat" and can create the ingredients "bread" and "sandwich".
Constraints:
n == recipes.length == ingredients.length1 <= n <= 1001 <= ingredients[i].length, supplies.length <= 1001 <= recipes[i].length, ingredients[i][j].length, supplies[k].length <= 10recipes[i], ingredients[i][j], andsupplies[k]consist only of lowercase English letters.- All the values of
recipesandsuppliescombined are unique. - Each
ingredients[i]does not contain any duplicate values.
Approach overview
Problem Overview: You are given a list of recipes, their required ingredients, and a list of initial supplies. Some ingredients may themselves be recipes. The task is to determine every recipe you can ultimately prepare by resolving these dependencies.
Approach 1: Backtracking with Memoization (DFS) (Time: O(N * K), Space: O(N))
This approach treats the problem as dependency resolution using recursion. For each recipe, run a DFS to check whether all its ingredients can be produced. If an ingredient exists in the supply set, it is immediately usable. If it is another recipe, recursively attempt to build it. Memoization caches whether a recipe is possible or impossible, preventing repeated work across different dependency chains.
The key insight is that recipes form a dependency graph. DFS explores these dependencies until reaching either base supplies or a missing ingredient. Memoization avoids recomputing the same recipe multiple times. This approach works well when the dependency depth is small, but recursive traversal still introduces overhead compared to iterative graph processing.
Relevant concepts include graph traversal and efficient lookups with a hash table.
Approach 2: Topological Sort using Kahn's Algorithm (Time: O(N + E), Space: O(N + E))
This is the optimal approach. Model the problem as a directed graph where ingredients point to recipes that depend on them. Track the number of missing ingredients for each recipe using an indegree counter. All initial supplies are pushed into a queue since they are already available.
Process the queue using Kahn's algorithm. When a supply or completed recipe becomes available, iterate over the recipes that depend on it and decrement their indegree. Once a recipe's indegree reaches zero, every required ingredient is available, so that recipe can be produced and added to the queue as a new supply.
The key insight is that recipes behave like nodes in a dependency DAG. Topological sorting naturally processes them in a valid production order. Each ingredient and dependency edge is processed once, giving linear complexity relative to nodes and edges. This method relies heavily on topological sort and adjacency lists built with arrays and hash maps.
Recommended for interviews: The topological sort solution using Kahn's algorithm is what most interviewers expect. It demonstrates strong understanding of dependency graphs and efficient processing of prerequisites. DFS with memoization still shows solid reasoning and works well conceptually, but the topological approach scales better and is the canonical solution for dependency problems.
Approach 1: Topological Sort using Kahn's Algorithm
This approach uses graph theory to solve the problem. Each recipe and ingredient is considered a node. An edge from node A to node B indicates that node B is needed to create node A. We can then perform a topological sort utilizing Kahn's Algorithm to determine the order in which we can create the recipes.
from collections import defaultdict, deque
def findAllRecipes(recipes, ingredients, supplies):
graph = defaultdict(list)
indegree = defaultdict(int)
for r, ing in zip(recipes, ingredients):
for i in ing:
graph[i].append(r)
indegree[r] += 1
queue = deque(supplies)
possible_recipes = set(supplies)
while queue:
item = queue.popleft()
for neighbor in graph[item]:
indegree[neighbor] -= 1
if indegree[neighbor] == 0:
possible_recipes.add(neighbor)
queue.append(neighbor)
return [r for r in recipes if r in possible_recipes]
# Example Usage
# print(findAllRecipes(["bread"], [["yeast","flour"]], ["yeast","flour","corn"]))#include <iostream>
#include <vector>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <queue>
std::vector<std::string> findAllRecipes(std::vector<std::string>& recipes, std::vector<std::vector<std::string>>& ingredients, std::vector<std::string>& supplies) {
std::unordered_map<std::string, std::vector<std::string>> graph;
std::unordered_map<std::string, int> indegree;
std::queue<std::string> q;
std::unordered_set<std::string> supplies_set(supplies.begin(), supplies.end());
for (int i = 0; i < recipes.size(); ++i) {
for (const std::string& ing : ingredients[i]) {
graph[ing].push_back(recipes[i]);
indegree[recipes[i]]++;
}
}
for (const std::string& s : supplies) {
q.push(s);
}
std::vector<std::string> result;
while (!q.empty()) {
std::string item = q.front(); q.pop();
if (supplies_set.find(item) == supplies_set.end()) {
result.push_back(item);
}
for (const std::string& neighbor : graph[item]) {
if (--indegree[neighbor] == 0) {
q.push(neighbor);
supplies_set.insert(neighbor);
}
}
}
return result;
}
int main() {
std::vector<std::string> recipes = {"bread", "sandwich"};
std::vector<std::vector<std::string>> ingredients = {{"yeast", "flour"}, {"bread", "meat"}};
std::vector<std::string> supplies = {"yeast", "flour", "meat"};
std::vector<std::string> creatable_recipes = findAllRecipes(recipes, ingredients, supplies);
for (const std::string& recipe : creatable_recipes) {
std::cout << recipe << " ";
}
return 0;
}In this implementation, we first build the graph and compute the in-degrees of each recipe. We fill the queue with our initial supplies and perform a BFS. We decrement the in-degree of the neighboring nodes when visiting each node, and if a neighbor's in-degree becomes zero, it means that all ingredients to make it are available, so we add it to the queue.
Time Complexity: O(n + m), where n is the number of recipes and m is the total number of ingredients.
Space Complexity: O(n + m), due to the graph and in-degree storage.
Approach 2: Backtracking with Memoization
This approach involves trying to create each recipe by checking if all its ingredients are available or can be created from other recipes. We use memoization to store the results of subproblems, avoiding redundant calculations.
import java.util.*;
public class RecipeFinder {
public List<String> findAllRecipes(String[] recipes, List<List<String>> ingredients, String[] supplies) {
Set<String> suppliesSet = new HashSet<>(Arrays.asList(supplies));
Map<String, Boolean> memo = new HashMap<>();
List<String> result = new ArrayList<>();
for (int i = 0; i < recipes.length; i++) {
if (canCreate(recipes[i], ingredients, recipes, suppliesSet, memo)) {
result.add(recipes[i]);
}
}
return result;
}
private boolean canCreate(String recipe, List<List<String>> ingredients, String[] recipes, Set<String> supplies, Map<String, Boolean> memo) {
if (supplies.contains(recipe)) return true;
if (memo.containsKey(recipe)) return memo.get(recipe);
int recipeIndex = getRecipeIndex(recipe, recipes);
if (recipeIndex == -1) return false;
for (String ing : ingredients.get(recipeIndex)) {
if (!canCreate(ing, ingredients, recipes, supplies, memo)) {
memo.put(recipe, false);
return false;
}
}
memo.put(recipe, true);
supplies.add(recipe); // Now we can create this recipe
return true;
}
private int getRecipeIndex(String recipe, String[] recipes) {
for (int i = 0; i < recipes.length; i++) {
if (recipes[i].equals(recipe)) return i;
}
return -1;
}
public static void main(String[] args) {
RecipeFinder finder = new RecipeFinder();
String[] recipes = { "bread", "sandwich" };
List<List<String>> ingredients = Arrays.asList(
Arrays.asList("yeast", "flour"),
Arrays.asList("bread", "meat")
);
String[] supplies = { "yeast", "flour", "meat" };
System.out.println(finder.findAllRecipes(recipes, ingredients, supplies));
}
}using System;
using System.Collections.Generic;
public class Solution {
public IList<string> FindAllRecipes(string[] recipes, IList<IList<string>> ingredients, string[] supplies) {
var supplySet = new HashSet<string>(supplies);
var memo = new Dictionary<string, bool>();
List<string> result = new List<string>();
for (int i = 0; i < recipes.Length; i++) {
if (CanCreate(recipes[i], ingredients, recipes, supplySet, memo)) {
result.Add(recipes[i]);
}
}
return result;
}
private bool CanCreate(string recipe, IList<IList<string>> ingredients, string[] recipes, HashSet<string> supplies, Dictionary<string, bool> memo) {
if (supplies.Contains(recipe)) return true;
if (memo.ContainsKey(recipe)) return memo[recipe];
int recipeIndex = Array.IndexOf(recipes, recipe);
if (recipeIndex == -1) return false;
foreach (string ingredient in ingredients[recipeIndex]) {
if (!CanCreate(ingredient, ingredients, recipes, supplies, memo)) {
memo[recipe] = false;
return false;
}
}
memo[recipe] = true;
supplies.Add(recipe);
return true;
}
public static void Main(string[] args) {
var solution = new Solution();
var recipes = new string[] { "bread", "sandwich" };
var ingredients = new List<IList<string>> {
new List<string> { "yeast", "flour" },
new List<string> { "bread", "meat" }
};
var supplies = new string[] { "yeast", "flour", "meat" };
var creatableRecipes = solution.FindAllRecipes(recipes, ingredients, supplies);
Console.WriteLine(string.Join(", ", creatableRecipes));
}
}This implementation checks each recipe to see if it can be created using a recursive backtracking approach. It covers all subproblems using memoization, thereby improving efficiency. An ingredient is considered ready if it can be created from available supplies, its own recipe, or if it already appears in supplies.
Time Complexity: O(n * m), where n is the number of recipes and m is the average number of ingredients.
Space Complexity: O(n + m), due to the auxiliary data structures used for memoization and recursion stack.
Approach 3: Default Approach
func findAllRecipes(recipes []string, ingredients [][]string, supplies []string) []string {
g := map[string][]string{}
indeg := map[string]int{}
for i, a := range recipes {
for _, b := range ingredients[i] {
g[b] = append(g[b], a)
}
indeg[a] = len(ingredients[i])
}
q := []string{}
for _, s := range supplies {
q = append(q, s)
}
ans := []string{}
for len(q) > 0 {
for n := len(q); n > 0; n-- {
i := q[0]
q = q[1:]
for _, j := range g[i] {
indeg[j]--
if indeg[j] == 0 {
ans = append(ans, j)
q = append(q, j)
}
}
}
}
return ans
}Complexity comparison
| Approach | Complexity |
|---|---|
| Topological Sort using Kahn's Algorithm | Time Complexity: O(n + m), where n is the number of recipes and m is the total number of ingredients. |
| Backtracking with Memoization | Time Complexity: O(n * m), where n is the number of recipes and m is the average number of ingredients. |
| Default Approach | – |
Complexity analysis
| Approach | Time | Space | When to Use |
|---|---|---|---|
| Backtracking with Memoization (DFS) | O(N * K) | O(N) | Useful for understanding dependency resolution or when recursion is easier to implement. |
| Topological Sort (Kahn's Algorithm) | O(N + E) | O(N + E) | Best for large dependency graphs and the expected optimal interview solution. |
Frequently asked questions
Is Find All Possible Recipes from Given Supplies easy or hard?
The problem is rated Medium on LeetCode with an acceptance rate around 56%. The difficulty comes from recognizing the dependency graph and applying topological sorting. Once modeled correctly, the algorithm is straightforward to implement.
Find All Possible Recipes from Given Supplies Python/Java solution
Python solutions usually implement Kahn's algorithm using collections.deque for the queue and defaultdict for the adjacency list. Java implementations rely on HashMap, ArrayList, and a Queue such as LinkedList. Both versions maintain indegree counts and push recipes into the queue once all ingredients are satisfied.
How to solve Find All Possible Recipes from Given Supplies in O(N + E)?
Build a graph mapping ingredients to recipes that depend on them. Track the number of missing ingredients for each recipe using an indegree array. Push all initial supplies into a queue, then repeatedly process available items and reduce indegrees of dependent recipes. When a recipe's indegree reaches zero, add it to the queue and mark it as producible.
What is the best approach for Find All Possible Recipes from Given Supplies?
The most efficient solution uses topological sorting with Kahn's algorithm. Recipes and ingredients form a dependency graph where each ingredient contributes to a recipe's indegree count. Starting from the available supplies, process nodes in BFS order and unlock recipes once all required ingredients are available. This runs in O(N + E) time.
Is Find All Possible Recipes from Given Supplies asked at Google/Amazon/Meta?
Dependency resolution and topological sorting problems frequently appear in interviews at companies like Google, Amazon, and Meta. Variations include course scheduling, build systems, and package dependency resolution. This problem tests understanding of DAG processing and graph traversal.
What data structure is used in Find All Possible Recipes from Given Supplies?
The solution typically uses an adjacency list to represent the graph, a hash map for fast ingredient-to-recipe lookups, an indegree array to track remaining dependencies, and a queue for BFS processing during Kahn's topological sort.
What is the time complexity of Find All Possible Recipes from Given Supplies?
The optimal topological sort approach runs in O(N + E) time, where N is the number of recipes and E is the total number of ingredient dependencies. Each edge in the dependency graph is processed once when updating indegrees. Space complexity is also O(N + E) for the adjacency list and queue.
