【问题标题】:Finding all the shortest unique substring which are of same length?找到所有相同长度的最短唯一子串?
【发布时间】:2019-12-05 13:50:40
【问题描述】:

给定一个仅包含四个字母的字符串序列,['a','g','c','t'] 例如:agggcttttaaaatttaatttgggccc

找到字符串序列中所有最短的唯一子串,它们的长度相等(长度应该是所有唯一子串中的最小值)?

例如:aaggcgccttt 答案:['aa', 'ag', 'gg','cg', 'cc','ct'] 解释:长度为2的最短唯一子串

我尝试过使用后缀数组加上最长公共前缀,但我无法完美地得出解决方案。

【问题讨论】:

  • 为什么不是cgct 来自aaggcgccttt
  • 是的。甚至可以添加。对不起,我忘了添加。
  • n是算法的参数吗?
  • 不,n 不是参数,也不是问题中给出的参数。最短的唯一子串的长度可以是任何值。但是对于给定的示例,它是 2,因为没有长度为 1 的唯一子字符串。
  • 好的,到目前为止你尝试了什么?从您的文字中看不太清楚,也许显示一些代码会有所帮助

标签: algorithm suffix-array string-algorithm


【解决方案1】:

我不确定您所说的“最小唯一子字符串”是什么意思,但是看看您的示例,我假设您的意思是“单个字母的最短运行”。如果是这种情况,您只需要遍历字符串一次(逐个字符)并计算您找到的所有最短运行。您应该跟踪到目前为止找到的最小运行的长度(开始时为无穷大)和当前运行的长度。

如果您需要找到确切的运行,您可以将找到的所有最小运行添加到例如遍历字符串时的列表(如果找到较短的运行,则相应地修改该列表)。

编辑: 我对这个问题进行了更多思考,并提出了以下解决方案。

我们找到所有长度为 i 的唯一子字符串(按升序排列)。因此,首先我们考虑所有长度为 1 的子字符串,然后考虑所有长度为 2 的子字符串,依此类推。如果我们找到了,我们就停下来,因为子字符串的长度只能从这一点开始增加。

您必须使用一个列表来跟踪您目前看到的子字符串,并使用一个列表来存储实际的子字符串。当您找到新的子字符串时,您还必须相应地维护它们。

这是我想出的 Java 代码,以备不时之需:

        String str = "aaggcgccttt";
        String curr = "";
        ArrayList<String> uniqueStrings = new ArrayList<String>();
        ArrayList<String> alreadySeen = new ArrayList<String>();

        for (int i = 1; i < str.length(); i++) {
            for (int j = 0; j < str.length() - i + 1; j++) {
                curr = str.substring(j, j + i); 

                if (!alreadySeen.contains(curr)){ //Sub-string hasn't been seen yet
                    uniqueStrings.add(curr);
                    alreadySeen.add(curr);
                }
                else //Repeated sub-string found
                    uniqueStrings.remove(curr);
            }

            if (!uniqueStrings.isEmpty()) //We have found non-repeating sub-string(s)
                break;

            alreadySeen.clear();
        }

        //Output
        if (uniqueStrings.isEmpty())
            System.out.println(str);
        else {
            for (String s : uniqueStrings)
                System.out.println(s);
        }

uniqueStrings 列表包含所有最小长度的唯一子字符串(用于输出)。 alreadySeen 列表跟踪所有已经看到的子字符串(用于排除重复的子字符串)。

【讨论】:

  • 为了清楚起见,我已经更新了问题。我不是在寻找最短的运行,而是寻找可能具有最小长度的唯一子字符串。为了清楚起见,请查看更新的示例。
  • 难道最短的唯一子串不总是只是原始字符串中唯一的单个字母吗?在您更新的示例中,“a”、“c”、“g”和“t”都是原始字符串的有效子字符串,并且比解决方案短。
  • 但它们都在字符串中出现了不止一次。我们正在寻找不重复多次的唯一子字符串。
  • 现在并没有真正给出太多答案。 如何找到唯一的子字符串?
  • 我如何找到不重复的子字符串,与此相反,我真的需要找到所有不重复的子字符串才能找到它们中最短的吗?跨度>
【解决方案2】:

我将用 Python 编写一些代码,因为这是我认为最简单的方法。 我实际上写了 overlappingnon-overlapping 变体。作为奖励,它还检查输入是否有效。 您似乎只对 overlapping 变体感兴趣:

import itertools


def find_all(
        text,
        pattern,
        overlap=False):
    """
    Find all occurrencies of the pattern in the text.

    Args:
        text (str|bytes|bytearray): The input text.
        pattern (str|bytes|bytearray): The pattern to find.
        overlap (bool): Detect overlapping patterns.

    Yields:
        position (int): The position of the next finding.
    """
    len_text = len(text)
    offset = 1 if overlap else (len(pattern) or 1)
    i = 0
    while i < len_text:
        i = text.find(pattern, i)
        if i >= 0:
            yield i
            i += offset
        else:
            break


