Find all numbers that appear in each of a set of lists
我有几个整数对象的 ArrayLists,存储在 HashMap 中。
我想获取每个列表中出现的所有数字(整数对象)的列表(ArrayList)。
到目前为止我的想法是:
- 这将为我们提供列表中所有值的”列表”,但只有一次
2.1 每次迭代执行 ArrayList.contains()
2.2 如果 ArrayLists 都没有为操作返回 false,则将该数字添加到包含所有最终值的”主列表”中。
如果你能想出更快或更高效的方法,有趣的是,当我写这篇文章时,我想出了一个相当不错的解决方案。但我仍然会发布它以防万一它对其他人有用。
当然,如果您有更好的方法,请告诉我。
- 您的第一个解决方案将在 O(n) 时间内完成,无需额外的存储空间,我非常怀疑您能否击败它。
- 感谢您为我的直觉增添了一些严谨性;)
- 如果您的两个列表是 [1, 1, 2] 和 [1, 1, 3],您希望输出是 [1, 1] 还是只是 [1]?即您是否希望保留重复项?
- 只是 1 – 我不需要重复 – 为反应缓慢而道歉,昨天正在打高尔夫球(当你们为我做我的工作时,我感觉很糟糕)
我不确定我是否理解您的目标。但是,如果您希望找到 List 对象集合的交集,则可以执行以下操作:
1
2 3 4 5 6 7 8 9 10 11 |
public static List<Integer> intersection(Collection<List<Integer>> lists){
if (lists.size()==0) return Collections.emptyList(); Iterator<List<Integer>> it = lists.iterator(); return new ArrayList<Integer>(resSet); |
此代码在项目总数中以线性时间运行。实际上这是平均线性时间,因为使用了 HashSet。
另外,请注意,如果您在循环中使用 ArrayList.contains(),可能会导致二次复杂度,因为此方法在线性时间运行,而 HashSet.contains() 在恒定时间运行。
- 可能值得在你的 while 循环中对 resSet 进行空检查。
- 哦,您不需要为每个 it.next() 构造一个新的哈希集 – retainAll 适用于集合,并且 it.next() 中的重复元素不会影响操作。
- 编辑:我想对于某些retainAll情况有一些节省,但在这种特殊情况下,自定义方法可能无论如何都是有序的。
- @Carl:如果我在列表本身上使用retainAll,它会增加时间复杂度。当 Y 是一个简单的 List 实现时,X.retainAll(Y) 在 O(|X|*|Y|) 时间内工作。当 Y 为 HashSet 时,它的工作时间平均为 O(|X|),所以复制是值得的。
您必须更改第 1 步:
– 使用最短列表而不是您的 hashSet(如果它不在最短列表中,则它不在所有列表中……)
然后在其他列表中调用 contains 并在一个返回 false 时删除值(并跳过对该值的进一步测试)
最后,最短的列表将包含答案…
一些代码:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
public class TestLists {
private static List<List<Integer>> listOfLists = new ArrayList<List<Integer>>(); private static List<Integer> filter(List<List<Integer>> listOfLists) { // find the shortest list // create result list from the shortest list // remove elements not present in all list from the result list // if one list doesn’t contain value, remove from result and break loop return result;
public static void main(String[] args) { } } |
使用 Google Collections Multiset 使这(表示方式)变得轻而易举(尽管我也喜欢 Eyal 的回答)。它可能不如这里的其他一些在时间/内存方面有效,但很清楚发生了什么。
假设列表本身不包含重复项:
1
2 3 4 5 6 7 8 9 10 11 12 |
Multiset<Integer> counter = HashMultiset.create();
int totalLists = 0; // for each of your ArrayLists { counter.addAll(list); totalLists++; } List<Integer> inAll = Lists.newArrayList(); for (Integer candidate : counter.elementSet()) |
如果列表可能包含重复的元素,它们可以先通过一个集合:
1
|
counter.addAll(list) => counter.addAll(Sets.newHashSet(list))
|
最后,如果您希望稍后可能需要一些额外的数据(例如,某个特定值与切入点有多接近),这也是理想的选择。
另一种稍微修改了 Eyal 的方法(基本上将通过集合过滤列表然后保留所有重叠元素的行为折叠在一起),并且比上述更轻量级:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public List<Integer> intersection(Iterable<List<Integer>> lists) {
Iterator<List<Integer>> listsIter = lists.iterator(); |
- 如果 List 和 Set 都足够小,则调用 set.retainAll (list)
- 否则调用 set.retainAll (new HashSet <Integer> (list))
我不能说在哪个阈值之后步骤 2 的第二个变体变得更快,但我猜可能是 > 20 大小左右。如果你的列表都很小,你可以不用这个检查。
我记得如果您不仅关心 O(*) 部分,而且关心因子,那么 Apache 集合具有更有效的纯整数结构。
- 这是 Ankurs 第一个解决方案的可怕突变,为地图中的每个列表创建一个新的 HashSet 基本上会导致你浪费一些 O(n^2) 空间。这是java,GC是不确定的。 GC 可以在未知的时间后收集未使用的哈希集,这意味着 O(n^2) 量的内存将坐在那里,分配,但不投入使用。或者换句话说,浪费了。
- @Rubys:我看不出你从哪里得到 O(n^2)。如果我不清楚 set 是在第一步中创建的。 IE。在整个循环中都是一样的。在步骤 2a 中创建”中间”集是为了加快查找速度(在 retainAll 中),因为在哈希集中它是(预期的)O(1) 与列表中的 O(n)。
- 据我们所知,列表和集合永远都不够小,并且在每次迭代中,您都会创建一个新的 HashSet。 hashet 本身将占用内存中的 O(n) 空间。它不是 O(n^2),那是我的错,它的 O(nm) 空间,其中 n 是最大的列表,m 是原始集合中的列表数。您会看到,在每次迭代中,您都会创建一个新的哈希集,这会花费 O(n) 空间。由于您必须将这些指针放在某处-。因此,在所有 m 次迭代中,您将使用 O(nm) SPACE。时光依旧美好。
来源:https://www.codenong.com/2765478/