【问题标题】:Most efficient way to return common elements from two string arrays从两个字符串数组返回公共元素的最有效方法
【发布时间】:2011-12-18 23:44:14
【问题描述】:

在 Java 中,从两个字符串数组返回公共元素的最有效方法是什么?我可以用一对 for 循环来做到这一点,但这似乎不是很有效。根据我对similar SO question 的评论,我能想到的最好方法是转换为List,然后应用retainAll

List<String> compareList = Arrays.asList(strArr1);
List<String> baseList = Arrays.asList(strArr2);
baseList.retainAll(compareList);

【问题讨论】:

    标签: java arrays compare


    【解决方案1】:

    已编辑:

    这是一个单行:

    compareList.retainAll(new HashSet<String>(baseList));
    

    retainAll impl(在 AbstractCollection 中)迭代 this,并在参数上使用 contains()。将参数转换为HashSet 将导致快速查找,因此retainAll 内的循环将尽快执行。

    另外,baseList 这个名字暗示它是一个常数,所以如果你缓存它,你会得到显着的性能提升:

    static final Set<String> BASE = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList("one", "two", "three", "etc")));
    
    static void retainCommonWithBase(Collection<String> strings) {
        strings.retainAll(BASE);
    }
    

    如果要保留原始列表,请执行以下操作:

    static List<String> retainCommonWithBase(List<String> strings) {
       List<String> result = new ArrayList<String>(strings);
       result.retainAll(BASE);
       return result;
    }
    

    【讨论】:

    • retainAll 似乎在集合上进行迭代,而不是在集合上查找(这有点奇怪,它可能已被所有基于哈希的集合覆盖)
    • @SebastienLorber 感谢您指出这一点。我已将您的评论纳入我的编辑中
    【解决方案2】:

    对两个数组进行排序。

    排序后,您可以使用两个索引对两个排序后的数组进行一次迭代。

    这将是 O(NlogN)。

    【讨论】:

    • @Burleigh 你是如何到达 n log n 的?
    • 或多或少地滥用了符号:)。对数组进行排序是数组长度中的 NlogN。渐近地,我们只需要考虑更长的数组 - 让我们称之为 N(假设字符串之间的比较是固定成本,这也是不正确的)。所以排序阶段是O(NLogN)。为了找到共同的元素,我们可以按顺序遍历数组,只有这样是 O(N),再次假设比较是固定成本。我想说顺序是 O(MNlogN) 更准确,其中 M 是任一数组中最长字符串的长度。
    【解决方案3】:

    然后我会使用 HashSets(和 retainAll),这将使整个检查 O(n)(对于第一个集合查找中的每个元素,如果它存在 (contains()),这是 O(1)哈希集)。不过Lists 的创建速度更快(HashSet 可能必须处理冲突......)。

    请记住,SetList 具有不同的语义(列表允许重复元素、空值...)。

    【讨论】:

    • 但是我们不需要执行查找,我们需要依次迭代元素。
    • 比暴力破解更好,但并不完全正确。插入哈希集不是 O(1),由于冲突,大多数时候它甚至不是 O(n)。
    【解决方案4】:

    列表不支持全部保留。改用 set:

    import java.util.*;
    public class Main {
        public static void main(String[] args) {
            String[] strings1={"a","b","b","c"},strings2={"b","c","c","d"};
            List<String> list=Arrays.asList(strings1);
            //list.retainAll(Arrays.asList(strings2)); // throws UnsupportedOperationException
            //System.out.println(list);
            Set<String> set=new LinkedHashSet<String>(Arrays.asList(strings1));
            set.retainAll(Arrays.asList(strings2));
            System.out.println(set);
        }
    }
    

    【讨论】:

    【解决方案5】:

    你想要的叫做交集。 看到: Intersection and union of ArrayLists in Java

    使用基于哈希的集合提供了一种非常快速的 contains() 方法,尤其是在具有优化哈希码的字符串上。


    如果可以导入库,可以考虑使用 Guava 的 Sets.intersection。


    编辑:

    不知道retainAll方法。

    请注意,对于 HashSets 和 LinkedHashSets 似乎没有覆盖的 AbstractCollection 实现是:

    public boolean retainAll(Collection c) { 布尔修改=假; 迭代器它 = 迭代器(); 而(it.hasNext()){ if (!c.contains(it.next())) { it.remove(); 修改=真; } } 返回修改; }

    这意味着您在集合参数上调用 contains()! 这意味着如果您传递一个 List 参数,您将在每次迭代时对列表的许多项目进行 equals 调用!

    这就是为什么我不认为上述使用retainAll的实现是好的。

    public <T> List<T> intersection(List<T> list1, List<T> list2) {
        boolean firstIsBigger = list1.size() > list2.size();
        List<T> big =  firstIsBigger ? list1:list2;
        Set<T> small =  firstIsBigger ? new HashSet<T>(list2) : new HashSet<T>(list1);
        return big.retainsAll(small)
    }
    

    选择将 Set 用于最小的列表,因为它可以更快地构建集合,并且大列表可以很好地迭代......

    请注意,原始列表参数之一可能会被修改,由您来制作副本...

    【讨论】:

    • 嗯,ArrayList 上的 retainAll 方法被覆盖。我这里没有任何 IDE,但这是我在 javadoc 中找到的
    【解决方案6】:

    我接受了一次面试,这个问题是他们在技术面试中问我的问题。我的答案是以下几行代码:

    public static void main(String[] args) {
    
            String[] temp1 = {"a", "b", "c"};
            String[] temp2 = {"c", "d", "a", "e", "f"};
            String[] temp3 = {"b", "c", "a", "a", "f"};
    
            ArrayList<String> list1 = new ArrayList<String>(Arrays.asList(temp1));
            System.out.println("list1: " + list1);
            ArrayList<String> list2 = new ArrayList<String>(Arrays.asList(temp2));
            System.out.println("list2: " + list2);
            ArrayList<String> list3 = new ArrayList<String>(Arrays.asList(temp3));
            System.out.println("list3: " + list3);
    
            list1.retainAll(list2);
            list1.retainAll(list3);
            for (String str : list1)
                System.out.println("Commons: " + str);
    }
    

    输出:

    list1: [a, b, c]
    list2: [c, d, a, e, f]
    list3: [b, c, a, a, f]
    Commons: a
    Commons: c
    

    【讨论】:

    • 您的 list1 和 list2 不会打印出元素而是地址。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-20
    • 1970-01-01
    • 2011-01-19
    • 2014-04-02
    • 1970-01-01
    • 2020-11-13
    相关资源
    最近更新 更多