查找所有长度最短的唯一子字符串?

时间:2019-07-27 14:48:27

标签: algorithm suffix-array string-algorithm

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

查找字符串序列中所有长度最短的唯一子字符串(长度应为所有唯一子字符串的最小值)?

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

我尝试使用后缀数组和最长的公共前缀,但我无法完美地绘制解决方案。

2 个答案:

答案 0 :(得分:0)

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

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

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

我们找到所有长度为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列表会跟踪已经看到的所有子字符串(用于排除重复的子字符串)。

答案 1 :(得分:0)

我将用Python编写一些代码,因为这是我发现的最简单的方法。 我实际上写了 overlapping non-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中的一些模板代码生成的(重叠变体为 blue ):

benchmark_full benchmark_zoom

以及其他完整代码:

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为令牌数(在您的情况下为4),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)。 / p>