【问题标题】:Longest repeated substring in massive string海量字符串中最长的重复子串
【发布时间】:2020-11-27 09:06:14
【问题描述】:

给定一个长字符串,找出最长的重复子字符串。

蛮力方法当然是查找所有子字符串并检查剩余字符串的子字符串,但有问题的字符串有数百万个字符(如 DNA 序列、AGGCTAGCT 等),我想在宇宙自行坍塌之前完成的事情。

尝试了多种方法,我有一个解决方案可以在高达数百万的字符串上运行得非常快,但对于较大的字符串实际上需要永远(6 小时以上),特别是当重复序列的长度变得非常长时.

def find_lrs(text, cntr=2):
    sol = (0, 0, 0)
    del_list = ['01','01','01']
    
    while len(del_list) != 0:
        d = defaultdict(list)
        
        for i in range(len(text)):
            d[text[i:i + cntr]].append(i)
        
        del_list = [(item, d[item]) for item in d if len(d[item]) > 1]

        # if list is empty, we're done
        if len(del_list) == 0:
            return sol
        else:
            sol = (del_list[0][1][0], (del_list[0][1][1]),len(del_list[0][0]))
        cntr += 1

    return sol

我知道这很难看,但是嘿,我是一个初学者,我很高兴我有一些工作要做。想法是遍历以长度为 2 的子字符串作为键开始的字符串,子字符串的索引位于该值处。如果文本是“BANANA”,那么在第一次通过后,dict 将如下所示:

{'BA': [0], 'AN': [1, 3], 'NA': [2, 4], 'A': [5]}

BA 只出现一次 - 从索引 0 开始。AN 和 NA 出现两次,分别出现在索引 1/3 和 2/4。

然后我创建一个列表,其中仅包含至少出现两次的键。在上面的示例中,我们可以删除 BA,因为它只出现一次 - 如果没有以 'BA' 开头的长度为 2 的子字符串,则不会有以 BA 开头的长度为 3 的子字符串。 所以经过剪枝后的第一个过去是: [('AN', [1, 3]), ('NA', [2, 4])]

由于至少有两种可能性,我们保存迄今为止找到的最长子字符串和索引,并将子字符串长度增加到 3。我们继续直到没有重复子字符串。

如上所述,这可以在大约 2 分钟内处理多达 1000 万个字符串,这显然是合理的 - 但是,最长的重复序列相当短。在较短的字符串但较长的重复序列上,运行需要-小时-。我怀疑这与字典的大小有关,但不太清楚为什么。

我当然想做的是通过删除明显不重复的子字符串来保持字典的简短,但是在迭代它时我不能从字典中删除项目。我知道有后缀树方法,而且现在 - 不在我的范围内。

可能只是这超出了我目前的知识范围,这当然很好,但我不禁动摇了这里有解决方案的想法。

【问题讨论】:

  • 子串能否重叠自身,例如:'ANA'在'BANANA'中有两次出现
  • 这似乎是计算机科学中一个经过充分研究的问题:en.wikipedia.org/wiki/Longest_repeated_substring_problem
  • 感觉可以通过跟踪子字符串出现的位置来减少搜索空间。如果你在 3 个地方找到一个长度为 N 的重复子串,你只需要检查这 3 个地方的 N+1 个长度的字符串,对吧?在我看来,它们不可能在其他任何地方发生。还是我弄错了?
  • 是的,子字符串可以重叠。 BANANA中最长的子串是ANA,长度为3,从[1]和[3]开始。
  • 这能回答你的问题吗? Find longest repetitive sequence in a string

标签: python longest-substring


【解决方案1】:

我忘记更新了。再次查看我的代码后,远离我的 PC - 在我的 iPad 上写下小图表 - 我意识到上面的代码并没有按照我的想法做。

如上所述,我的攻击计划是从以长度为 2 的子字符串作为键开始的字符串开始,子字符串的索引位于该值处,创建一个仅捕获长度为 2 的列表至少出现两次的子字符串,并且只查看那些位置。

一切都很好 - 但仔细观察,您会发现我实际上从未将默认字典更新为只有两个或多个重复的位置! //用头撞桌子。

我最终想出了两个解决方案。第一个解决方案使用了一种稍微不同的方法,即“排序后缀”方法。这将获取单词的所有后缀,然后按字母顺序对它们进行排序。例如,“香蕉”的后缀,排序后,将是: 一种 全日空 ANANA 香蕉 不适用 娜娜

然后我们查看每个相邻的后缀,并找出每对有多少个字母开始有共同之处。 A 和 ANA 只有“A”的共同点。 ANA 和 ANANA 有共同的“ANA”,所以我们有长度 3 作为最长的重复子串。 ANANA 和 BANANA 在开始时没有任何共同点,BANANA 和 NA 也是如此。 NA 和 NANA 有共同的“NA”。所以'ANA',长度为3,是最长的重复子串。

我做了一个小辅助函数来进行实际比较。代码如下所示:

def longest_prefix(suf1, suf2, mx=None):
    min_len = min(len(suf1), len(suf2))
    for i in range(min_len):
        if suf1[i] != suf2[i]:
            return (suf1[0:i], len(suf1[0:i]))
    return (suf1[0:i], len(suf1[0:i]))


def longest_repeat(txt):
    lst = sorted([text[i:] for i in range(len(text))])
    print(lst)
    mxLen = 0
    mx_string = ""
    for x in range(len(lst) - 1):
        temp = longest_prefix(lst[x], lst[x + 1])
        if temp[1] > mxLen:
            mxLen = temp[1]
            mx_string = temp[0]
    first = txt.find(mx_string)
    last = txt.rfind(mx_string)
    return (first, last, mxLen)

这行得通。然后我回去重新查看我的原始代码,发现我没有重置字典。关键是每次通过后我都会更新字典以 - 只 - 查看重复的候选者。

def longest_repeat(text):
    # create the initial dictionary with all length-2 repeats
    
     cntr = 2 # size of initial substring length we look for
     d = defaultdict(list)
     for i in range(len(text)):
         d[text[i:i + cntr]].append(i)
    
     # find any item in dict that wasn't repeated at least once
     del_list = [(d[item]) for item in d if len(d[item]) > 1]
     sol = (0,0,0)
    
     # Keep looking as long as del_list isn't empty,
     while len(del_list) > 0:
         d = defaultdict(list) # reset dictionary
         cntr += 1 # increment search length
         for item in del_list:
             for i in item:
                 d[text[i:i + cntr]].append(i)
     # filter as above
     del_list = [(d[item]) for item in d if len(d[item]) > 1]
    
     # if not empty, update solution
     if len(del_list) != 0:
         sol = (del_list[0][0], del_list[0][1], cntr)
     return sol

这相当快,而且我认为它更容易理解。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-09-11
    • 2022-08-14
    • 1970-01-01
    • 1970-01-01
    • 2020-04-27
    • 2013-02-21
    • 2021-08-02
    相关资源
    最近更新 更多