这是一个我正在使用的面试问题。
输入:两个排序的整数数组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并且对于它的每个N元素在B上进行二进制搜索(O(log(M))时间)。
我已经实施了这两种方法并进行了一项实验,看看两者的比较方式(详细信息可以找到here)。当N大约是N的70倍时,当N有100万个元素时,二元搜索方法似乎赢了。
答案 0 :(得分:6)
怎么样:
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);
}
从概念上讲,它与你的相似,但包含许多简化。
我认为你不能提高时间复杂度。
编辑:我已尝试过此代码,并通过了所有单元测试。
答案 1 :(得分:5)
此问题本质上简化为 join 操作,然后是过滤器操作(删除重复项并仅保留内部匹配项)。
由于输入都已经排序,因此可以通过merge join有效地实现连接,其中O(大小(a)+大小(b))。
过滤器操作将是O(n),因为连接的输出已排序并且要删除重复项,您只需检查每个元素是否与之前的元素相同。仅过滤内部匹配是微不足道的,您只需丢弃任何未匹配的元素(外部联接)。
并行性(在连接和过滤器中)都有机会获得更好的性能。例如,Hadoop上的Apache Pig框架提供parallel implementation合并连接。
在性能和复杂性(以及可维护性)之间存在明显的权衡。所以我想说一个面试问题的好答案确实需要考虑到性能要求。
基于集合的比较 - O(nlogn) - 相对较慢,非常简单,如果没有性能问题则使用。简单胜利。
合并连接+过滤器 - O(n) - 快速,容易出现编码错误,请使用if 表现是一个问题。理想情况下,尝试利用现有的库来执行此操作,或者甚至在适当的情况下使用数据库。
并行实现 - O(n / p) - 非常 快速,需要其他基础设施,如果卷是使用 非常大,预计会增长,这是一个重要的表现 瓶颈。
(另请注意,问题 intersectSortedArrays 中的函数本质上是一个修改后的合并连接,其中过滤器在连接期间完成。您可以在之后进行过滤而不会丢失性能,尽管内存略有增加足迹)。
最后的想法。
事实上,我怀疑大多数现代商业RDBMS在其连接实现中提供线程并行性,因此Hadoop版本提供的是机器级并行(分发)。从设计的角度来看,问题的一个好的,简单的解决方案可能是将数据放在数据库上,索引在A和B上(有效地对数据进行排序)并使用SQL内连接。
答案 2 :(得分:3)
使用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;
}
答案 3 :(得分:0)
如果您正在使用'Integer'(对象)数组并且想要使用java API方法,则可以检查以下代码。请注意,下面的代码可能具有更多的复杂性(因为它使用从一个数据结构到其他的一些转换逻辑)和内存消耗(因为使用对象)而不是基本方法,如上所列。我刚尝试过( shrugs ):
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);
}
}
答案 4 :(得分:0)
我不知道以这种方式解决问题是否是一个好主意:
说
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[i]
的每个元素A[start] ~ A[end]
,使用较小范围(例如A)的数组,在B[start,end]
中执行二进制搜索,
如果找到,将元素放入C,将B.start重置为foundIdx + 1,
否则B.start设置为最小元素[j],其中B [j]为 大于A [i],缩小范围
4)继续3)直到处理A [start,end]中的所有元素。
这样,更糟糕的情况是lg(n!)if(A和B是否相同)?不确定。
平均案例?
答案 5 :(得分:0)
这是一项记忆改进:
最好将结果(C)存储在动态结构(如链表)中,并在找到相交元素后创建数组(与数组r完全相同)。如果你有一个非常大的A和B数组并且期望相比较少的公共元素(为什么在你只需要少量时搜索大量的连续内存?),这种技术会特别好。
编辑:我还会改变一件事,这可能只是有点挑剔,我会避免在最坏情况下的迭代次数事先知道时使用未绑定的循环。