Найдите наименьшую уникальную подстроку для каждой строки в массиве
(Я пишу это в контексте JavaScript, но приму алгоритмически правильный ответ на любом языке)
как найти кратчайшую подстроку каждого элемента в массиве строк, где подстрока не содержится ни в одном из других элементов, игнорируя регистр?
Предположим у меня есть массив, например:
var names = ["Anne", "Anthony", "LouAnn", "Kant", "Louise", "ark"];
выход должен быть чем-то вроде:
var uniqueNames = ["ne", "h", "ua", "ka", "i", "r"];
для моих целей, вы можете можно с уверенностью предположить, что ни один элемент не будет полностью содержаться в другом элементе.
Мои Мысли:
Кажется, что можно было бы, вероятно, грубо заставить это, по линиям:
var names = ["Anne", "Anthony", "LouAnn", "Kant", "Louise", "ark"];
var uniqueNames = [], nameInd, windowSize, substrInd, substr, otherNameInd, foundMatch;
// For each name
for (nameInd = 0; nameInd < names.length; nameInd++)
{
var name = names[nameInd];
// For each possible substring length
windowLoop:
for (windowSize = 1; windowSize <= name.length; windowSize++)
{
// For each starting index of a substring
for (substrInd = 0; substrInd <= name.length-windowSize; substrInd++)
{
substr = name.substring(substrInd,substrInd+windowSize).toLowerCase();
foundMatch = false;
// For each other name
for (otherNameInd = 0; otherNameInd < names.length; otherNameInd++)
{
if (nameInd != otherNameInd && names[otherNameInd].toLowerCase().indexOf(substr) > -1)
{
foundMatch = true;
break;
}
}
if (!foundMatch)
{
// This substr works!
uniqueNames[nameInd] = substr;
break windowLoop;
}
}
}
}
но я должен представить, что есть более элегантное решение, использующее деревья/префиксы, массивы суффиксов или что-то интересное.
изменить: Я считаю, что это форма, которую выбранный ответ будет принимать программно в JavaScript:
var names = ["Anne", "Anthony", "LouAnn", "Kant", "Louise", "ark"];
var uniqueNames = [], permutations = {}, permutation, nameInd, windowSize, substrInd, substr;
// For each name
for (nameInd = 0; nameInd < names.length; nameInd++)
{
var name = names[nameInd];
// For each possible substring length
windowLoop:
for (windowSize = 1; windowSize <= name.length; windowSize++)
{
// For each starting index of a substring
for (substrInd = 0; substrInd <= name.length-windowSize; substrInd++)
{
substr = name.substring(substrInd,substrInd+windowSize).toLowerCase();
permutations[substr] = (typeof permutations[substr] === "undefined")?nameInd:-1;
}
}
}
for (substr in permutations)
{
permutation = permutations[substr];
if (permutation !== -1 && ((typeof uniqueNames[permutation] === "string" && substr.length < uniqueNames[permutation].length) || typeof uniqueNames[permutation] === "undefined"))
{
uniqueNames[permutation] = substr;
}
}
3 ответов
сказать N
- количество строк и L
максимальная длина строки. Вы делаете до N*L*L*N
итераций.
Я могу только улучшить его немного, торгуя одной итерации для дополнительной памяти. Для каждой возможной длины подстроки (L
итераций),
перечислить все подстроки этой длины в каждом имени (
N*L
), и сохраните его среди индекса с именем в хэш-таблицу (1
). Если для этой подстроки уже есть индекс, вы знаете это не сработает, тогда вы замените индекс каким-то специальным значением, например-1
.прогулка по хэш-таблице, подбирая подстроки, для которых индекс не
-1
- это ответы для их соответствующих индексов, но используйте их только в том случае, если у этих имен еще нет более короткого ответа от предыдущей итерации
использование памяти может быть значительно сокращено путем сохранения ссылки обратно в существующую строку вместо копирования подстрок.
эта проблема может быть решена в O (N*L*L*L) сложности. Подход будет использовать суффикс пытается. Каждый узел trie также будет хранить количество префиксов, которое будет ссылаться на количество раз, когда подстрока, сформированная при переходе к этому узлу от корня, появилась во всех суффиксах, вставленных до сих пор.
мы будем строить N+1 пытается. Первый trie будет глобальным, и мы будем вставлять все суффиксы всех N строку в него. Следующий N попытки будут локальными для каждого из N строки, содержащие соответствующие суффиксы.
этот шаг предварительной обработки построения попыток будет выполнен в O (N*L*L).
теперь, когда попытки были построены, для каждой строки мы можем начать quering количество раз, когда подстрока (начиная с минимальной длины) произошла в глобальном trie и trie, соответствующем этой строке. Если он одинаков в обоих случаях, это означает, что он не включен ни в какие другие строки, кроме себя. Это может быть достигнуто в O (N*L*L*L). Сложность может быть объяснена как N для каждой строки, L*L для рассмотрения каждой подстроки и L для выполнения запроса в trie.
если вы создаете обобщенное дерево суффиксов, вам просто нужно найти самую мелкую точку, в которой инфикс каждой строки ветвится от инфиксов других строк, и взять метку в эту точку ветвления плюс один "отличительный" символ. Кикер заключается в том, что должен быть такой дополнительный символ (он может ветвиться только в метасимволе, застрявшем в конце каждой строки), и точка ветвления может не привести к листу, она может привести к поддереву с листьями все из одной строки (поэтому необходимо учитывать внутренние узлы).
для каждой строки s найдите самый мелкий (по глубине родительской метки) узел N, который содержит только листья из S и чья метка края содержит по крайней мере один символ. Метка пути от корня до родителя N плюс один символ от метки края, ведущей к N, является самым коротким инфиксом S, не найденным в других строках.
Я считаю, что маркировка узлов, которые содержат только одну строку может быть выполняется во время построения или с помощью O(N) сканирования GST; тогда просто сканировать конечное дерево и поддерживать минимум для каждой строки. Так что это все O (N).
(edit -- I can't reply to comments yet)
чтобы уточнить, каждый суффикс в дереве суффиксов имеет узел, где он ветвится от других суффиксов; цель здесь-найти суффикс/a для каждой строки, которая ветвится от суффиксов из всех остальных строк на минимальной глубине, как измеряется меткой пути к этому узлу. Все, что нам нужно, это один дополнительный символ после этой точки, чтобы иметь подстроку, которая не появляется ни в одной другой строке.
пример:
строки: abbc, abc
используя алгоритм Уконнена, после первой строки у нас есть дерево суффиксов только суффиксов из этой строки; я обозначу их [1] здесь:
abbc[1]
b
bc[1]
c[1]
c[1]
Далее вставляем суффиксы строки 2:
ab
bc[1]
c[2]
b
bc[1]
c
[1]
[2]
c
[1]
[2]
теперь мы хотим найти самая короткая строка, которая ведет к ветви с только [1] под ней; мы можем сделать это, сканируя все [1] и глядя на их ближайших родителей, которые я перечислю здесь по метке пути, плюс один символ (который я буду использовать ниже):
abbc: abb
bbc: bb
bc: bc[1]
c: c[1]
обратите внимание, что я включил [1], так как это метасимвол, который различает в противном случае идентичные суффиксы [1] и [2]. Это удобно при поиске подстрок, которые повторяются в нескольких строках, но это не полезно для нашей задачи, поскольку, если мы удаляем [1] мы заканчиваем строку, которая встречается в [2] также, т. е. это не кандидат.
теперь ни одна из меток справа не встречается в любой другой строке, поэтому мы выбираем самый короткий, не включая метасимвол, который является bb.
аналогично, во второй строке есть следующие кандидаты:
abc: abc
bc: bc[2]
c: c[2]
только у одного нет метасимвола в конце, поэтому мы должны пойти с abc.
мой последний момент заключается в том, что этот минимальный поиск для каждой строки не должно происходить по одному; GST можно сканировать один раз, чтобы помечать узлы как содержащие листья из одной строки ([1],[2],..[n]) или" смешанные", а затем минимальные не разделяемые строки (Я бы назвал эти" отличительные инфиксы") могут быть вычислены за один проход.