【问题标题】:How to intersect two sorted integer arrays without duplicates?如何相交两个无重复的排序整数数组?
【发布时间】:2012-03-03 04:41:54
【问题描述】:

这是我用作编程练习的一个面试问题。

输入: 两个排序的整数数组 A 和 B,按升序排列,大小分别为 N 和 M

输出:按升序排列的排序整数数组 C,其中包含同时出现在 A 和 B 中的元素

约束:C中不允许重复

示例: 对于输入 A = {3,6,8,9} 和 B = {4,5,6,9,10,11},输出应为 C = {6 ,9}

谢谢大家的回答!总而言之,解决这个问题有两种主要方法:

我最初的解决方案是保留两个指针,每个数组一个,从左到右交替扫描数组,同时挑选出匹配的元素。因此,当我们一个数组的当前元素大于第二个数组时,我们会不断增加第二个数组的指针,直到找到当前的第一个数组元素或超过它(找到一个更大的)。我将所有匹配项保存在一个单独的数组中,一旦我们到达任一输入数组的末尾,就会返回该数组。

我们可以做到这一点的另一种方法是线性扫描其中一个数组,同时使用二进制搜索在第二个数组中找到匹配项。这意味着 O(N*log(M)) 时间,如果我们扫描 A 并在 B 上对它的 N 个元素中的每一个进行二进制搜索(O(log(M)) 时间)。

我已经实现了这两种方法并运行了一个实验来看看这两种方法的比较(可以在here 找到有关这方面的详细信息)。当 N 有 100 万个元素时,当 M 大约是 N 的 70 倍时,二分搜索方法似乎更胜一筹。

【问题讨论】:

  • 请告诉我们您的问题?
  • 这应该改为进行代码审查
  • 仅仅因为一个数组更大,并不意味着将两个数组组合起来会产生相同的大小。
  • @BrianGraham OP 正在程序末尾创建具有适当大小的新数组,因此这应该不是问题。
  • @AKJ 如果我有[0, 1, 2, 3, 4][5, 6, 4, 9, 8],则生成的交叉点大于他确定大小的方式;导致缺失值。

标签: java arrays algorithm sorting


【解决方案1】:

怎么样:

public static int[] intersectSortedArrays(int[] a, int[] b){
    int[] c = new int[Math.min(a.length, b.length)]; 
    int ai = 0, bi = 0, ci = 0;
    while (ai < a.length && bi < b.length) {
        if (a[ai] < b[bi]) {
            ai++;
        } else if (a[ai] > b[bi]) {
            bi++;
        } else {
            if (ci == 0 || a[ai] != c[ci - 1]) {
                c[ci++] = a[ai];
            }
            ai++; bi++;
        }
    }
    return Arrays.copyOfRange(c, 0, ci); 
}

从概念上讲,它与您的相似,但包含许多简化。

我认为你无法提高时间复杂度。

编辑:我试过这段代码,它通过了你所有的单元测试。

【讨论】:

  • @izomorphius:很好,已修复。
  • @aix 我在这里看不到循环。如果索引超出数组长度怎么办。
  • @AKJ:我发布的第一个版本中没有循环(由于我的一个奇怪的复制和粘贴错误)。我前段时间已经解决了。如果您刷新,您应该会看到正确的版本。
  • @AKJ:我不确定您所说的“索引超出数组长度”是什么意思。 aibiwhile 条件约束,c 在构造上足够大。
  • @aix,我正在查看没有 while 循环的代码,因此很混乱。
【解决方案2】:

如果您正在使用“整数”(对象)数组并希望使用 java API 方法,您可以查看以下代码。请注意,下面的代码可能比上面列出的原始方法更复杂(因为它使用一些从一个数据结构到另一个数据结构的转换逻辑)和内存消耗(因为使用对象)。我刚试过(耸耸肩):

public class MergeCollections {
    public static void main(String[] args) {
        Integer[] intArray1 = new Integer[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        Integer[] intArray2 = new Integer[] {2, 3, 5, 7, 8, 11, 13};

        Set<Integer> intSet1 = new TreeSet<Integer>();
        intSet1.addAll(Arrays.asList(intArray1));
        intSet1.addAll(Arrays.asList(intArray2));
        System.out.println(intSet1);
    }
}

还有输出:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13]

另外,请查看此链接:Algolist - Algo to merge sorted arrays

编辑:将 HashSet 更改为 TreeSet

编辑 2:现在问题已经编辑清楚,我添加了一个简单的解决方案来查找交集:

public class Intersection {
    public static void main(String[] args) {
        Integer[] intArray1 = new Integer[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        Integer[] intArray2 = new Integer[] {2, 3, 5, 7, 8, 11, 13};

        List<Integer> list1 = Arrays.asList(intArray1);
        Set<Integer> commonSet = new TreeSet<Integer>();
        for(Integer i: intArray2) {
            if(list1.contains(i)) {
                commonSet.add(i);
            }
        }

        System.out.println(commonSet);
    }
}

【讨论】:

  • 虽然 TreeSet (etc) 可能很好用,但这更符合习惯。
  • 另外,有点白痴(特别是如果有人试图学习算法)。 :)
  • 托尼,试图发布一个快速的解决方案,但忘记了。我编辑了代码以使用 TreeSet。谢谢你的建议。 :)
  • 您的解决方案是更好的面试答案。我可能会拒绝回答“C-like”解决方案的受访者。
  • 实际上,问题要求与数组相交,而不是合并它们。
