Ссылка на задачу — 2115. Find All Possible Recipes from Given Supplies.
В этой задаче для эффективного решения важно не только выбрать эффективный алгоритм, но и аккуратно разложить строки по необходимым структурам данных, избежать лишних копирований и обеспечить ясную картину ownership'a за объектами.
📌 Описание задачи
- У нас есть список рецептов, каждому из которых соответствует список ингредиентов.
- Некоторые ингредиенты могут быть другими рецептами, а некоторые — изначально доступны (входят в список запасов).
- Нужно определить, какие рецепты можно приготовить, имея начальные запасы и возможность использовать приготовленные рецепты.
💡 Идея
Используем рекурсивный DFS для проверки доступности рецептов.
Чтобы избежать повторных вычислений, применяем мемоизацию (кеширование уже проверенных результатов).
Вместо хранения графа явным образом, мы используем индексирование рецептов и проходим по их списку ингредиентов.
🔍 Детали подхода
- Предобработка входных данных:
- Создаём хеш-таблицу для быстрого доступа к индексу каждого рецепта.
- Добавляем начальные запасы в кеш доступных ингредиентов.
- Рекурсивная проверка доступности (DFS):
- Если рецепт уже есть в кеше, возвращаем сохранённое значение.
- Если это не рецепт, сразу отмечаем его как недоступный (
false
).
- Предотвращаем рекурсивное зацикливание, временно помечая рецепт как
false
.
- Проверяем все ингредиенты: если хотя бы один недоступен, рецепт приготовить нельзя.
- Если все ингредиенты доступны, сохраняем позитивный результат (
true
).
- Сбор доступных рецептов:
- Фильтруем исходный список рецептов, оставляя только те, которые можно приготовить.
📊 Асимптотика
- Время:
O(N + E)
O(N)
– обработка списка рецептов и создание индекса.
O(E)
– проверка всех зависимостей (граф рецептов).
- Каждый рецепт проверяется только один раз благодаря мемоизации.
- Память:
O(N)
O(N)
– кеш доступности (can_prepare_cache
) и индекс рецептов.
- Списки ингредиентов не дублируются, а используются из входных данных.
💻 Исходный код
use std::collections::{HashMap, HashSet};
struct KitchenContext {
recipe_index: HashMap<String, usize>, // Maps recipe names to their index in the ingredients list
ingredients: Vec<Vec<String>>, // Stores ingredients required for each recipe
}
struct PreparationCache {
can_prepare_cache: HashMap<String, bool>, // Caches whether a recipe/ingredient can be prepared
}
impl Solution {
pub fn find_all_recipes(recipes: Vec<String>, ingredients: Vec<Vec<String>>, supplies: Vec<String>) -> Vec<String> {
let kc = KitchenContext::new(&recipes, ingredients);
let mut cache = PreparationCache::new(supplies);
recipes.into_iter()
.filter(|recipe| kc.can_prepare(recipe, &mut cache))
.collect()
}
}
impl KitchenContext {
// Initializes the KitchenContext with given recipes and ingredients
fn new(recipes: &[String], ingredients: Vec<Vec<String>>) -> Self {
let mut recipe_index = HashMap::new();
// Map each recipe to its corresponding index
for (idx, recipe) in recipes.iter().enumerate() {
recipe_index.insert(recipe.clone(), idx);
}
Self {
recipe_index,
ingredients,
}
}
// Determines whether a recipe can be prepared
fn can_prepare(&self, recipe: &str, cache: &mut PreparationCache) -> bool {
// Check cache and lazily determine recipe index
let mut recipe_idx = None;
let cached_result = *cache.can_prepare_cache.entry(recipe.to_string()).or_insert_with(|| {
recipe_idx = self.recipe_index.get(recipe).copied();
false // Default to false to prevent cycles
});
// If already cached, return the stored value
if recipe_idx.is_none() {
return cached_result;
}
// Check if all ingredients can be prepared
let idx = recipe_idx.unwrap();
for ingredient in &self.ingredients[idx] {
if !self.can_prepare(ingredient, cache) {
return false;
}
}
// Mark as preparable and return true
cache.can_prepare_cache.insert(recipe.to_string(), true);
true
}
}
impl PreparationCache {
// Initializes the cache with the given supplies
fn new(supplies: Vec<String>) -> Self {
let mut can_prepare_cache = HashMap::new();
for supply in supplies {
can_prepare_cache.insert(supply, true);
}
Self { can_prepare_cache }
}
}
Tags: #rust #algorithms #dfs #memoization