找到两个排序数组的交集,在某些情况下需要少于O(m + n)个比较

时间:2012-11-22 03:54:52

标签: python performance algorithm

以下是在O(m+n)中执行此操作的一种方法,其中mn是两个数组的长度:

import random

def comm_seq(arr_1, arr_2):
    if len(arr_1) == 0 or len(arr_2) == 0:
        return []

    m = len(arr_1) - 1
    n = len(arr_2) - 1

    if arr_1[m] == arr_2[n]:
        return comm_seq(arr_1[:-1], arr_2[:-1]) + [arr_1[m]]

    elif arr_1[m] < arr_2[n]:
        return comm_seq(arr_1, arr_2[:-1])

    elif arr_1[m] > arr_2[n]:
        return comm_seq(arr_1[:-1], arr_2)


if __name__ == "__main__":
    arr_1 = [random.randrange(0,5) for _ in xrange(10)]
    arr_2 = [random.randrange(0,5) for _ in xrange(10)]
    arr_1.sort()
    arr_2.sort()
    print comm_seq(arr_1, arr_2)

是否有一种技术在某些情况下使用的比较少于O(m+n)?例如:arr_1=[1,2,2,2,2,2,2,2,2,2,2,100]arr_2=[1,3,100]

(不寻找哈希表实现)

4 个答案:

答案 0 :(得分:5)

二进制搜索算法需要O(logm)时间来查找长度为m的数组中的数字。 因此,如果我们从长度为m的数组中搜索长度为n的数组的每个数字,则其总时间复杂度为O(nlogm)如果m远大于n ,则O(nlogm)实际上小于O(m+n)。因此,在这种情况下,我们可以基于二分搜索实现一种新的更好的解决方案。 source

然而,这并不一定意味着二元搜索比O(m + n)情况更好。事实上,当n <&lt;&lt;&lt; m(与m相比,n非常小)。

答案 1 :(得分:5)

据我所知,有几种不同的方法可以解决这个问题, 但没有一种方法比O(m + n) 更好。我不知道你怎么能有比这更快的算法(除非奇怪的量子计算答案),因为你必须比较两个数组中的所有元素,否则你可能会错过重复。

蛮力 使用两个嵌套for循环。从第一个数组中取出每个元素,并在第二个数组中进行线性搜索。 O(M * N)时间,O(1)空间

地图搜寻 使用哈希表或二叉搜索树之类的查找结构。将所有第一个数组放入地图结构中,然后遍历所有第二个数组并查找地图中的每个元素以查看它是否存在。无论数组是否排序,这都有效。二进制搜索树时间的 O(M * log(M)+ N * log(M))或Hashtable的O(M + N)时间,都是O(M)空间。

二进制搜索 像蛮力一样,但是从第一个数组中获取每个元素,并在第二个数组中搜索它。 O(m * log(N))时间,O(1)空间

并行行走 像合并排序的合并部分。从每个数组的前面开始有两个指针。比较这两个元素,如果它们相等则存储副本,否则将指针移动到较小的值一个点并重复直到你到达其中一个数组的末尾。 O(M + N)时间,O(1)空间

无论如何, 必须 检查两个数组中的每个元素,否则您将不知道是否找到了所有重复项。你可以争论边缘情况,其中一个数组更大或更小,但这不适用于你考虑所有输入范围的算法。

答案 2 :(得分:0)

你可以使用hash_table来保存大数组,然后扫描另一个小数组来计算两个数组的交集。

import random

def comm_seq(arr_1, arr_2):
    if len(arr_1) < len(arr_2): arr_1, arr_2 = arr_2, arr_1
    cnt = {}
    for item in arr_1: 
        cnt.setdefault(item, 0)
        cnt[item] += 1
    # save the large array in a hash_table
    ret = []
    for item in arr_2:
        p = cnt.get(item, 0)
        if p: 
            ret.append(item):
            cnt[item] -= 1
    # scan the small array and get the answer
    return ret

if __name__ == "__main__":
    arr_1 = [random.randrange(0,5) for _ in xrange(10)]
    arr_2 = [random.randrange(0,5) for _ in xrange(10)]
    arr_1.sort()
    arr_2.sort()
    print comm_seq(arr_1, arr_2)

如果我们将py字典的复杂性视为O(1),则总复杂度为O(min(n,m))

答案 3 :(得分:0)

如果使用单边和普通二分搜索的组合,则可以使用O(N * log(M / N))比较的算法。在最坏的情况下(当两个阵列具有相同的大小时),这等于O(N)= O(M + N)个比较。这里M是最大数组的大小,N是较小数组中不同元素的数量。

获取两个数组中最小的数组并搜索第二个数组中的每个元素。从单边二分搜索开始:尝试位置M / N,2 * M / N,4 * M / N,......直到找到大于必要的元素。然后使用常规二分搜索在位置0和2 k * M / N之间找到一个元素。

如果找到匹配元素,则使用单面和普通二进制搜索的相同组合来查找重复匹配元素的运行结束位置,并将适当数量的匹配元素复制到输出。您可以使用相同的二进制搜索组合来计算较小数组中重复元素的数量,并获得这些重复计数的最小值,以确定结果中应包含多少元素。

要继续使用较小数组中的下一个元素,请使用较大数组中的起始位置,上一步结束。