【解决方案3】:

不知道这样解决问题好不好:

  A,B are 1 based arrays
    A.length=m
    B.length=n

1) 初始化一个长度为 min(m,n) 的数组 C

2) 通过检查第一个和最后一个只关注公共部分 元素。这里可以使用二进制搜索。举个例子来保存 一些话:

 A[11,13,15,18,20,28,29,80,90,100.........300,400]
    ^                                          ^
 B[3,4,5,6,7.8.9.10.12,14,16,18,20,..400.....9999]
                     ^                ^


then we need only focus  on

    A[start=1](11)-A[end=m](400)
    and
    B[start=9](12)-B[end](400)

3)。比较两个数组的 范围 (end-start)。取具有较小 范围 的数组,例如 A,对于来自 A[start] ~ A[end] 的每个元素 A[i],在 B[start,end] 中进行二分搜索,

  • 如果找到,将元素放入C,将B.start重置为foundIdx+1,

  • 否则 B.start 被设置为最小元素 [j],其中 B[j] 为 大于A[i],缩小范围

4) 继续 3) 直到处理完 A[start, end] 中的所有元素。

  • 通过第1步,我们可以找到没有交集的情况 两个数组。
  • 在步骤 3 中进行二分搜索时,我们将 A[i] 与 A[i-1] 进行比较,如果 同样,跳过 A[i]。为了保持 C 中的元素是唯一的。

这样,更糟糕的情况是 lg(n!) if(A and B are same) ?不确定。

平均案例?

【讨论】:

    【解决方案4】:

    这个问题本质上简化为 join 操作,然后是 filter 操作(删除重复项并仅保留内部匹配项)。

    由于输入都已经排序,因此可以通过merge join 有效地实现连接,使用 O(size(a) + size(b))。

    filter 操作将是 O(n),因为连接的输出已排序,并且要删除重复项,您所要做的就是检查每个元素是否与之​​前的元素相同.只过滤内部匹配是微不足道的,您只需丢弃任何不匹配的元素(外部连接)。

    并行性(在连接和过滤器中)有机会获得更好的性能。例如,Hadoop 上的Apache Pig 框架提供了合并连接的parallel implementation

    在性能和复杂性(以及可维护性)之间存在明显的权衡。所以我想说一个面试问题的好答案确实需要考虑到性能需求。

    • 基于集合的比较 - O(nlogn) - 相对较慢,非常简单,如果没有性能问题,请使用。简单取胜。

    • Merge join + Filter - O(n) - 快速,容易出现编码错误,使用 if 性能是个问题。理想情况下,尝试利用现有库来执行此操作,或者在适当的情况下甚至可以使用数据库。

    • 并行实现 - O(n/p) - 非常 速度快,需要其他基础设施,如果卷是 非常大,预计会增长,这是一个主要的表现 瓶颈。

    (另请注意,问题 intersectSortedArrays 中的函数本质上是修改后的合并连接,其中过滤器在连接期间完成。您可以在没有性能损失的情况下进行过滤,尽管内存略有增加足迹)。

    最后的想法。

    事实上,我怀疑大多数现代商业 RDBMS 在它们的连接实现中提供线程并行,所以 Hadoop 版本提供的是机器级并行(分布)。从设计的角度来看,这个问题的一个好的、简单的解决方案可能是将数据放在数据库中,在 A 和 B 上建立索引(有效地对数据进行排序)并使用 SQL 内连接。

    【讨论】:

    • 非常好的连接——我现在可以看到这个问题在 DBMS 的上下文中是如何相关的(并且可能是最普遍的)。
    【解决方案5】:

    这是一个内存改进:

    最好将结果 (C) 存储在动态结构中,如链表,并在找到相交元素后创建一个数组(与数组 r 完全相同)。如果您有非常大的 A 和 B 数组并且期望公共元素相比之下很少,这种技术将特别好(当您只需要少量时,为什么要搜索大量的连续内存?)。

    编辑:我还要改变的一件事,这可能只是有点挑剔,那就是在事先知道最坏情况的迭代次数时,我会避免使用未绑定循环。

    【讨论】:

    • Big Theta 不是比 Big Oh 更紧密吗?我认为在我的解决方案中,最坏的情况逐渐等同于最好的情况,因此我使用了 Big Theta。我发现了一个有趣的 SO 讨论 here
    • Eek,对不起,我有点累了,把 Theta 读成了 Omega(不是逐字逐句,而是在意思上)。你完全正确,我已经编辑了我的帖子。也就是说,这篇文章的主要目的是解释使用动态数据结构将是一个非常好的主意,因为您不需要完全搜索,并且无论如何您最终都会将其解析为一个新数组。
    【解决方案6】:

    使用 arraylist 存储结果。

    public ArrayList<Integer> arrayIntersection(int [] a, int[] b)
    {
        int len_a=a.length;
        int len_b=b.length;
        int i=0;
        int j=0;
        ArrayList<Integer> alist=new ArrayList();
    
        while(i<len_a && j<len_b)
        {
            if(a[i]<b[j])
                i++;
            else if(a[i]>b[j])
                j++;
            else if(a[i]==b[j])
            {
                alist.add(a[i]);
                i++;
                j++;
    
            }
        }
    
       return alist;    
      }
    

    【讨论】:

    • 只是为了向新读者澄清一下,此解决方案中的结果可能包含重复值。
    最近更新 更多