Найдите длину самого маленького окна, содержащего все символы строки в другой строке
Недавно у меня были интервью. Я не очень хорошо, потому что я застрял в следующем вопросе
предположим, что задана последовательность: A D C B D A B C D A C D
и последовательность поиска похожа: A C D
Задача
заключалась в том, чтобы найти начальный и конечный индекс в заданной строке, содержащий все символы строки поиска, сохраняющие порядок.
Выход: при запуске индекса начинаются с 1:
начальный индекс 10
end index 12
пояснение:
1.start/end index не являются 1/3 соответственно, потому что, хотя они содержат строку, но порядок не поддерживается
2.start/end index не являются 1/5 соответственно, потому что хотя они содержат строку в порядке, но длина не оптимальна
3.start/end index не 6/9 соответственно, потому что, хотя они содержат строку в порядке, но длина не оптимальна
Пройдите как найти наименьшую подстроку, содержащую все символы из данной строки?.
Но вышеупомянутый вопрос отличается от того, что порядок не поддерживается. Я все еще стараюсь поддерживать индексы. Любая помощь будет оценена по достоинству. спасибо
Ответы
Ответ 1
Я попытался написать некоторый простой c-код для решения проблемы:
Update:
Я написал функцию search
, которая ищет нужные символы в правильном порядке, возвращая длину окна и сохраняя начальную точку окна до ìnt * startAt
. Функция обрабатывает подпоследовательность заданного сена с указанной начальной точки int start
до конца
Остальная часть алгоритма находится в main
, где все возможные подпоследовательности тестируются с небольшой оптимизацией: мы начинаем искать следующее окно сразу после начальной точки предыдущего, поэтому мы пропускаем ненужные очереди. Во время процесса мы продолжаем отслеживать "лучшее решение"
Сложность - O (n * n/2)
Update2:
ненужные зависимости удалены, ненужные последующие вызовы strlen(...)
были заменены параметрами размера, переданными в search(...)
#include <stdio.h>
// search for single occurrence
int search(const char hay[], int haySize, const char needle[], int needleSize, int start, int * startAt)
{
int i, charFound = 0;
// search from start to end
for (i = start; i < haySize; i++)
{
// found a character ?
if (hay[i] == needle[charFound])
{
// is it the first one?
if (charFound == 0)
*startAt = i; // store starting position
charFound++; // and go to next one
}
// are we done?
if (charFound == needleSize)
return i - *startAt + 1; // success
}
return -1; // failure
}
int main(int argc, char **argv)
{
char hay[] = "ADCBDABCDACD";
char needle[] = "ACD";
int resultStartAt, resultLength = -1, i, haySize = sizeof(hay) - 1, needleSize = sizeof(needle) - 1;
// search all possible occurrences
for (i = 0; i < haySize - needleSize; i++)
{
int startAt, length;
length = search(hay, haySize, needle, needleSize, i, &startAt);
// found something?
if (length != -1)
{
// check if it the first result, or a one better than before
if ((resultLength == -1) || (resultLength > length))
{
resultLength = length;
resultStartAt = startAt;
}
// skip unnecessary steps in the next turn
i = startAt;
}
}
printf("start at: %d, length: %d\n", resultStartAt, resultLength);
return 0;
}
Ответ 2
Начните с начала строки.
Если вы столкнулись с A, отметьте позицию и нажмите ее в стеке. После этого продолжайте проверять символы последовательно, пока
1. Если вы столкнулись с A, обновите позицию A до текущего значения.
2. Если вы столкнулись с C, вставьте его в стек.
После того, как вы встретите C, снова продолжайте проверять символы последовательно, пока,
1. Если вы столкнулись с D, сотрите стек, содержащий A и C, и отметьте оценку от A до D для этой подпоследовательности.
2. Если вы столкнулись с A, тогда запустите еще один Stack и отметьте это положение.
2а. Если теперь вы сталкиваетесь с C, удалите предыдущие стеки и сохраните последний стек.
2b. Если вы столкнулись с D, то удалите старый стек и отметьте счет и проверьте, меньше ли он текущего наилучшего.
Продолжайте делать это, пока не достигнете конца строки.
Псевдокод может выглядеть примерно так:
Initialize stack = empty;
Initialize bestLength = mainString.size() + 1; // a large value for the subsequence.
Initialize currentLength = 0;
for ( int i = 0; i < mainString.size(); i++ ) {
if ( stack is empty ) {
if ( mainString[i] == 'A' ) {
start a new stack and push A on it.
mark the startPosition for this stack as i.
}
continue;
}
For each of the stacks ( there can be at most two stacks prevailing,
one of size 1 and other of size 0 ) {
if ( stack size == 1 ) // only A in it {
if ( mainString[i] == 'A' ) {
update the startPosition for this stack as i.
}
if ( mainString[i] == 'C' ) {
push C on to this stack.
}
} else if ( stack size == 2 ) // A & C in it {
if ( mainString[i] == 'C' ) {
if there is a stack with size 1, then delete this stack;// the other one dominates this stack.
}
if ( mainString[i] == 'D' ) {
mark the score from startPosition till i and update bestLength accordingly.
delete this stack.
}
}
}
}
Ответ 3
Я изменил свое предыдущее предложение, используя одну очередь, теперь я считаю, что этот алгоритм работает с O(N*m)
time:
FindSequence(char[] sequenceList)
{
queue startSeqQueue;
int i = 0, k;
int minSequenceLength = sequenceList.length + 1;
int startIdx = -1, endIdx = -1;
for (i = 0; i < sequenceList.length - 2; i++)
{
if (sequenceList[i] == 'A')
{
startSeqQueue.queue(i);
}
}
while (startSeqQueue!=null)
{
i = startSeqQueue.enqueue();
k = i + 1;
while (sequenceList.length < k && sequenceList[k] != 'C')
if (sequenceList[i] == 'A') i = startSeqQueue.enqueue();
k++;
while (sequenceList.length < k && sequenceList[k] != 'D')
k++;
if (k < sequenceList.length && k > minSequenceLength > k - i + 1)
{
startIdx = i;
endIdx = j;
minSequenceLength = k - i + 1;
}
}
return startIdx & endIdx
}
Моя предыдущая (O (1) память):
FindSequence(char[] sequenceList)
{
int i = 0, k;
int minSequenceLength = sequenceList.length + 1;
int startIdx = -1, endIdx = -1;
for (i = 0; i < sequenceList.length - 2; i++)
if (sequenceList[i] == 'A')
k = i+1;
while (sequenceList.length < k && sequenceList[k] != 'C')
k++;
while (sequenceList.length < k && sequenceList[k] != 'D')
k++;
if (k < sequenceList.length && k > minSequenceLength > k - i + 1)
{
startIdx = i;
endIdx = j;
minSequenceLength = k - i + 1;
}
return startIdx & endIdx;
}
Ответ 4
Вот моя версия. Он отслеживает возможных кандидатов для оптимального решения. Для каждого символа в сене он проверяет, находится ли этот символ в последовательности каждого кандидата. Затем он выбирает самый короткий кандидат. Совсем просто.
class ShortestSequenceFinder
{
public class Solution
{
public int StartIndex;
public int Length;
}
private class Candidate
{
public int StartIndex;
public int SearchIndex;
}
public Solution Execute(string hay, string needle)
{
var candidates = new List<Candidate>();
var result = new Solution() { Length = hay.Length + 1 };
for (int i = 0; i < hay.Length; i++)
{
char c = hay[i];
for (int j = candidates.Count - 1; j >= 0; j--)
{
if (c == needle[candidates[j].SearchIndex])
{
if (candidates[j].SearchIndex == needle.Length - 1)
{
int candidateLength = i - candidates[j].StartIndex;
if (candidateLength < result.Length)
{
result.Length = candidateLength;
result.StartIndex = candidates[j].StartIndex;
}
candidates.RemoveAt(j);
}
else
{
candidates[j].SearchIndex += 1;
}
}
}
if (c == needle[0])
candidates.Add(new Candidate { SearchIndex = 1, StartIndex = i });
}
return result;
}
}
Он работает в O (n * m).
Ответ 5
Вот мое решение в Python. Он возвращает индексы, предполагающие 0-индексированные последовательности. Поэтому для данного примера он возвращает (9, 11)
вместо (10, 12)
. Очевидно, что это легко мутировать, чтобы вернуть (10, 12)
, если хотите.
def solution(s, ss):
S, E = [], []
for i in xrange(len(s)):
if s[i] == ss[0]:
S.append(i)
if s[i] == ss[-1]:
E.append(i)
candidates = sorted([(start, end) for start in S for end in E
if start <= end and end - start >= len(ss) - 1],
lambda x,y: (x[1] - x[0]) - (y[1] - y[0]))
for cand in candidates:
i, j = cand[0], 0
while i <= cand[-1]:
if s[i] == ss[j]:
j += 1
i += 1
if j == len(ss):
return cand
Использование:
>>> from so import solution
>>> s = 'ADCBDABCDACD'
>>> solution(s, 'ACD')
(9, 11)
>>> solution(s, 'ADC')
(0, 2)
>>> solution(s, 'DCCD')
(1, 8)
>>> solution(s, s)
(0, 11)
>>> s = 'ABC'
>>> solution(s, 'B')
(1, 1)
>>> print solution(s, 'gibberish')
None
Я считаю, что временная сложность O (p log (p)), где p - количество пар индексов в последовательности, которая относится к search_sequence[0]
и search_sequence[-1]
, где индекс для search_sequence[0]
меньше, чем index для search_sequence[-1]
, потому что он сортирует эти p-пары, используя алгоритм O (n log n). Но опять же, моя подстрочная итерация в конце может полностью затмить этот шаг сортировки. Я не уверен.
Вероятно, она имеет худшую временную сложность, которая ограничена O (n * m), где n - длина последовательности, а m - длина последовательности поиска, но на данный момент я не могу придумать пример в худшем случае.
Ответ 6
Вот мой алгоритм O (m * n) в Java:
class ShortestWindowAlgorithm {
Multimap<Character, Integer> charToNeedleIdx; // Character -> indexes in needle, from rightmost to leftmost | Multimap is a class from Guava
int[] prefixesIdx; // prefixesIdx[i] -- rightmost index in the hay window that contains the shortest found prefix of needle[0..i]
int[] prefixesLengths; // prefixesLengths[i] -- shortest window containing needle[0..i]
public int shortestWindow(String hay, String needle) {
init(needle);
for (int i = 0; i < hay.length(); i++) {
for (int needleIdx : charToNeedleIdx.get(hay.charAt(i))) {
if (firstTimeAchievedPrefix(needleIdx) || foundShorterPrefix(needleIdx, i)) {
prefixesIdx[needleIdx] = i;
prefixesLengths[needleIdx] = getPrefixNewLength(needleIdx, i);
forgetOldPrefixes(needleIdx);
}
}
}
return prefixesLengths[prefixesLengths.length - 1];
}
private void init(String needle) {
charToNeedleIdx = ArrayListMultimap.create();
prefixesIdx = new int[needle.length()];
prefixesLengths = new int[needle.length()];
for (int i = needle.length() - 1; i >= 0; i--) {
charToNeedleIdx.put(needle.charAt(i), i);
prefixesIdx[i] = -1;
prefixesLengths[i] = -1;
}
}
private boolean firstTimeAchievedPrefix(int needleIdx) {
int shortestPrefixSoFar = prefixesLengths[needleIdx];
return shortestPrefixSoFar == -1 && (needleIdx == 0 || prefixesLengths[needleIdx - 1] != -1);
}
private boolean foundShorterPrefix(int needleIdx, int hayIdx) {
int shortestPrefixSoFar = prefixesLengths[needleIdx];
int newLength = getPrefixNewLength(needleIdx, hayIdx);
return newLength <= shortestPrefixSoFar;
}
private int getPrefixNewLength(int needleIdx, int hayIdx) {
return needleIdx == 0 ? 1 : (prefixesLengths[needleIdx - 1] + (hayIdx - prefixesIdx[needleIdx - 1]));
}
private void forgetOldPrefixes(int needleIdx) {
if (needleIdx > 0) {
prefixesLengths[needleIdx - 1] = -1;
prefixesIdx[needleIdx - 1] = -1;
}
}
}
Он работает на каждом входе, а также может обрабатывать повторяющиеся символы и т.д.
Вот несколько примеров:
public class StackOverflow {
public static void main(String[] args) {
ShortestWindowAlgorithm algorithm = new ShortestWindowAlgorithm();
System.out.println(algorithm.shortestWindow("AXCXXCAXCXAXCXCXAXAXCXCXDXDXDXAXCXDXAXAXCD", "AACD")); // 6
System.out.println(algorithm.shortestWindow("ADCBDABCDACD", "ACD")); // 3
System.out.println(algorithm.shortestWindow("ADCBDABCD", "ACD")); // 4
}
Ответ 7
Я не читал каждый ответ здесь, но я не думаю, что кто-то заметил, что это всего лишь ограниченная версия локального парного выравнивания последовательностей, в котором нам разрешено вставлять только символы (а не удалять или заменять их). Как таковой он будет решен путем упрощения алгоритма Smith-Waterman, который учитывает только 2 случая на вершину (прибытие в вершину либо путем сопоставления характер, или вставка символа), а не 3 случая. Этот алгоритм O (n ^ 2).
Ответ 8
Вот мое решение. Он следует за одним из решений совпадения шаблонов. Пожалуйста, прокомментируйте/исправьте меня, если я ошибаюсь.
Учитывая входную строку как в вопросе
A D C B D A B C D A C D
. Пусть сначала вычисляются индексы, где A
. Предполагая индекс на основе нуля, это должно быть [0,5,9]
.
Теперь псевдокод выглядит следующим образом.
Store the indices of A in a list say *orders*.// orders=[0,5,9]
globalminStart, globalminEnd=0,localMinStart=0,localMinEnd=0;
for (index: orders)
{
int i =index;
Stack chars=new Stack();// to store the characters
i=localminStart;
while(i< length of input string)
{
if(str.charAt(i)=='C') // we've already seen A, so we look for C
st.push(str.charAt(i));
i++;
continue;
else if(str.charAt(i)=='D' and st.peek()=='C')
localminEnd=i; // we have a match! so assign value of i to len
i+=1;
break;
else if(str.charAt(i)=='A' )// seen the next A
break;
}
if (globalMinEnd-globalMinStart<localMinEnd-localMinStart)
{
globalMinEnd=localMinEnd;
globalMinStart=localMinStart;
}
}
return [globalMinstart,globalMinEnd]
}
P.S: это псевдокод и приблизительная идея. Id будет рад исправить это и понять, если что-то не так.
AFAIC Сложность времени -O (n). Космическая сложность O (n)