Python 有效地查找字符串是否包含一组字符(如子字符串但忽略顺序)?
在Python中,查找字符串中是否存在一组排列成字符串的字符的最有效方法是什么 例如,如果我有Python 有效地查找字符串是否包含一组字符(如子字符串但忽略顺序)?,python,string,algorithm,Python,String,Algorithm,在Python中,查找字符串中是否存在一组排列成字符串的字符的最有效方法是什么 例如,如果我有string=“hello world”和子字符串“roll”,函数将返回true,因为“roll”中的所有4个字母都存在于“hello world”中 有一种显而易见的暴力方法,但我想知道是否有一种高效的特定于Python的方法来实现这一点 编辑:字母计数很重要。因此,例如rolll不包括在hello world中(只有三个l)。建立每个字符串中字符的直方图,然后您可以验证子字符串中的每个字母是否出现
string=“hello world”
和子字符串“roll”
,函数将返回true,因为“roll”
中的所有4个字母都存在于“hello world”
中
有一种显而易见的暴力方法,但我想知道是否有一种高效的特定于Python的方法来实现这一点
编辑:字母计数很重要。因此,例如
rolll
不包括在hello world
中(只有三个l)。建立每个字符串中字符的直方图,然后您可以验证子字符串中的每个字母是否出现在较大的字符串中。运行时间是线性的(O(n+m)
),空间与字母表的大小成正比
这是一种形式
请注意,
collections.Counter
是一个直方图数据结构,因此算法大致相同。由于计数器
使用哈希表,因此它的空间复杂度与实际遇到的项目(字母)数量成比例,但常数因子高于鸽子洞方法,因此计数器
的效率略低,但不太可能是这样的。我想到的第一件事是,您可以轻松地计算原始字符串的单个字符数,然后计算每个子字符串的字符数。然后交叉检查原始字符串的字符数,查看创建子字符串所需的每种类型的字符数是否存在。这是一个非常简单的方法来确定给定字符串是否是另一个字符串的字谜。这同样适用于您的场景。您可以使用集合。计数器
:
from collections import Counter
substring_counts = Counter(substring)
text_counts = Counter(text)
if all(text_counts[letter] >= count for letter, count in substring_counts.items()):
# All the letters in `substring` are in `count`
对于“包含”检查,我通常会选择集合:
set(string).issuperset(set(substring))
# or
set(string) >= set(substring)
我不确定这里的复杂性,但是说集合构造和超集检查都是O(n),所以这将是O(n+m),与
相等。 正如Kasramvd所指出的,在使用issuperset
时,不需要创建一组子字符串:
set(string).issuperset(substring)
不过,使用
=
仍然需要转换。使用哈希的概念:
在python中,使用dict()
对于字符串中的字符,我们制作了一个hashmap,该hashmap记录了可用的不同字符及其各自的计数
接下来,我们迭代子字符串,看看是否所有字符都可用。。如果可用,我们将减少hashmap中该字符的计数并向前移动。如果不存在,则只需将中断
输出并打印错误
。。。。。这么简单
希望对你有帮助 我认为最好在子字符串上循环,而不是字符串本身。作为一种更具python风格的方法,您可以通过对主字符串中子字符串字母的数量求和,在sum
中使用生成器表达式。然后将其与子字符串的长度进行比较:
sum(i in s for i in sub) == len(sub)
如您所见,当字符串变大时,它比计数器
和设置
方法快得多:
In [45]: s = "hello world"
In [46]: sub = "roll"
In [47]: s *= 1000
In [48]: %timeit set(s).issuperset(sub)
10000 loops, best of 3: 136 µs per loop
In [53]: %timeit substring_counts = Counter(sub); text_counts = Counter(s); all(text_counts[letter] >= count for letter, count in substring_counts.items())
100 loops, best of 3: 2.21 ms per loop
In [49]: %timeit sum(i in s for i in sub) == len(sub)
1000000 loops, best of 3: 739 ns per loop
如果且仅当sub
中的每个字符都存在于s
中,则可以使用类似的内容,即True
,如OP所述:
all(sub.count(i) <= s.count(i) for i in set(sub))
all(子计数(i)如果您想提高效率,可以构造一个计数器
,对字符串
中的所有字母进行计数,并对子字符串
中的每个字母递减这些计数。如果任何计数低于0,则字符串
中的字符实例不足,无法创建子字符串
。这是一个O(字符串
+子字符串
)算法
from collections import Counter
def unordered_substring(string, substring):
string_counter = Counter(string)
for char in substring:
string_counter[char] -= 1
if string_counter[char] < 0:
return False
return True
所以最后它还是计数器
毕竟:P:)是明显的暴力方法设置(字符串)。issuperset(设置(“滚动”)
?这是为了规范吗?@muru将其作为一个答案发布,即使是。比计数器
好得多。你关心字母的数量吗?例如,应该thefunc(“你好世界”,“rolllllll”)
是真是假?“hello world”
包含三个l
,因此“rolll”
应包含在“hello world”中“
@Abhijit修复了此问题,感谢您无需将子字符串转换为集
。@muru集不限于唯一元素?请参阅我的说明:编辑:字母计数很重要。例如,hello world中不包含Roll(只有两个l)。@Roeedler我理解您的观点,但您的示例有一个问题:“你好世界”有三个l;“世界”"还有一个。@Roeedler如果字母计数很重要,collections.Counter
是一个不错的选择。Daniel Pryden answer工作得很好,如果在同一个字符串上运行多个查询,效果尤其好。但是我认为在大多数情况下,如果不进行计数,只需检查一个布尔值,并保持所输入的字母数,答案会更快如果你遇到了所有的字母,你就会断掉。事实上,O(n+m)不是很好,如果它实际上需要与n成比例的时间,尽管匹配是在一个小得多的初始序列中找到的。应该有办法解决这个问题,使它成为O(n'+m)其中n'是产生结果所需的最小前缀的长度。@hassanarafat:也许我误解了,但是你仍然需要建立针的直方图,以便有效地在草堆中搜索,对吗?如果你有针的直方图,那么你可以做O(n)穿过干草堆,一路上减少直方图中的字符数。如果直方图中的字符数为零,则表示成功;如果干草堆中剩余的字符数少于直方图中剩余的字符总数,则表示失败。我的建议只是对
from collections import Counter
def unordered_substring(string, substring):
string_counter = Counter(string)
for char in substring:
string_counter[char] -= 1
if string_counter[char] < 0:
return False
return True
def unordered_substring_long(string, substring):
substring_counter = Counter(substring)
total_count = sum(substring_counter.values())
for char in string:
if substring_counter[char] > 0:
substring_counter[char] -= 1
total_count -= 1
if total_count == 0:
return True
return False