拓扑排序题目:从给定原材料中找到所有可以做出的菜

文章目录

题目

标题和出处

标题:从给定原材料中找到所有可以做出的菜

出处:2115. 从给定原材料中找到所有可以做出的菜

难度

4 级

题目描述

要求

有 n \texttt{n} n 道不同菜的信息。给定一个字符串数组 recipes \texttt{recipes} recipes 和一个二维字符串数组 ingredients \texttt{ingredients} ingredients。第 i \texttt{i} i 道菜的名字为 recipesi \texttt{recipesi} recipesi,如果拥有它所有 的原材料 ingredientsi \texttt{ingredientsi} ingredientsi,那么就可以做出 这道菜。一道菜的原材料可能是另一道 菜,也就是说 ingredientsi \texttt{ingredientsi} ingredientsi 可能包含 recipes \texttt{recipes} recipes 中另一个字符串。

另外给定一个字符串数组 supplies \texttt{supplies} supplies,包含初始时拥有的所有原材料,每一种原材料都有无限多。

返回可以做出的所有菜。可以按任意顺序返回答案。

注意两道菜在它们的原材料中可能互相包含。

示例

示例 1:

输入: recipes = "bread", ingredients = \["yeast","flour"], supplies = "yeast","flour","corn" \texttt{recipes = "bread", ingredients = \["yeast","flour"], supplies = "yeast","flour","corn"} recipes = "bread", ingredients = \["yeast","flour"], supplies = "yeast","flour","corn"

输出: "bread" \texttt{"bread"} "bread"

解释:

我们可以做出 "bread" \texttt{"bread"} "bread",因为我们有原材料 "yeast" \texttt{"yeast"} "yeast" 和 "flour" \texttt{"flour"} "flour"。

示例 2:

输入: recipes = "bread","sandwich", ingredients = \["yeast","flour","bread","meat"], supplies = "yeast","flour","meat" \texttt{recipes = "bread","sandwich", ingredients = \["yeast","flour","bread","meat"], supplies = "yeast","flour","meat"} recipes = "bread","sandwich", ingredients = \["yeast","flour","bread","meat"], supplies = "yeast","flour","meat"

输出: "bread","sandwich" \texttt{"bread","sandwich"} "bread","sandwich"

解释:

我们可以做出 "bread" \texttt{"bread"} "bread",因为我们有原材料 "yeast" \texttt{"yeast"} "yeast" 和 "flour" \texttt{"flour"} "flour"。

我们可以做出 "sandwich" \texttt{"sandwich"} "sandwich",因为我们有原材料 "meat" \texttt{"meat"} "meat" 且可以做出原材料 "bread" \texttt{"bread"} "bread"。

示例 3:

输入: recipes = "bread","sandwich","burger", ingredients = \["yeast","flour","bread","meat","sandwich","meat","bread"], supplies = "yeast","flour","meat" \texttt{recipes = "bread","sandwich","burger", ingredients = \["yeast","flour","bread","meat","sandwich","meat","bread"], supplies = "yeast","flour","meat"} recipes = "bread","sandwich","burger", ingredients = \["yeast","flour","bread","meat","sandwich","meat","bread"], supplies = "yeast","flour","meat"

输出: "bread","sandwich","burger" \texttt{"bread","sandwich","burger"} "bread","sandwich","burger"

解释:

我们可以做出 "bread" \texttt{"bread"} "bread",因为我们有原材料 "yeast" \texttt{"yeast"} "yeast" 和 "flour" \texttt{"flour"} "flour"。

我们可以做出 "sandwich" \texttt{"sandwich"} "sandwich",因为我们有原材料 "meat" \texttt{"meat"} "meat" 且可以做出原材料 "bread" \texttt{"bread"} "bread"。

我们可以做出 "burger" \texttt{"burger"} "burger",因为我们有原材料 "meat" \texttt{"meat"} "meat" 且可以做出原材料 "bread" \texttt{"bread"} "bread" 和 "sandwich" \texttt{"sandwich"} "sandwich"。

数据范围

  • n = recipes.length = ingredients.length \texttt{n} = \texttt{recipes.length} = \texttt{ingredients.length} n=recipes.length=ingredients.length
  • 1 ≤ n ≤ 100 \texttt{1} \le \texttt{n} \le \texttt{100} 1≤n≤100
  • 1 ≤ ingredientsi.length, supplies.length ≤ 100 \texttt{1} \le \texttt{ingredientsi.length, supplies.length} \le \texttt{100} 1≤ingredientsi.length, supplies.length≤100
  • 1 ≤ recipesi.length, ingredientsij.length, suppliesk.length ≤ 10 \texttt{1} \le \texttt{recipesi.length, ingredientsij.length, suppliesk.length} \le \texttt{10} 1≤recipesi.length, ingredientsij.length, suppliesk.length≤10
  • recipesi \texttt{recipesi} recipesi、 ingredientsij \texttt{ingredientsij} ingredientsij 和 suppliesk \texttt{suppliesk} suppliesk 只包含小写英语字母
  • 所有 recipes \texttt{recipes} recipes 和 supplies \texttt{supplies} supplies 中的值各不相同
  • ingredientsi \texttt{ingredientsi} ingredientsi 中的字符串各不相同

