如何获得两个数组之间的交集作为新数组?


70

在各种情况下,我多次面对这个问题。尽管我熟悉C或Java,但它对所有编程语言都是通用的。

让我们考虑两个数组(或集合):

char[] A = {'a', 'b', 'c', 'd'};
char[] B = {'c', 'd', 'e', 'f'};

如何获得两个数组之间的公共元素作为新数组?在这种情况下,数组A和B的交集为char[] c = {'c', 'd'}

我想避免一个数组在另一个数组内的重复迭代,这将使执行时间增加(A的长度乘以B的长度),这对于大型数组而言实在太多了。

有什么方法可以在每个数组中进行一次传递来获取公共元素?


23
首先对数组进行排序。然后,您只需要单次通过。
丹尼尔·菲舍尔

51
使用HashSet存储来自第一个数组的所有数据。然后,对于第二个数组中的每个元素,检查HashSet是否包含()该元素。排序的复杂度为O(n lg n),而此方法的复杂度为O(n)
user1700184 2012年

4
什么对您更重要?时间或空间效率?
Jakub Zaverka

1
数组的类型实际上是char吗?通过对类型进行一些限制,可以解决以下注释中的某些参数。例如,O(N)如果您没有针对该类型的合理的哈希函数,那么有关哈希表的所有预期事项都将消失在窗口之外(这就是Java鼓励您编写一个哈希表的原因)。相反,对于char足够大的输入,最快的方法可能是为每个字符值(通常为256或65536)创建一个包含一个元素的数组,并使用它来记录每个输入中出现哪些字符。
史蒂夫·杰索普

24
在没有明确定义“最佳”是什么的情况下,我提出了一种用于对数组进行排序的解决方案:将每个数组项目打印在一张纸上,然后将每张纸放在清晰标记的信封中。将信封邮寄给您的祖母,并附上说明,仅退还两个信封中包括的物品以及一些cookie。该解决方案虽然不是很快O(USPS^grandmother),但是最好是因为cookie很棒。
zzzzBov 2012年

Answers:


12

因为在我看来,这就像一个字符串算法,所以我暂时假设无法对该序列进行排序(因此无法对字符串进行排序),那么您可以使用最长公共序列算法(LCS)

假设输入大小恒定,则问题的复杂度为O(nxm),(两个输入的长度)


1
但是,为什么要O(n*m)在存在O(n+m)O(nlog(m))的情况下进行复杂的解决?:|
amit 2012年

1
LCS将如何给出2个字符串的交集?我们可能会错过许多字符。例如,对于s1 = [ABDCEF]和s2 = [CDEFGH],LCS将是[DEF],而2个字符串的交集是[CDEF]!我在这里想念什么吗?
srbhkmr 2012年

1
srbh.kmr,@ Gabriel,我不同意您的例子。这不是LCS的缺点,但是在您的示例中,两个输入只需共享确切的公共序列即可找到匹配项。通常它从左到右运行,但是您可以从另一个方向运行。该算法只搜索公共序列,而仅搜索公共字符,因此“一个”与“ eno”最多有一个字母匹配。这是算法的作用,而不是它的缺点
Moataz Elmasry 2012年

2
LCS不能为这个问题提供正确的答案,算法在顺序重要的字符串上工作,在这种情况下,交集始终在集合上,顺序无关紧要。@Gabriel的简单示例就在现场,我们需要对输入数据进行更多假设才能使其生效。给定排序约束,散列答案可能是最好的。
杰斯2012年

4
好吧,OP要求两个数组之间有交集。交集是集合理论中的一个概念,集合(根据定义)不依赖于其元素的顺序。我并不是说LCS出于某种原因是不好的。我只是声称它不会给您交叉路口。这是一种解决问题的算法,但不能解决(集合)交集问题。如果不将数组视为集合,则可以,但是您可能并没有要求真正的交集。但我理解您的假设的要点:如果是数组,则更多的是字符串而不是集合。
加百利

108
foreach element e in array A
    insert e into hash table H

foreach element e in array B
    if H contains e 
        print e

该算法是O(N)在时间和O(N)空间上进行的。

为了避免多余的空间,您可以使用基于排序的方法。


