检查字符串是不是包含模式(关于一对一的符号映射)

Posted

技术标签:

【中文标题】检查字符串是不是包含模式(关于一对一的符号映射)【英文标题】:Check if string contains pattern (with respect to one-to-one symbols mapping)检查字符串是否包含模式(关于一对一的符号映射) 【发布时间】:2021-12-17 06:01:17 【问题描述】:

我正在尝试解决以下问题:

检查,如果 string 包含子字符串,可以从 pattern 使用一对一的小写符号替换(使用原始小写字母和新字母之间的双射)。

我的字符串仅由大写和小写英文字母组成(A-Z、a-z

例如:

正面: 字符串:justanexampleXstring 模式:onecompleX 答案:true(anexampleX onecompleX,使用的映射:ao, xc;大写不受映射影响)否定: 字符串:AaBbDd 模式:AaBa 答案:false(模式中有两个重复的小写字母,原始字符串中没有重复的小写字母)

目前我正在考虑在multiple pattern search 设置中使用Rabin–Karp 算法:

一种简单的搜索 k 模式的方法是重复一次单模式搜索,花费 O(n+m) 时间,总计 O((n+m)k) 时间。相比之下,上述算法可以在 O(n+km) 的预期时间内找到所有 k 个模式,假设哈希表检查在 O(1) 的预期时间内工作。

但是对所有可能的映射生成的字符串进行简单检查会导致 k=26!阶乘,26 个字母之间所有可能的一对一映射的数量)

对当前方法的任何想法或优化将不胜感激。 提前谢谢!

【问题讨论】:

通常,字符串和模式有多大? @RBarryYoung 假设字符串长度可以达到 10^6 并且模式通常会短得多;但如果可以覆盖模式长度也高达 10**6 的情况,那就太好了。 是否有进一步的限制或模式ab匹配字符串cd @500-InternalServerError 没有进一步的限制;例如abcd 是正确的 @500-InternalServerError 添加了有问题的反例 【参考方案1】:

您可以在相同的预期运行时间内对 Rabin-Karp 稍作改动(假设您的哈希函数满足通常的一致性要求)。这个想法是考虑什么样的字符串相当于小写字母双射映射下的模式。首先,所有大写字母必须完全匹配,所以我们暂时可以忽略它们。对于小写字母,重要的是基于相等字符的索引分区。

例如,如果pattern = 'banana',其长度为m=6,则索引的分组将是3组,一组用于'b'索引,一组用于'a',一组用于'n'。 那么(无序的)分区是([0], [1, 3, 5], [2, 4])。现在,让我们找到一个等效的字符串表示。而不是这个分区,将模式的每个小写字符替换为到下一个相等字符的距离(即,其分区组中的下一个较大值),或者m,如果这是字符的最后的样子。这意味着我们替换的“香蕉”字符串变为(6, 2, 2, 2, 6, 6);另一个长度为m的小写字符串当且仅当它与等值索引分组下的模式匹配时才具有相同的“替换”字符串,当且仅当它们在字母双射下匹配时才匹配模式。

我们可以使用 Rabin-Karp 的长度为 m 的滚动哈希,替换上面的小写字母,并将大写字母映射到“m”以上的另一个数字范围(例如,m+1 到 m+26)。唯一的问题是当我们的窗口向右滑动时,窗口中间的一个字符可能会改变值: 给定我们替换的字符串'banana'(6, 2, 2, 2, 6, 6),如果我们添加到字符串末尾的下一个字符是'a',我们的替换字符串'ananaa'是(2, 2, 2, 6, 1, 6)。我们可以通过在处理字符串时为每个小写字母实现“最后一次看到”索引的哈希图来解决这个问题。使用sum of coefficients of prime powers滚动哈希,我们可以快速计算出需要的变化。

如果您想知道更新滚动哈希的确切数学: 在从 Rabin-Karp 执行通常的窗口移位更新之后,如果添加的字符是小写的并且在我们的窗口中较早出现 j 字符,则其字符值从“m”变为“j”。对于素数p,它对“滚动哈希模和”的贡献从m*(p^j) 变为j*(p^j),我们可以减去差值。您可以轻松计算任何窗口字符当前对散列的贡献的任何滚动散列也将起作用。

一个更有趣的问题是 Knuth-Morris-Pratt 或 Boyer-Moore 是否可以适应以类似方式工作;我还没有找到类似的转换,因为我的算法依赖于滚动哈希。尽管如此,Rabin-Karp 的平均运行时间和最坏情况运行时间将保持不变。

【讨论】:

【参考方案2】:

我不知道 Rabin-Karp,但这比 k=26 好!你提到的。我正在将onecompleX 之类的模式转换为(.)(.)(.)(.)\1(.)(.)(.)\3X 之类的正则表达式:

import re

def solve(string, pattern):
    def replace(match, group=):
        letter = match[0]
        if letter not in group:
            group[letter] = len(group) + 1
            return '(.)'
        return f'\\group[letter]'
    regex = re.sub('[a-z]', replace, pattern)
    return bool(re.search(regex, string))

print(solve('justanexampleXstring', 'onecompleX'))
print(solve('AaBbDd', 'AaBa'))

输出(Try it online!):

True
False

【讨论】:

以上是关于检查字符串是不是包含模式(关于一对一的符号映射)的主要内容,如果未能解决你的问题,请参考以下文章

GORM:使用字符串作为键的一对一表映射

检查字符串是不是包含模式,忽略位置。并返回项目匹配的索引

检查文本区域是不是包含符号并删除符号后的所有内容

什么是签名字符? [复制]

检查字符串是不是“平衡”的递归函数

检查 Postgres JSON 数组是不是包含字符串