我正在研究以下问题:
假设您有一个菜品列表,其中每个菜都与 成分清单。将具有常用成分的菜肴分组在一起。
例如:
输入:
"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());
}
答案 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())