查找具有给定排名的所有固定长度子数组

Posted

技术标签:

【中文标题】查找具有给定排名的所有固定长度子数组【英文标题】:Find all subarrays of fixed length with a given ranking 【发布时间】:2019-05-31 22:28:15 【问题描述】:

我有一个数字数组,例如:

A = [1, 5, 2, 4, 3]

还有一个决定排名的数组,例如:

B = [0, 2, 1]

我的目标是找到 A 的所有“服从”等级 B 的子数组。如果子数组服从等级,这意味着子数组的第 i 个最小元素必须以 B[i] 作为其(子数组)指数。所以要匹配一个子数组,其中最小的元素必须在位置 0,第二小的元素必须在位置 2,最大的元素必须在位置 1。

所以比如这里,有两个匹配排序的A的子数组:[1,5,2](因为A[0]

到目前为止,我已经设法找到了一个具有O(mn)(m 是 len(A),n 是 len(B))时间复杂度的解决方案,它遍历所有长度为 3 的子数组并验证是否它们的顺序正确:

A = [1, 5, 2, 4, 3]
B = [0, 2, 1]
m = len(A)
n = len(B)
for i in range(m - n + 1):
    current_subarray = A[i:i + n]
    # we now do n - 1 comparaisons to check whether the subarray is correctly ordered.
    for B_index in range(n - 1):
        if current_subarray[B[B_index]] > current_subarray[B[B_index + 1]]:
            break
    else:
        print("Subarray found:", current_subarray)

结果:

Subarray found: [1, 5, 2]
Subarray found: [2, 4, 3]

这可行,但我想知道是否有更好的优化算法(比O(mn) 更好)来完成这项任务。

【问题讨论】:

您是否正在寻找时间复杂度较低的东西?因为我不确定这样的事情是否存在。 @ParitoshSingh 是的,这就是我要找的。也许不是,但我想这就是我问的原因:)。让我怀疑的是我正在处理子数组,其中一些子数组重叠 - 也许有一种方法可以通过缓存一些来减少计算量,比如优化字符串搜索(如 KMP)算法的工作原理?跨度> 我看到的问题是这个。考虑 [0,1,3,2]。在第一个子列表中,[1,3] 的相对等级为 1 和 2,而在第二个子列表中,[1,3] 的相对等级为 0 和 2。本质上,结果取决于“窗口” ,因此您需要重新评估才能确定。在这种情况下,您可能缓存的任何结果最终都需要重新检查,从而失去所有好处。 (如果我错了,请有人纠正我) @ParitoshSingh 正确,但是考虑长度 > 2 的子数组。例如,当我从 [0, 1, 3] 转到 [1, 3, 2] 时(在您的列表中) ,假设我对第一个子数组进行了比较并推断它不服从。我继续第二个子数组,但是我可能已经做了一些比较(最后两个元素成为第二个子数组的前两个元素)。即使,正如您所说,知道 1 确实如此,但是因为它的“一些”案例而不是全部案例,所以无论如何您都必须重新检查所有案例。而且由于比较是一个恒定的时间操作,因此您最终会出现在第 1 格。更改窗口会改变有关比较有利和不利的所有内容。 【参考方案1】:

您可以使用scipy.stats.rankdata 直接获取排名,而不是遍历 B 来比较排名:

from scipy.stats import rankdata

A = [1, 5, 2, 4, 3]
B = [0, 2, 1]

m = len(A)
n = len(B)

for i in range(m - n + 1):
    current_subarray = A[i:i + n]

    ranked_numbers = (rankdata(current_subarray).astype(int) - 1).tolist()
    if ranked_numbers == B:
        print("Subarray found:", current_subarray)

# Subarray found: [1, 5, 2]
# Subarray found: [2, 4, 3]

注意: rankdata() 从 1 而不是 0 开始排名,这就是为什么上面从 numpy 数组中的每个元素中减去 1 的原因。

【讨论】:

谢谢,但是我对使用的算法更感兴趣,并且我查看了 scipy 源 - 如果我错了,请纠正我 - 但看起来他们正在对列表进行排序 -所以最终复杂度并不比 O(mn) 好? @pingguo 是的,它看起来使用了来自source code 的合并排序或快速排序。在那种情况下,上面的可能会更慢,因为它需要为每个排名执行 O(nlogn) 排序。您必须同时确定两者的时间。我认为您无法比现有的解决方案做得更好。【参考方案2】:

这是基于一些线性代数的numpy 解决方案。

首先将B转换为基:

import numpy as np
A = [1, 5, 2, 4, 3]
B = [0, 2, 1]

b = np.eye(len(B))[B]
print(b)
#array([[1, 0, 0],
#       [0, 0, 1],
#       [0, 1, 0]])