5
@Yola没有比O(n)更快的解决方案;不可能。
康拉德·鲁道夫

5
请注意,仍然存在重复项的问题(问题中未指定如何处理),但是通常您希望打印每个元素min{#occurances(A),#occurances(B)}或仅打印一次,而此解决方案将它们打印#occurances(B)一次
2012年

6
@Yola我们可以整天为诗歌加油。但你说“不是最快的方式”,这是错误的做法(在预期的情况下,它最快的解决方案),并在理论上(如果推来推你可以实现一个模式,要么使非线性性能极其不可能或甚至可以保证在有限域内的线性性能)。在典型的用例中,这些理论上完美的解决方案都不一定很容易确保,但是哈希对于典型的用例来说已经是最快的解决方案。
康拉德·鲁道夫

3
@Yola否,在哈希表中进行搜索需要O(1)。计算散列所需的时间取决于键的类型,但通常不比比较两个键(在树和排序中需要)慢得多。特别是对于简单的char按键,它的速度非常快。
康拉德·鲁道夫2012年

3
如果限制数据集的大小,则所有操作都需要O(1)时间。哈希查找在数据集大小上达到O(1),但达到一定限制,但也将其作为素数。由于常量较小而不是O较大,因此哈希运算速度很快。
Yakk-Adam Nevraumont 2012年

33

效率的下限是O(n)-您至少需要阅读所有元素。然后有几个方法:

哑巴最简单的方法

从第二个数组中的第一个数组中搜索每个元素。时间复杂度O(n ^ 2)。

排序方式

您只需要对数组1进行排序,然后使用二进制搜索从数组2中搜索元素。时间复杂度:排序O(nlogn),搜索O(n * logn)= O(nlogn),总O(nlogn)。

哈希方法

从数组一元素创建一个哈希表。在哈希表中的第二个表中搜索元素。时间复杂度取决于哈希函数。您可以在最佳情况下(所有元素将具有不同的哈希值)获得O(1)进行搜索,但在最坏情况下(所有元素将具有相同的哈希值)获得O(n)。总时间复杂度:O(n ^ x),其中x是哈希函数效率的一个因素(介于1和2之间)。

保证某些哈希函数可以构建没有冲突的表。但是建筑物不再为每个元素严格花费O(1)时间。在大多数情况下,它将为O(1),但是如果表已满或遇到冲突,则需要重新整理表-花费O(n)的时间。这种情况发生的频率不高,比纯净发生的频率要少得多。因此,AMORTIZED时间复杂度为O(1)。我们不关心某些耗时为O(n)的加法器,只要大多数加法器花费O(1)的时间即可。

但是即使这样,在极端情况下,必须在每次插入时重新整理表,因此严格的时间复杂度将为O(n ^ 2)


1
无需重新哈希,因为可以预先计算数组的长度,并且可以创建size为的哈希表n * LF^-1,其中LF是您的预定负载系数。复杂度取决于O(1)每个操作LF < 1,而不是每个操作O(n^LF),因为您需要的预期读取次数是E= 1 + 1*LF + 1*LF^2 + ... + 1*LF^n < CONSTANT(几何序列的总和),所以每个操作都是O(1)。也就是说,哈希表的最坏的情况下仍然O(n)每运,但平均情况下将是O(1)
艾米特

同样,可以在中完成排序方法O(NlogM),其中N是较长数组的长度,M是较短数组的长度。
阿米特2012年

1
@amit我同意,但是如果表中有冲突,仍然需要重新哈希。
Jakub Zaverka

如果您假设有针对冲突的重新哈希解决方案(而不是链接或线性开放式寻址),则可以。但这不是O(n)一般情况O(n^LF)
amit 2012年

2
@Konrad我不否认这一点。我只是在严格的时间复杂度(最坏的情况)下操作。
Jakub Zaverka

20

我知道某些语言中有几种方法可以完全满足您的要求,您是否考虑过研究其中的某些实现?

PHP- array_intersect()

$array1 = array("a" => "green", "red", "blue");
$array2 = array("b" => "green", "yellow", "red");
$result = array_intersect($array1, $array2);
print_r($result);

>> green
   red

Java- List.retainAll

Collection listOne = new ArrayList(Arrays.asList("milan","dingo", "elpha", "hafil", "meat", "iga", "neeta.peeta"));
Collection listTwo = new ArrayList(Arrays.asList("hafil", "iga", "binga", "mike", "dingo"));

listOne.retainAll( listTwo );
System.out.println( listOne );

>> dingo, hafil, iga

1
Python也可以通过set做到这一点。我想象许多语言也都有可以处理此问题的set类型。
thegrinner

我可以打赌retainAllArraylist(实际上是std java中所有List实现)都执行O(n ^ 2)。
st0le 2012年

5
    public static void main(String[] args) {
        char[] a = {'a', 'b', 'c', 'd'};
        char[] b = {'c', 'd', 'e', 'f'};
        System.out.println(intersect(a, b));
    }

    private static Set<Character> intersect(char[] a, char[] b) {
        Set<Character> aSet = new HashSet<Character>();
        Set<Character> intersection = new HashSet<Character>();
        for (char c : a) {
            aSet.add(c);
        }
        for (char c : b) {
            if (aSet.contains(c)) {
                intersection.add(c);
            }
        }
        return intersection;
    }

性能将略低于最佳性能,因为您实际上不需要创建第二组就仅检查它是否包含第二组中的元素。
Groo 2012年

我还要检查大小,如果b大而a小,则您的代码将在较大的集合上运行较慢
exussum 2012年

@ user1281385但是,在这种情况下,contains()无论集合的大小如何,处理字符始终具有O(1)复杂性。
Mik378

4
int s[256] // for considering all ascii values, serves as a hash function

for(int i=0;i<256;i++)
s[i]=0;

char a[]={'a','b','c','d'};
char b[]={'c','d','e','f'};

for(int i=0;i<sizeof(a);i++)
{
   s[a[i]]++;
 }

 for(int i=0;i<sizeof(b);i++)//checker function
 {
     if(s[b[i]]>0)
       cout<<b[i]; 
  }


  complexity O(m+n);
  m- length of array a
  n- length of array b

3

谷歌番石榴

对此已经有很多好的答案,但是如果您希望使用单行方法使用库进行延迟编码,那么我会使用Google Guava(用于Java)及其Sets.intersection方法。

(手头没有编译器,请耐心等待)

char[] A = {'a', 'b', 'c', 'd'};
char[] B = {'c', 'd', 'e', 'f'};

Set<Character> intersection = Sets.intersection(
    Sets.newHashSet<Character>(Chars.asList(a)),
    Sets.newHashSet<Character>(Chars.asList(b))
);

显然,这是假设两个数组都没有重复,在这种情况下,使用设置的数据结构将更有意义,并且可以更有效地进行这种操作,尤其是如果您从一开始就不是从原始数组开始的话。

可能适合或可能不适合您的用例,但是对于一般情况而言,这种方法很简单。


2
  1. 对两个数组进行排序。
  2. 然后循环执行,直到它们具有相同的元素,或者数组之一到达末尾。

渐近地,这需要排序的复杂性。即O(NlogN)其中N是较长输入数组的长度。


5
O(NlogM)通过单独对较短的数组进行排序,然后迭代+二进制搜索较长数组中的每个元素,可以将其改进为(N是较大arra的长度,M较短)。
amit

2

如果您关心重复项,请使用哈希映射索引列表A,其中键为元素,值为该元素被查看过的次数。

您遍历A中的第一个元素和每个元素,如果该元素在地图中不存在,则将其放入其中,值为1(如果它已存在于地图中),则将该值添加一个。

接下来,迭代B,如果该值存在,则减去1。如果不存在,则在该元素的表上的值中放入-1。

最后,遍历映射,对于任何值!= 0的元素,将其打印为差异。

private static <T> List<T> intersectArrays(List<T> a, List<T> b) {
    Map<T, Long> intersectionCountMap = new HashMap<T, Long>((((Math.max(a.size(), b.size()))*4)/3)+1);
    List<T> returnList = new LinkedList<T>();
    for(T element : a) {
        Long count = intersectionCountMap.get(element);
        if (count != null) {
            intersectionCountMap.put(element, count+1);
        } else {
            intersectionCountMap.put(element, 1L);
        }
    }
    for (T element : b) {
        Long count = intersectionCountMap.get(element);
        if (count != null) {
            intersectionCountMap.put(element, count-1);
        } else {
            intersectionCountMap.put(element, -1L);
        }            
    }
    for(T key : intersectionCountMap.keySet()) {
        Long count = intersectionCountMap.get(key);
        if (count != null && count != 0) {
            for(long i = 0; i < count; i++) {
                returnList.add(key);
            }
        }
    }
    return returnList;
}

这应该在中运行O(n),因为我们只迭代一次List和Map一次。Java中使用的数据结构应该高效,因为HashMap构造的能力可以处理最大的列表大小。

我使用aLinkedList作为返回值,因为它为我们提供了一种添加和迭代列表的方式,用于未知大小的交集。


1

最好的方法是根本不从数组开始。数组对于随机访问元素是最佳的,但对于搜索而言并非最佳(这是找到交集的全部内容)。在谈论交集时,您必须将数组视为集合。因此,请使用更合适的数据结构(在Java中为Set)。这样,任务将更加高效。


1
依靠。对于少量数据点(约少于15个),数组用于搜索的最快数据结构。
康拉德·鲁道夫2012年

…顺便说一句,Set在Java中是一个接口,可以通过线性数组实现。
康拉德·鲁道夫2012年

1

您可以使用树,但是时间将是O(n(log n))并且元素必须可比


3
相对于排序解决方案,树对于恒定数据的效率非常低。造成这种情况的主要原因是缓存-阵列比树的缓存效率高得多。(虽然它是O(nlogn),相同种类和迭代,隐藏的常数会更高)
艾米特

1

首先,使用最佳排序算法对两个数组进行排序。
然后,通过线性搜索,可以获得公共元素。

如果提供了额外的空间,那么我们可以使用哈希表来做到这一点。


1

在红宝石中,你只能说

a = ['a', 'b', 'c', 'd']
b = ['c', 'd', 'e', 'f']
c = a & b

c包含['c','d']


1

首先对两个数组进行排序,然后对其进行迭代,如果它们是相同的元素,则添加到要返回的数组中。

代码在这里:

public static void printArr(int[] arr){
    for (int a:arr){
        System.out.print(a + ", ");
    }
    System.out.println();
}

public static int[] intersectionOf(int[] arr1, int[] arr2){
    Arrays.sort(arr1);
    Arrays.sort(arr2);

    printArr(arr1);
    printArr(arr2);

    int i=0, j=0, k=0;
    int[] arr = new int[Math.min(arr1.length, arr2.length)];

    while( i < arr1.length && j < arr2.length){
        if(arr1[i] < arr2[j]){
            i++;
        } else if(arr1[i] > arr2[j]){
            j++;
        } else {
            arr[k++] = arr1[i++];
            j++;
        }
    }
    return Arrays.copyOf(arr, k);
}

public static void main(String[] args) {
    int[] arr1 = {1, 2, 6};
    int[] arr2 = {10, 2, 5, 1};
    printArr(intersectionOf(arr1,arr2));
}

输出:

arr1: 1, 2, 6, 
arr2: 1, 2, 5, 10, 
arr: 1, 2, 

0

假设您正在处理ANSI字符。该方法对于Unicode应该相似,只是更改范围。

char[] A = {'a', 'b', 'c', 'd'};
char[] B = {'c', 'd', 'e', 'f'};
int[] charset = new int[256]

for(int i=0; i<A.length; i++) {
  charset[A[i]]++;
}

现在,在B上进行迭代,您可以检查要迭代的字符的对应字符集值是否大于0。您可以将它们存储在列表或任何其他集合中。

这种方法需要O(n)的时间复杂度,并且不考虑用于存储公用元素的新数组/列表来为检查提供恒定的空间。

就空间复杂度而言,这比HashSet / Hashtable方法更好。


2
Unicode的范围(理论上)为32位-这将需要16 GiB RAM用于您的charset表。您了解人们为什么使用哈希表吗?
康拉德·鲁道夫

@KonradRudolph我认为这确实取决于问题以及人们愿意做出的取舍。如果我们要比较文件的TB级数,那么如果要坚持使用一台计算机,固定大小的字符集表将是一种更可行的方法。
Vamshidhar Behara 2012年

@KonradRudolph如果我们知道我们正在处理ASCII字符串,则上述情况在任何时候都优于哈希表方法。
Vamshidhar Behara 2012年

继续做梦。Unicode数组将为4 GiB大。即使我们有足够的RAM,随机访问模式也会破坏缓存性能。哈希表会更快。
康拉德·鲁道夫2012年

对于大数据集,是否不会在哈希表中插入O(n)?怎么会更快?我认为在处理大数据集时,map-reduce方法可能是解决此问题的更好方法。
Vamshidhar Behara 2012年

0

您可以在.NET 3.5或更高版本中使用HashSet。示例C#代码:

HashSet<int> set1 = new HashSet<int>(new int[]{8, 12, 13, 15});

HashSet<int> set2 = new HashSet<int>(new int[] { 15, 16, 7, 8, 9 });

set1.IntersectWith(set2);

foreach (int i in set1)

   Console.Write(i+ " ");

//输出:8 15


0

排序一个数组(m Log(m))现在从其他数组中选取每个元素,然后在第一个数组(排序后的一个)中进行二进制搜索-> n Log(m)

总时间复杂度:-(n + m)Log(m)


0

我希望以下内容会有所帮助。有两种不同的处理方法:

  • 简单交集,您可以将一个数组中的所有元素与另一个数组进行比较。

  • 基于排序和搜索的方法,该方法对一个数组进行排序,并使用二进制搜索在第一个数组中搜索第二个数组元素。

//

public class IntersectionOfUnsortedArrays {
    public static void main(String[] args) {
        int[] arr1 = { 12, 4, 17 };
        int[] arr2 = { 1, 12, 7, 17 };
        System.out.println("Intersection Using Simple Comparision");
        printArray(simpleIntersection(arr1, arr2));
        System.out.println("Intersection Using Sort and Binary Search");
        printArray(sortingBasedIntersection(arr1, arr2));
    }

    /*
     * Simple intersection based on the comparison without any sorting.
     * Complexity O(n^2)
     */
    public static int[] simpleIntersection(int[] a, int[] b) {
        int minlen = a.length > b.length ? b.length : a.length;
        int c[] = new int[minlen];
        int k=0;
        for(int i=0;i<a.length;i++){
            for(int j=0;j<b.length;j++){
                if(a[i]==b[j]){
                    c[k++]=a[i];
                }
            }
        }
        int arr[] = new int[k];
        // copy the final array to remove unwanted 0's from the array c
        System.arraycopy(c, 0, arr, 0, k);
        return arr;
    }

    /*
     * Sorting and Searching based intersection.
     * Complexity Sorting O(n^2) + Searching O(log n)
     */

    public static int[] sortingBasedIntersection(int[] a, int[] b){
        insertionSort(a);
        int minlen = a.length > b.length ? b.length : a.length;
        int c[] = new int[minlen];
        int k=0;
        for(int i=0;i<b.length;i++){
            int result = binarySearch(a,0,a.length,b[i]);
            if(result > -1){
                c[k++] = a[result];
            }
        }
        int arr[] = new int[k];
        // copy the final array to remove unwanted 0's from the array c
        System.arraycopy(c, 0, arr, 0, k);
        return arr;
    }

    public static void insertionSort(int array[]) {
        for (int i = 1; i < array.length; i++) {
            int j = i;
            int b = array[i];
            while ((j > 0) && (array[j - 1] > b)) {
                array[j] = array[j - 1];
                j--;
            }
            array[j] = b;
        }
    }

    static int binarySearch(int arr[], int low, int high, int num) {
        if (high < low)
            return -1;
        int mid = (low + high) / 2;
        if (num == arr[mid])
            return mid;
        if (num > arr[mid])
            return binarySearch(arr, (mid + 1), high, num);
        else
            return binarySearch(arr, low, (mid - 1), num);
    }

    public static void printArray(int[] array) {
        for (int value : array) {
            System.out.print(" "+value);
        }
        System.out.println("\n");
    }
}


0

如果已经对集合进行了排序(如问题所示),那么最好的解决方案(尚未提到)是一种运行在O(n + m)中的类似于合并排序的算法。

比较每个集合的前几个元素。如果它们相同,则将元素添加到交集并从其集合中弹出这两个元素。如果元素不同,则弹出与其他元素相比更大的元素。重复直到一个集合为空。


0

使用Java 8功能,这是一种算法,可兑现列表中的重复项而不是将列表变成一组。没有排序,所以没有n log n

  1. 将列表之一转换为地图,其值是出现的次数(成本:O(n))。
  2. 对于其他列表中的每个项目,如果该项目存在于地图中,则将出现次数减少一个(成本:O(n))。

因此,总成本为O(n)。码:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class Dup {
  public static void main(String[] args) {
    List<Integer> listA = Arrays.asList(3, 1, 4, 1, 9, 5, 9);
    List<Integer> listB = Arrays.asList(2, 6, 5, 3, 5, 8, 9, 7, 9, 3, 2, 3);
    findCommons(listA, listB);
  }

  static void findCommons(List<Integer> listA, List<Integer> listB) {
    Map<Integer, Long> mapA = 
        listA.stream().collect(
            Collectors.groupingBy(Integer::intValue, Collectors.counting()));

    List<Integer> commons = new ArrayList<>();
    listB.stream()
        .filter(e -> mapA.get(e) != null)
        .filter(e -> mapA.get(e) > 0)
        .forEach(e -> {
            mapA.put(e, mapA.get(e) - 1);
            commons.add(e);
        });

    System.out.println(commons);
  }
}

上面的代码将给出以下输出:[5, 3, 9, 9]


0

导入java.util.Scanner;

公共类arraycommon {

public static void main(String[] args) {
    Scanner sc=new Scanner(System.in);
    // display common element in two diffrent array
    int sizea,sizeb,i=0,j=0,k=0;
    int count=0;
    System.out.println("enter the size array A:"+'\n');
    sizea=sc.nextInt();
    System.out.println("enter the size array B"+'\n');
    sizeb=sc.nextInt();
    int a[]=new int[sizea];
    int b[]=new int[sizeb];
    int c[]=new int[sizea];


    System.out.println("enter the element in array A:"+'\n');
    for (i = 0; i < sizea; i++) {

        a[i]=sc.nextInt();
    }
    System.out.println("enter the element in array B:"+'\n');
    for (i = 0; i < sizeb; i++) {

        b[i]=sc.nextInt();
    }
    System.out.println("the element in array A:"+'\n');
    for (i = 0; i < sizea; i++) {

        System.out.print(a[i]+" ");

    }
    System.out.println('\n');
    System.out.println("the element in array B:"+'\n');
    for (i = 0; i < sizeb; i++) 
    {

        System.out.print(b[i]+" ");
    }

    for (i = 0; i <sizea; i++) 
    {
        for (j = 0; j < sizeb; j++) 
        {
           if(a[i]==b[j])
           {
               count++;
               c[k]=a[i];
               k=k+1;
           }
        }
    }
    System.out.println('\n');
    System.out.println("element common in array is");

    if(count==0)
    {
        System.out.println("sorry no common elements");
    }
    else
    {
        for (i = 0; i <count; i++) 
        {

        System.out.print(c[i]+" ");
        }
    }

}

}


对代码的一些解释会使此答案更有帮助。
Tot Zam

0
    simply search each element of first array with each element of second array and stored matched result in third array
class Union
{
  public static void main(String[] args) {
  char a[] ={'f','g','d','v','a'};
  char b[] ={'a','b','c','d','e'};
  char temp[] = new char[5];
  int p=0;
  for(int i=0;i<a.length;i++)
  {
    for(int j=0;j<b.length;j++)
    {
      if(a[i]==b[j])     //searches if both array has common element
      {

        temp[p] = a[i];   //if match found store it in a new array
        p++;
      }

    }

  }
  for(int k=0;k<temp.length;k++)
  {
      System.out.println(temp[k]);
  }

  }
}

这是蛮力方法,不是很有效。
严厉的沃登,2017年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.