解法

思路和算法

可以将所有的原材料和原材料之间的依赖关系看成有向图,每一道菜也是一种原材料。如果一道菜 recipe \textit{recipe} recipe 的原材料有 ingredient \textit{ingredient} ingredient,则存在一条从 ingredient \textit{ingredient} ingredient 指向 recipe \textit{recipe} recipe 的有向边。

只有当一道菜的所有原材料都具备时才能做出这道菜,因此可以使用拓扑排序判断从初始原材料开始是否可以做出每道菜。

首先建立有向图,记录每种原材料对应的所有后继原材料,即每种原材料是哪些菜的原材料,并记录每种原材料的入度,即每种原材料依赖于多少种原材料,然后从初始原材料开始,使用广度优先搜索实现拓扑排序。

拓扑排序时,对于每个原材料,执行如下操作。

  1. 得到该原材料的所有后继原材料。

  2. 对于每种后继原材料,将后继原材料的入度减 1 1 1。如果在更新入度之后,后继原材料的入度变为 0 0 0,则后继原材料对应的一道菜是可以做出的菜,将这道菜添加道答案中,并继续对后继原材料执行搜索。

遍历结束之后,即可得到可以做出的所有菜。

代码

java 复制代码
class Solution {
    public List<String> findAllRecipes(String[] recipes, List<List<String>> ingredients, String[] supplies) {
        List<String> allRecipes = new ArrayList<String>();
        Map<String, List<String>> adjacentMap = new HashMap<String, List<String>>();
        Map<String, Integer> indegrees = new HashMap<String, Integer>();
        int n = recipes.length;
        for (int i = 0; i < n; i++) {
            String recipe = recipes[i];
            List<String> currIngredients = ingredients.get(i);
            for (String ingredient : currIngredients) {
                adjacentMap.putIfAbsent(ingredient, new ArrayList<String>());
                adjacentMap.get(ingredient).add(recipe);
            }
            indegrees.put(recipe, currIngredients.size());
        }
        Queue<String> queue = new ArrayDeque<String>();
        for (String ingredient : supplies) {
            queue.offer(ingredient);
        }
        while (!queue.isEmpty()) {
            String ingredient = queue.poll();
            List<String> adjacent = adjacentMap.getOrDefault(ingredient, new ArrayList<String>());
            for (String next : adjacent) {
                indegrees.put(next, indegrees.get(next) - 1);
                if (indegrees.get(next) == 0) {
                    allRecipes.add(next);
                    queue.offer(next);
                }
            }
        }
        return allRecipes;
    }
}

复杂度分析

  • 时间复杂度: O ( n + m + d ) O(n + m + d) O(n+m+d),其中 n n n 是数组 recipes \textit{recipes} recipes 的长度, m m m 是数组 supplies \textit{supplies} supplies 的长度, d d d 是数组 ingredients \textit{ingredients} ingredients 中的元素总数。有向图中有 n + m n + m n+m 个顶点和 d d d 条边,建立有向图和拓扑排序的时间复杂度由顶点数和边数决定。这里将字符串的长度视为常数。

  • 空间复杂度: O ( n + m + d ) O(n + m + d) O(n+m+d),其中 n n n 是数组 recipes \textit{recipes} recipes 的长度, m m m 是数组 supplies \textit{supplies} supplies 的长度, d d d 是数组 ingredients \textit{ingredients} ingredients 中的元素总数。有向图中有 n + m n + m n+m 个顶点和 d d d 条边,存储有向图的信息需要 O ( n + m + d ) O(n + m + d) O(n+m+d) 的空间,拓扑排序时的队列需要 O ( n + m ) O(n + m) O(n+m) 的空间,因此空间复杂度是 O ( n + m + d ) O(n + m + d) O(n+m+d)。这里将字符串的长度视为常数。

相关推荐
岑梓铭12 天前
《考研408数据结构》第六章(6.3 图的遍历)复习笔记
数据结构·笔记·考研··拓扑排序·408
khalil10202 个月前
代码随想录算法训练营Day-58 图论08 | 拓扑排序精讲、dijkstra(朴素版)精讲
c++·算法·图论·dijkstra·拓扑排序·prim·最短距离
澈2072 个月前
图论入门:拓扑排序实战指南
算法·拓扑排序·有向图
梦想画家3 个月前
PostgreSQL 图计算双雄:Apache AGE 与 pgGraphBLAS 的融合实战指南
数据库·postgresql·图算法
I_LPL4 个月前
day54 代码随想录算法训练营 图论专题8
数据结构·图论·拓扑排序·dijkstra算法
I_LPL4 个月前
hot100 图论专题
算法·图论·dfs·bfs·拓扑排序
lcreek4 个月前
LeetCode LCR114.火星词典
leetcode··拓扑排序
逝雪Yuki5 个月前
P4017 最大食物链计数
c++·算法·动态规划·拓扑排序·洛谷
Darkwanderor5 个月前
图论——拓扑排序和图上DP
c++·算法·动态规划·图论·拓扑排序