【问题标题】:Find all numbers that appear in each of a set of lists查找出现在一组列表中的所有数字
【发布时间】:2010-05-04 13:05:32
【问题描述】:

我有几个整数对象的 ArrayLists,存储在 HashMap 中。

我想获取每个列表中出现的所有数字(整数对象)的列表(ArrayList)。

到目前为止,我的想法是:

  1. 遍历每个 ArrayList 并将所有值放入 HashSet
    • 这将为我们提供列表中所有值的“列表”,但只有一次
  2. 遍历 HashSet
    2.1 每次迭代执行 ArrayList.contains()
    2.2 如果所有 ArrayList 都没有为操作返回 false,则将该数字添加到包含所有最终值的“主列表”中。

如果你能想出更快或更高效的方法,有趣的是,当我写这篇文章时,我想出了一个相当不错的解决方案。但我仍然会发布它以防万一它对其他人有用。

当然,如果您有更好的方法,请告诉我。

【问题讨论】:

  • 您的第一个解决方案将在 O(n) 时间内完成,无需额外的存储空间,我非常怀疑您能否战胜它。
  • 感谢您为我的直觉增添了一些严谨性;)
  • 如果您的两个列表是 [1, 1, 2] 和 [1, 1, 3],您希望输出是 [1, 1] 还是只是 [1]?即您是否希望保留重复项?
  • 只有 1 - 我不需要重复 - 抱歉响应缓慢,昨天正在打高尔夫球(当你们为我工作时,我感觉很糟糕)

标签: java algorithm reporting arraylist


【解决方案1】:

我不确定我是否理解您的目标。但是,如果您希望找到 List 对象集合的交集,则可以执行以下操作:

public static List<Integer> intersection(Collection<List<Integer>> lists){
    if (lists.size()==0)
        return Collections.emptyList();

    Iterator<List<Integer>> it = lists.iterator();
    HashSet<Integer> resSet = new HashSet<Integer>(it.next());
    while (it.hasNext())
        resSet.retainAll(new HashSet<Integer>(it.next()));

    return new ArrayList<Integer>(resSet);
}

此代码在项目总数中以线性时间运行。实际上这是平均线性时间,因为使用了 HashSet。

另外,请注意,如果您在循环中使用 ArrayList.contains(),可能会导致二次复杂度,因为此方法在线性时间运行,而 HashSet.contains() 在恒定时间运行。

【讨论】:

  • 可能值得在你的 while 循环中对 resSet 进行空检查。
  • 哦,你不需要为每个 it.next() 构造一个新的哈希集——retainAll 适用于集合,并且 it.next() 中的重复元素不会影响操作.
  • edit:我想在某些情况下使用 retainAll 可以节省一些费用,但在这种特殊情况下,自定义方法可能无论如何都是合适的。
  • @Carl:如果我在列表本身上使用retainAll,会增加时间复杂度。当 Y 是一个简单的 List 实现时,X.retainAll(Y) 在 O(|X|*|Y|) 时间内工作。 Y为HashSet时,平均工作时间为O(|X|),所以复制是值得的。
【解决方案2】:

您必须更改第 1 步: - 使用最短列表而不是您的 hashSet(如果它不在最短列表中,则它不在所有列表中......)

然后在其他列表中调用 contains 并在一个返回 false 时删除值(并跳过对该值的进一步测试)

最后,最短的列表将包含答案...

一些代码:

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
        List<Integer> shortestList = null;
        for (List<Integer> list : listOfLists) {
            if (shortestList == null || list.size() < shortestList.size()) {
                shortestList = list;
            }
        }

        // create result list from the shortest list
        final List<Integer> result = new LinkedList<Integer>(shortestList);

        // remove elements not present in all list from the result list
        for (Integer valueToTest : shortestList) {
            for (List<Integer> list : listOfLists) {
                // no need to compare to itself
                if (shortestList == list) {
                    continue;
                }

                // if one list doesn't contain value, remove from result and break loop
                if (!list.contains(valueToTest)) {
                    result.remove(valueToTest);
                    break;
                }
            }
        }

        return result;
    }


    public static void main(String[] args) {
        List<Integer> l1 = new ArrayList<Integer>(){{
            add(100);
            add(200);
        }};
        List<Integer> l2 = new ArrayList<Integer>(){{
            add(100);
            add(200);
            add(300);
        }};
        List<Integer> l3 = new ArrayList<Integer>(){{
            add(100);
            add(200);
            add(300);
        }};
        List<Integer> l4 = new ArrayList<Integer>(){{
            add(100);
            add(200);
            add(300);
        }};
        List<Integer> l5 = new ArrayList<Integer>(){{
            add(100);
            add(200);
            add(300);
        }};
        listOfLists.add(l1);
        listOfLists.add(l2);
        listOfLists.add(l3);
        listOfLists.add(l4);
        listOfLists.add(l5);
        System.out.println(filter(listOfLists));

    }

}

【讨论】:

    【解决方案3】:
    1. 从第一个 List 创建一个 Set(例如 HashSet)。
    2. 对于每个剩余的列表:
      • 如果listset 都足够小,请致电set.retainAll (list)
      • 否则请致电set.retainAll (new HashSet &lt;Integer&gt; (list))

    我不能说第 2 步的第二个变体在哪个阈值之后变得更快,但我猜&gt; 20 的大小可能会这样。如果你的列表都很小,你可以不用这个检查。

    据我所知,如果您不仅关心 O(*) 部分,而且关心因子,那么 Apache 集合具有更高效的纯整数结构。

    【讨论】:

    • 这是 Ankur 第一个解决方案的可怕突变,为地图中的每个列表创建一个新的 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。时光依旧美好。
    【解决方案4】:

    使用 Google 收藏集 Multiset 使这(在表示方面)变得轻而易举(尽管我也喜欢 Eyal's answer)。它在时间/内存方面可能不如这里的其他一些高效,但很清楚发生了什么。

    假设列表本身不包含重复项:

    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())
      if (counter.count(candidate) == totalLists) inAll.add(candidate);`
    

    如果列表可能包含重复元素,则可以先通过集合:

    counter.addAll(list) => counter.addAll(Sets.newHashSet(list))
    

    最后,如果您希望稍后可能需要一些额外的数据(例如,某个特定值与切入点有多接近),这也是理想的选择。

    另一种稍微修改了 Eyal 的方法(基本上将通过集合过滤列表然后保留所有重叠元素的行为折叠在一起),并且比上述更轻量级:

    public List<Integer> intersection(Iterable<List<Integer>> lists) {
    
     Iterator<List<Integer>> listsIter = lists.iterator();
     if (!listsIter.hasNext()) return Collections.emptyList();
     Set<Integer> bag = new HashSet<Integer>(listsIter.next());
     while (listsIter.hasNext() && !bag.isEmpty()) { 
      Iterator<Integer> itemIter = listsIter.next().iterator();
      Set<Integer> holder = new HashSet<Integer>(); //perhaps also pre-size it to the bag size
      Integer held;
      while (itemIter.hasNext() && !bag.isEmpty())
       if ( bag.remove(held = itemIter.next()) )
        holder.add(held);
      bag = holder;
     }
     return new ArrayList<Integer>(bag);
    }
    

    【讨论】:

      猜你喜欢
      • 2020-07-24
      • 1970-01-01
      • 1970-01-01
      • 2012-04-06
      • 2020-03-18
      • 2011-09-11
      • 2012-11-08
      相关资源
      最近更新 更多