常用食材组合

时间:2019-04-09 20:39:46

标签: java algorithm dictionary data-structures

我正在研究以下问题:

  

假设您有一个菜品列表,其中每个菜都与   成分清单。将具有常用成分的菜肴分组在一起。

例如:

输入:

"Pasta" -> ["Tomato Sauce", "Onions", "Garlic"] 
"Chicken Curry" --> ["Chicken", "Curry Sauce"] 
"Fried Rice" --> ["Rice", "Onions", "Nuts"] 
"Salad" --> ["Spinach", "Nuts"] 
"Sandwich" --> ["Cheese", "Bread"] 
"Quesadilla" --> ["Chicken", "Cheese"] 

输出:

("Pasta", "Fried Rice") 
("Fried Rice, "Salad")
("Chicken Curry", "Quesadilla")
("Sandwich", "Quesadilla") 

时间和空间的复杂度是多少?

我想出了以下代码。有没有更好的方法来解决此问题?看来算法是图论中的连通组件。

public static void main(String[] args) {
    List<String> ing1 = Arrays.asList("Tomato Sauce", "Onions", "Garlic");
    List<String> ing2 = Arrays.asList("Chicken", "Curry Sauce");
    List<String> ing3 = Arrays.asList("Rice", "Onions", "Nuts");
    List<String> ing4 = Arrays.asList("Spinach", "Nuts");
    List<String> ing5 = Arrays.asList("Cheese", "Bread");
    List<String> ing6 = Arrays.asList("Chicken", "Cheese");

    Map<String, List<String>> map = new HashMap<>();
    map.put("Pasta", ing1);
    map.put("Chicken Curry", ing2);
    map.put("Fried Rice", ing3);
    map.put("Salad", ing4);
    map.put("Sandwich", ing5);
    map.put("Quesadilla", ing6);

    System.out.println(group(map));
}

private static List<List<String>> group(Map<String, List<String>> map) {
    List<List<String>> output = new ArrayList<>();

    if (map == null || map.isEmpty()) {
        return output;
    }

    Map<String, List<String>> holder = new HashMap<>();

    for (Map.Entry<String, List<String>> entry : map.entrySet()) {
        String key = entry.getKey();
        List<String> value = entry.getValue();
        for (String v : value) {
            if (!holder.containsKey(v)) {
                holder.put(v, new ArrayList<String>());
            }
            holder.get(v).add(key);
        }
    }
    return new ArrayList<List<String>>(holder.values());
}

3 个答案:

答案 0 :(得分:2)

我们可以使用图论对这种方法进行实际的复杂度估算。 “连接的组件”方法将具有O(|V| + |E|)的复杂性,其中V是所有成分 菜肴的集合,而E是包含所有关系(a, b)的集合,其中每个a是一道菜,b是这道菜b的成分。 (即,假设您将此图表G = (V, E)存储在邻接列表中,而不是邻接矩阵中)

在任何需要找出每道菜的所有成分以找到结果的算法中,您都必须调查每道菜及其成分的所有。这将导致花费O(|V| + |E|)时间的调查(即遍历),这意味着没有此类算法会比您的方法更好。

答案 1 :(得分:1)

让我们首先把这个问题变成图形问题。每道菜和每种配料都是vertex。菜肴和配料之间的每个关系都是edge

让我们分析解决方案的最大大小。假设总共有N个菜肴和M个配料,则最大的解决方案输出是每一个单独的菜肴都相关时。在这种情况下,输出的大小为N^2,因此这是可以实现的时间复杂度的下限。我们可以很容易地创建一个输入,必须对其进行所有顶点和边上的迭代,因此时间复杂度的另一个下限是N * M。另外,我们必须保存所有的顶点和边,以使M * N成为空间复杂度的下限。

现在让我们分析您的解决方案。您遍历所有菜肴= N,对于每一个菜肴都遍历所有值= M,并使用O(1)来检查字典中是否总共{{1 }}。您的空间复杂度也是O(N * M)。我会说你的解决方案很好。

答案 2 :(得分:0)

您只需要在此处构建一个反向地图。

我认为您可以使用Java8中引入的Stream API以更具表现力的方式编写代码。

基本步骤:

  • 从地图上提取所有成分
  • 对于每种成分,要获得一套餐具,您将有很多这样的餐具-将所有这些餐具收集到一个餐具中-这样该方法的返回类型将变为Set<Set<String>>

以下是实现:

private static Set<Set<String>> buildReverseMap(Map<String, Set<String>> map) {
    // extracting all the values of map in a Set
    Set<String> ingredients = map.values()
            .stream()
            .flatMap(Set::stream)
            .collect(Collectors.toSet());


    return ingredients.stream()
            // map each ingredient to a set
            .map(s ->
                    map.entrySet()
                            .stream()
                            .filter(entry -> entry.getValue().contains(s))
                            .map(Map.Entry::getKey)
                            .collect(Collectors.toSet())
            ).collect(Collectors.toSet());
}

时间复杂度分析:

假设您有N个菜和M的食材,在最坏的情况下,每个dist都可以拥有所有食材。对于每种成分,您都需要遍历每道菜,并检查其中是否包含当前成分。可以在摊销的O(1)中进行此检查,因为每个菜的配料都可以作为HashSet<String>

因此,对于每种成分,您将遍历每道菜肴,并检查该菜肴是否包含在摊销的O(1)中。这使时间复杂度得以分摊O(M*N)

空间复杂度分析:

简单地O(M*N),因为在最坏的情况下,您可以使每个dist由每种可用成分组成。

注意:

只需将List<Set<String>>更改为Set<Set<String>>,就可以返回.collect(Collectors.toSet())而不是.collect(Collectors.toList())