def is_valid(text, tokens):
    """
    Check if the text only contains the specified tokens.

    Args:
        text (str|bytes|bytearray): The input text.
        tokens (str|bytes|bytearray): The valid tokens for the text.

    Returns:
        result (bool): The result of the check.
    """
    return set(text).issubset(set(tokens))


def shortest_unique_substr(
        text,
        tokens='acgt',
        overlapping=True,
        check_valid_input=True):
    """
    Find the shortest unique substring.

    Args:
        text (str|bytes|bytearray): The input text.
        tokens (str|bytes|bytearray): The valid tokens for the text.
        overlap (bool)
        check_valid_input (bool): Check if the input is valid.

    Returns:
        result (set): The set of the shortest unique substrings.
    """
    def add_if_single_match(
            text,
            pattern,
            result,
            overlapping):
        match_gen = find_all(text, pattern, overlapping)
        try:
            next(match_gen)  # first match
        except StopIteration:
            # the pattern is not found, nothing to do
            pass
        else:
            try:
                next(match_gen)
            except StopIteration:
                # the pattern was found only once so add to results
                result.add(pattern)
            else:
                # the pattern is found twice, nothing to do
                pass

    # just some sanity check
    if check_valid_input and not is_valid(text, tokens):
        raise ValueError('Input text contains invalid tokens.')

    result = set()
    # shortest sequence cannot be longer than this
    if overlapping:
        max_lim = len(text) // 2 + 1
        max_lim = len(tokens)
        for n in range(1, max_lim + 1):
            for pattern_gen in itertools.product(tokens, repeat=2):
                pattern = ''.join(pattern_gen)
                add_if_single_match(text, pattern, result, overlapping)
            if len(result) > 0:
                break
    else:
        max_lim = len(tokens)
        for n in range(1, max_lim + 1):
            for i in range(len(text) - n):
                pattern = text[i:i + n]
                add_if_single_match(text, pattern, result, overlapping)
            if len(result) > 0:
                break
    return result

在对输出的正确性进行一些健全性检查后:

shortest_unique_substr_ovl = functools.partial(shortest_unique_substr, overlapping=True)
shortest_unique_substr_ovl.__name__ = 'shortest_unique_substr_ovl'

shortest_unique_substr_not = functools.partial(shortest_unique_substr, overlapping=False)
shortest_unique_substr_not.__name__ = 'shortest_unique_substr_not'

funcs = shortest_unique_substr_ovl, shortest_unique_substr_not

test_inputs = (
    'aaa',
    'aaaa',
    'aaggcgccttt',
    'agggcttttaaaatttaatttgggccc',
)

import functools

for func in funcs:
    print('Func:', func.__name__)
    for test_input in test_inputs:    
        print(func(test_input))
    print()
Func: shortest_unique_substr_ovl
set()
set()
{'cg', 'ag', 'gg', 'ct', 'aa', 'cc'}
{'tg', 'ag', 'ct'}

Func: shortest_unique_substr_not
{'aa'}
{'aaa'}
{'cg', 'tt', 'ag', 'gg', 'ct', 'aa', 'cc'}
{'tg', 'ag', 'ct', 'cc'}

明智的做法是衡量我们的实际速度。

您可以在下面找到一些基准,使用来自here 的一些模板代码生成(重叠变体在蓝色中):

以及其余代码的完整性:

def gen_input(n, tokens='acgt'):
    return ''.join([tokens[random.randint(0, len(tokens) - 1)] for _ in range(n)])


def equal_output(a, b):
    return a == b


input_sizes = tuple(2 ** (1 + i) for i in range(16))

runtimes, input_sizes, labels, results = benchmark(
    funcs, gen_input=gen_input, equal_output=equal_output,
    input_sizes=input_sizes)

plot_benchmarks(runtimes, input_sizes, labels, units='ms')
plot_benchmarks(runtimes, input_sizes, labels, units='μs', zoom_fastest=2)

就渐近时间复杂度分析而言,仅考虑 重叠 情况,设N 为输入大小,设K 为令牌数(您的case),find_all() 是 O(N),shortest_unique_substr 的主体是 O(K²) (+ O((K - 1)²) + O((K - 2)²) + ...)。 所以,这总体上是O(N*K²)O(N*(Σk²))(对于k = 1, …, K),因为K 是固定的,所以这是O(N),正如基准所表明的那样。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2013-07-28
  • 1970-01-01
  • 1970-01-01
  • 2014-07-09
  • 1970-01-01
  • 2014-02-02
  • 2014-11-12
相关资源
最近更新 更多