Python 检查序列是否包含非连续子序列的最快方法?
假设给出了两个元素列表,Python 检查序列是否包含非连续子序列的最快方法?,python,python-3.x,algorithm,time-complexity,Python,Python 3.x,Algorithm,Time Complexity,假设给出了两个元素列表,A和B。我想检查A是否包含B的所有元素。具体来说,元素必须以相同的顺序出现,并且它们不需要是连续的。如果是这种情况,我们说B是a的子序列 以下是一些例子: A = [4, 2, 8, 2, 7, 0, 1, 5, 3] B = [2, 2, 1, 3] is_subsequence(A, B) # True 我找到了一个非常优雅的方法来解决这个问题(请参阅): 我现在想知道这个解决方案在输入量可能非常大的情况下是如何工作的。假设我的列表包含数十亿个数字 上面代码的复杂
A
和B
。我想检查A
是否包含B
的所有元素。具体来说,元素必须以相同的顺序出现,并且它们不需要是连续的。如果是这种情况,我们说B
是a
的子序列
以下是一些例子:
A = [4, 2, 8, 2, 7, 0, 1, 5, 3]
B = [2, 2, 1, 3]
is_subsequence(A, B) # True
我找到了一个非常优雅的方法来解决这个问题(请参阅):
我现在想知道这个解决方案在输入量可能非常大的情况下是如何工作的。假设我的列表包含数十亿个数字
- 上面代码的复杂性是什么?最坏的情况是什么?我试着用非常大的随机输入测试它,但它的速度主要取决于自动生成的输入
- 最重要的是,是否有更有效的解决方案?为什么这些解决方案比这个更有效
A
创建迭代器;您可以将其视为指向a
中要查看的下一个位置的简单指针,而中的会在a
上向前移动指针,直到找到匹配项。它可以多次使用,但只能向前移动;在
中多次对单个迭代器使用包含测试时,迭代器不能后退,因此只能测试“仍要访问”值是否等于左侧操作数
在上一个示例中,使用B=[2,7,2]
,会发生以下情况:
it=iter(A)
为A
列表创建迭代器对象,并将0
存储为下一个要查看的位置
all()
函数测试iterable中的每个元素,如果发现这样的结果,则尽早返回False
。否则它会继续测试每个元素。在这里,测试在it
调用中重复x,其中x
依次设置为B
中的每个值
x
首先设置为2
,因此测试其中的2
。
it
设置为下次查看A[0]
。这是4,不等于2
,因此内部位置计数器增加到1
A[1]
是2
,这是相等的,因此其中的2
此时返回True
,但不是在增加2
的“下一个要查看的位置”计数器之前
其中的2
为真,因此all()
继续
B
中的下一个值是7
,因此测试其中的7
。
it
设置为下次查看A[2]
。这是8
,而不是7
。位置计数器增加到3
it
设置为下次查看A[3]
。这是2
,而不是7
。位置计数器增加到4
it
设置为下次查看A[4]
。这是7
,等于7
。位置计数器增加到5
,并返回True
其中的7
为真,因此all()
继续
B
中的下一个值是2
,因此测试其中的2
。
it
设置为下次查看A[5]
。这是0
,而不是2
。位置计数器增加到6
it
设置为下次查看A[6]
。这是1
,而不是2
。位置计数器增加到7
it
设置为下次查看A[7]
。这是5
,而不是2
。位置计数器增加到8
it
设置为下次查看A[8]
。这是3
,而不是2
。位置计数器增加到9
- 没有
A[9]
,因为A
中没有那么多元素,因此返回False
其中的2
是False
,因此all()
通过返回False
结束
您可以使用迭代器验证这一点,并观察其副作用;在这里,我使用print()
写出给定输入的下一个值:
>>> A = [4, 2, 8, 2, 7, 0, 1, 5, 3]
>>> B = [2, 7, 2]
>>> with_sideeffect = lambda name, iterable: (
print(f"{name}[{idx}] = {value}") or value
for idx, value in enumerate(iterable)
)
>>> is_sublist(with_sideeffect(" > A", A), with_sideeffect("< B", B))
< B[0] = 2
> A[0] = 4
> A[1] = 2
< B[1] = 7
> A[2] = 8
> A[3] = 2
> A[4] = 7
< B[2] = 2
> A[5] = 0
> A[6] = 1
> A[7] = 5
> A[8] = 3
False
A=[4,2,8,2,7,0,1,5,3]
>>>B=[2,7,2]
>>>带_sideeffect=lambda名称,iterable:(
打印(f“{name}[{idx}]={value}”)或值
对于idx,枚举中的值(iterable)
)
>>>是子列表(有副作用(“>A”,A),有副作用(A[0]=4
>A[1]=2
A[2]=8
>A[3]=2
>A[4]=7
A[5]=0
>A[6]=1
>A[7]=5
>A[8]=3
假的
您的问题需要连续测试B
的每个元素,这里没有捷径。您还必须扫描A
,以测试B
的元素是否按正确的顺序存在。只有在找到B
的所有元素(部分扫描)时,才能宣布胜利;在扫描A
中的所有元素且未找到要测试的B
中的当前值时,才能宣布失败
因此,假设B的大小总是小于A,那么最好的情况是B
中的所有K个元素都等于A
的前K个元素。最坏的情况是B
的所有元素都不在A
中,并且需要通过A
进行完全扫描。无论B
中存在多少个元素都无关紧要;如果您正在测试K中的元素K,您已经通过A
部分扫描了,必须通过A
完成扫描才能找到
A = [4, 2, 8, 2, 7, 0, 1, 5, 3]
B = [2, 1, 6]
is_subsequence(A, B) # False
A = [4, 2, 8, 2, 7, 0, 1, 5, 3]
B = [2, 7, 2]
is_subsequence(A, B) # False
def is_subsequence(A, B):
it = iter(A)
return all(x in it for x in B)
>>> A = [4, 2, 8, 2, 7, 0, 1, 5, 3]
>>> B = [2, 7, 2]
>>> with_sideeffect = lambda name, iterable: (
print(f"{name}[{idx}] = {value}") or value
for idx, value in enumerate(iterable)
)
>>> is_sublist(with_sideeffect(" > A", A), with_sideeffect("< B", B))
< B[0] = 2
> A[0] = 4
> A[1] = 2
< B[1] = 7
> A[2] = 8
> A[3] = 2
> A[4] = 7
< B[2] = 2
> A[5] = 0
> A[6] = 1
> A[7] = 5
> A[8] = 3
False