现在我们可以遍历A 的每个子数组并将其投影到这个空间中。如果结果向量已排序,则表示子数组遵循排名。

for i in range(0, (len(A) - len(B))+1):
    a = np.array(A[i:i+len(B)])
    if (np.diff(a.dot(b))>0).all():
        print(a)
#[1 5 2]
#[2 4 3]

我不是 numpy 专家,所以可能有办法进一步优化并消除循环。


更新,这里有一个更干净的版本:

def get_ranked_subarrays(A, B):
    m = len(A)
    n = len(B)
    b = np.eye(n)[B]
    a = np.array([A[i:i+n] for i in range(0, m - n+1)])
    return a[(np.diff(a.dot(b))>0).all(1)].tolist()

A = [1, 5, 2, 4, 3]
B = [0, 2, 1]
get_ranked_subarrays(A, B)
#[[1, 5, 2], [2, 4, 3]]

性能结果:

您的解决方案非常适合小型 n,但随着 A 的大小变大,numpy 解决方案的表现会更好:

这是您的代码,我将其转换为返回所需子数组(而不是打印)的函数:

def get_ranked_subarrays_op(A, B):
    m = len(A)
    n = len(B)
    out = []
    for i in range(m - n + 1):
        current_subarray = A[i:i + n]
        # we now do n - 1 comparisons to check whether the subarray is correctly ordered.
        for B_index in range(n - 1):
            if current_subarray[B[B_index]] > current_subarray[B[B_index + 1]]:
                break
        else:
            out.append(current_subarray)
    return out

大随机A的计时结果:

array_size = 1000000
A = np.random.randint(low=0, high=10, size=array_size)
B = [0, 2, 1]

%%timeit
get_ranked_subarrays_op(A, B)
#1 loop, best of 3: 1.57 s per loop

%%timeit
get_ranked_subarrays(A, B)
#1 loop, best of 3: 890 ms per loop

但是,如果m 也变大,由于短路,您的解决方案会稍微好一些(对于较大的m,短路的可能性会变大)。这是我们让m 为 100 的计时结果。

array_size = 1000000
basis_size = 100
A = np.random.randint(low=0, high=10, size=array_size)
B = range(basis_size)
np.random.shuffle(B)

%%timeit
get_ranked_subarrays_op(A, B)
#1 loop, best of 3: 1.9 s per loop

%%timeit
get_ranked_subarrays(A, B)
#1 loop, best of 3: 2.79 s per loop

【讨论】:

感谢您的回答 - 我从来没有想过这样做!然而,虽然这提高了速度,但在我看来它仍然像 O(nm),因为点积是 O(n) :(。是的,最重要的是,由于中断,大 m 会导致更短的时间。【参考方案3】:

您可以遍历A 并检查生成的子数组:

A, B = [1, 5, 2, 4, 3], [0, 2, 1]
def results(a, b):
   _l = len(b)
   for c in range(len(a)-_l+1):
     _r = a[c:c+_l]
     new_r = [_r[i] for i in b]
     if all(new_r[i] < new_r[i+1] for i in range(len(new_r)-1)):
       yield _r

print(list(results(A, B)))

输出:

[[1, 5, 2], [2, 4, 3]]

【讨论】:

你在变量中使用的所有下划线是怎么回事? @coldspeed 纯属个人风格 感谢您的及时答复!但是,也许我的问题不够清楚,我只需要服从排名的子数组 of A。您的解决方案为我提供了所有可能服从排名的子数组,但其中大多数不是 A 的子数组。这不会使此方法更长(因为我必须删除不属于 A 的子数组)? @pingguo 根据计时结果,您似乎真的无法比已有的解决方案更好。 @pault 我添加了新的计时,但是,如果您觉得它们不准确,我会删除它们。【参考方案4】:

至少,我们可以通过考虑相邻元素的(二元)关系来更快地排除候选窗口,这可以允许并行检查。致电less than0greater than1。那么:

A = [1,  5,  2,  4,  3]
A'=   [0,  1,  0,  1]

B'=   [0,  1]
B = [0,  2,  1]

显然,任何候选者都必须匹配关系序列。另请注意,B 中唯一可以允许重叠的部分类型是升序或降序(这意味着如果找到匹配项,我们可以先验地跳过)。

【讨论】:

以上是关于查找具有给定排名的所有固定长度子数组的主要内容,如果未能解决你的问题,请参考以下文章

第82题给定一个数组,求所有偶数长度子数组的元素的和

从具有给定步幅/步长的 numpy 数组中获取子数组

2.打印给定数组中元素和为0的所有子数组

未排序数组中累加和为给定值的最长子数组长度

第k大-二分查找-1139. 第k大的子数组

[程序员代码面试指南]数组和矩阵问题-未排序正数数组中累加和为给定值的最长子数组长度