【问题标题】:Efficient algorithm to match two arrays with same values but different order匹配具有相同值但不同顺序的两个数组的有效算法
【发布时间】:2021-05-23 03:18:34
【问题描述】:

我们知道大小为n 的两个数组AB 具有相同的一组不同的整数值,但可能以不同的方式排序,因此两个数组之间存在一对一的匹配。 我们不知道数组中存储的实际值,因此无法访问数组的值,因此无法使用 sorted 等函数。但是我们可能会询问任何一对A[i]B[j] 的问题,并得到A[i] = B[j]A[i] > B[j]A[i] < B[j] 的答案。

将每个元素AB 中的对应元素匹配的有效算法是什么?我首先想到的是一种简单的蛮力方法,首先将A[1]B 中的对应项匹配,方法是重复询问A[1]B[j] 之间的关系问题j = 1n,直到我们找到匹配。然后对A[i]i = 2n 执行相同的操作,以找到其余A[i]s 的匹配项。最坏情况下的运行时间是n + (n - 1) + ... + 1 = O(n^2)。但这似乎不是最有效的方法,因为我们只使用了是否A[i] = B[j]的信息,而没有使用A[i] > B[j]A[i] < B[j]的更详细信息。直觉上应该有一种方法可以利用A[i]B[j] 之间的相对顺序,在不到O(n^2) 的时间内设计算法。

任何帮助将不胜感激!

【问题讨论】:

    标签: arrays algorithm matching


    【解决方案1】:

    这是Matt's algorithm 的 Python 实现。

    • AB 创建和隐藏AB,只提供比较A[i]B[j] 并检查索引列表IJ 是否完美匹配。
    • quickmatch是解算法

    演示输出显示计算出的IJ 以及它们是否完美匹配:

    [16, 0, 19, 7, 10, 15, 12, 14, 5, 4, 11, 18, 1, 9, 2, 6, 13, 8, 3, 17]
    [1, 3, 13, 12, 0, 18, 9, 4, 14, 17, 11, 6, 5, 7, 15, 2, 8, 10, 16, 19]
    True
    

    代码:

    from random import shuffle
    
    class AB:
        def __init__(self, n):
            A = list(range(n))
            B = list(range(n))
            shuffle(A)
            shuffle(B)
            def cmp(i, j):
                a = A[i]
                b = B[j]
                return -1 if a < b else 1 if a > b else 0
            def check(I, J):
                if not (sorted(I) == sorted(J) == list(range(n))):
                    return False
                return all(A[i] == B[j] for i, j in zip(I, J))
            self.cmp = cmp
            self.check = check
    
    def quickmatch(I, J):
        if not I:
            return I, J
    
        ipivot = I[0]
        jpivot = next(j for j in J if ab.cmp(ipivot, j) == 0)
    
        small_I = [i for i in I if ab.cmp(i, jpivot) < 0]
        small_J = [j for j in J if ab.cmp(ipivot, j) > 0]
        small_I, small_J = quickmatch(small_I, small_J)
    
        large_I = [i for i in I if ab.cmp(i, jpivot) > 0]
        large_J = [j for j in J if ab.cmp(ipivot, j) < 0]
        large_I, large_J = quickmatch(large_I, large_J)
    
        return (small_I + [ipivot] + large_I,
                small_J + [jpivot] + large_J)
    
    # Create a test case
    n = 20
    ab = AB(n)
    
    # Solve
    I = list(range(n))
    J = list(range(n))
    I, J = quickmatch(I, J)
    
    # Check
    print(I)
    print(J)
    print(ab.check(I, J))
    

    【讨论】:

    • 感谢您通过创建显式代码和测试函数来实现 Matt 的算法。很高兴您在此过程中玩得开心!
    【解决方案2】:

    由于您无法比较同一数组中的元素,因此您无法单独对它们进行排序,但您可以进行一种相互快速排序,仍然在预期的 O(N log N) 时间内:

    1. 选择 A 的一个元素作为 A 轴
    2. 根据 B 的元素与 A 轴的比较情况对它们进行分区。其中之一将比较相等。这就是 B 轴
    3. 根据 A 的元素与 B 轴的比较方式对它们进行分区。如果存在 1-1 匹配,则分区将与 B 的分区大小相同。
    4. 在较低的 A 和 B 分区以及较高的 A 和 B 分区上递归。

    【讨论】:

    • 玩得很开心implementing这个。
    • 嗨@Matt,非常感谢你的回答,这正是我想要的,你解释得很清楚!
    【解决方案3】:

    OP 的问题不是很清楚。首先他说这些条目是整数,然后他说他只能将它们 2 2 进行比较,这表明他不能散列它们。如果无法进行散列,这是一个解决方案:

    我将对两个数组进行排序,保持元素沿元素的位置。然后,您可以检查排序值是否相等,并根据位置找到匹配项。它是 O(n log(n))

    val1 = ["1", "5", "3", "6"]
    val2 = ["3", "6", "1", "5"]
    
    //  Key = ... means to compare i and j, compare val1[i] with val2[j]
    val1s = sorted(range(len(val1)), key = lambda i: val1[i])
    // The result is
    val1s
    [0, 2, 1, 3]
    // which is effectively the order in which val1 must be read to have it sorted...
    
    val2s = sorted(range(len(val2)), key = lambda i: val2[i])
    
    perm = [None]*len(val1)
    for i in range(len(val1)):
        perm[val1s[i]] = val2s[i]
        
    perm
    
    [2, 3, 0, 1]
    

    注意:代码逐字处理任何具有可比性的内容(例如:字符串)。

    编辑:我正在回答评论“您无法对数组进行排序,因为您不知道数组元素”。这在某种意义上是错误的。我没有对数组进行排序(我不允许更改它),而是返回一个列表,该列表给出了读取要排序的数组的正确顺序。完全有可能在不访问元素的情况下获得它。查看python代码。

    【讨论】:

    • 感谢回复,可能是我表述的不够清楚。我们没有任何元素的数值,因此没有可用的排序。我们只能成对比较它们,并得到它们的相对顺序。
    • 对不起,但这对我来说没有意义。您可以对任何可比较的东西进行排序!不仅是数字,还有例如字符串、扑克牌……
    • 好吧,也许我应该这样说,我们无法访问数组的实际值。我们唯一能得到的信息是任何对 A[i] 和 B[j] 的相对顺序,然后我们仅根据该信息匹配 2 个数组。
    • @hivert 您无法对数组进行排序,因为您不知道数组元素
    • @hivert 你没有得到val1val2
    【解决方案4】:

    这是我对这个问题的尝试:

    from collections import defaultdict
    a = [1,2,4,5,8]
    b = [8,5,4,1,2]
    d = defaultdict(list)   #(element,index) map
    
    for i,v in enumerate(a):
        d[v]=[i]
    for j,x in enumerate(b):
        d[x].append(j)
    print(d)
    

    非常简单。我正在为一个数组创建一个索引映射的元素,并使用它来匹配第二个数组的键。
    时间复杂度:O(n) 空间复杂度:O(n)
    试图找到一个 O(1) 空间复杂度解决方案,这可以直观地使用分而治之的方法来完成,利用我们可以要求每个元素的指定查询。很快就会更新。

    【讨论】:

    • 感谢您的回复。 Hivert 是对的,也许我说得不够清楚。我们没有任何元素的数值,因此无法进行排序。我们只能成对比较它们,并得到它们的相对顺序。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-21
    相关资源
    最近更新 更多