главная новое лучшее написать
3

Ссылка на задачу — 2115. Find All Possible Recipes from Given Supplies.
В этой задаче для эффективного решения важно не только выбрать эффективный алгоритм, но и аккуратно разложить строки по необходимым структурам данных, избежать лишних копирований и обеспечить ясную картину ownership'a за объектами.

📌 Описание задачи

💡 Идея

Используем рекурсивный DFS для проверки доступности рецептов.
Чтобы избежать повторных вычислений, применяем мемоизацию (кеширование уже проверенных результатов).

Вместо хранения графа явным образом, мы используем индексирование рецептов и проходим по их списку ингредиентов.

🔍 Детали подхода

  1. Предобработка входных данных:
    • Создаём хеш-таблицу для быстрого доступа к индексу каждого рецепта.
    • Добавляем начальные запасы в кеш доступных ингредиентов.
  2. Рекурсивная проверка доступности (DFS):
    • Если рецепт уже есть в кеше, возвращаем сохранённое значение.
    • Если это не рецепт, сразу отмечаем его как недоступный (false).
    • Предотвращаем рекурсивное зацикливание, временно помечая рецепт как false.
    • Проверяем все ингредиенты: если хотя бы один недоступен, рецепт приготовить нельзя.
    • Если все ингредиенты доступны, сохраняем позитивный результат (true).
  3. Сбор доступных рецептов:
    • Фильтруем исходный список рецептов, оставляя только те, которые можно приготовить.

📊 Асимптотика

💻 Исходный код

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