Алгоритм поиска наименьшего фрагмента из поиска документа?
Я проходил через Skiena отлично "Руководство по разработке алгоритмов" и получил зависание на одном из упражнений.
Вопрос:
"Учитывая строку поиска из трех слов, найдите самый маленький фрагмент документа, который содержит все три слова поиска, т.е. Фрагмент с наименьшим количеством слов в нем. Вам предоставляются позиции индекса, в которых эти слова встречаются в строке поиска, например word1: (1, 4, 5), word2: (4, 9, 10) и word3: (5, 6, 15). Каждый из списков находится в отсортированном порядке, как указано выше."
Все, что я придумал, это O (n ^ 2)... Этот вопрос находится в главе "Сортировка и поиск", поэтому я предполагаю, что есть простой и умный способ сделать это. Я сейчас что-то делаю с графиками, но это похоже на перебор.
Идеи?
Благодаря
Ответы
Ответ 1
Я уже опубликовал довольно простой алгоритм, который решает именно эту проблему в этом ответе
Результаты поиска Google: как найти минимальное окно, содержащее все ключевые слова для поиска?
Однако в этом вопросе мы предположили, что ввод представлен текстовым потоком, а слова хранятся в легкодоступном для поиска наборе.
В вашем случае ввод представлен немного по-другому: как кучка векторов с отсортированными позициями для каждого слова. Это представление легко трансформируется в то, что необходимо для вышеуказанного алгоритма, просто сведя все эти векторы в один вектор пар (position, word)
, упорядоченных по положению. Это можно сделать буквально, или это можно сделать "фактически", поставив исходные векторы в очередь приоритетов (упорядоченных в соответствии с их первыми элементами). Выделение элемента из очереди в этом случае означает появление первого элемента из первого вектора в очереди и, возможно, погружение первого вектора в очередь в соответствии с его новым первым элементом.
Конечно, так как ваш оператор проблемы явно фиксирует количество слов как три, вы можете просто проверить первые элементы всех трех массивов и вывести наименьшее на каждой итерации. Это дает вам алгоритм O(N)
, где N
- общая длина всех массивов.
Кроме того, ваше утверждение проблемы, похоже, предполагает, что целевые слова могут перекрываться в тексте, что довольно странно (учитывая, что вы используете термин "слово" ). Это намеренно? В любом случае, это не представляет проблемы для вышеупомянутого связанного алгоритма.
Ответ 2
Если я что-то не заметил, вот простой алгоритм O (n):
- Мы представляем фрагмент с помощью (x, y), где x и y, где фрагмент начинается и заканчивается соответственно.
- Фрагмент доступен, если он содержит все 3 слова поиска.
- Начнем с недостижимого фрагмента (0,0).
- Повторяйте следующее, пока y не достигнет конца строки:
- Если текущий фрагмент (x, y) возможен, перейдите к фрагменту (x + 1, y)
Else (текущий фрагмент недостижим) перейдите к фрагменту (x, y + 1)
Выберите самый короткий фрагмент среди всех возможных фрагментов, которые мы прошли.
Время выполнения - на каждой итерации либо x, либо y увеличивается на 1, очевидно, x не может превышать y и y не может превышать длину строки, поэтому общее число итераций равно O (n), Кроме того, осуществимость может быть проверена в O (1) в этом случае, так как мы можем отслеживать, сколько вхождений каждого слова в текущий фрагмент. Мы можем поддерживать этот счет при O (1) при каждом увеличении x или y на 1.
Корректность. Для каждого x мы вычисляем минимальный возможный фрагмент (x,?). Таким образом, мы должны перейти к минимальному фрагменту. Кроме того, если y является наименьшим y таким образом, что (x, y) выполнимо, то if (x + 1, y ') является допустимым фрагментом y' >= y (Этот бит - это то, почему этот алгоритм является линейным, а остальные - т).
Ответ 3
Из этого вопроса кажется, что вам даны указатели для каждого из ваших "поисковых слов" (word1, word2, word3,..., word n) в документе. Используя алгоритм сортировки, n независимых массивов, связанных с поисковыми словами, можно легко представить в виде единого массива всех позиций индекса в возрастающем числовом порядке и метку слова, связанную с каждым индексом в массиве (массив индексов).
Основной алгоритм:
(Предназначен для работы независимо от того, планировал ли этот плакат этот вопрос два сосуществования поисковых слов с одним и тем же номером индекса.)
Сначала мы определяем простую функцию для измерения длины фрагмента, содержащего все n меток, заданных начальной точкой в массиве индексов. (Из определения нашего массива видно, что любая начальная точка в массиве обязательно будет индексированным местоположением одной из n поисковых меток.) Функция просто отслеживает уникальные метки поиска, которые видят, как функция выполняет итерации через элементы в массиве до тех пор, пока не будут соблюдены все n меток. Длина фрагмента определяется как разница между индексом найденной последней уникальной метки и индексом начальной точки в массиве индексов (найдена первая уникальная метка). Если все n меток не наблюдаются до конца массива, функция возвращает нулевое значение.
Теперь функция длины фрагмента может быть запущена для каждого элемента в вашем массиве, чтобы связать размер фрагмента, содержащий все n поисковых слов, начиная с каждого элемента массива. Наименьшее значение, отличное от Null, возвращаемое функцией длины фрагмента во всем массиве индексов, является фрагментом вашего документа, который вы ищете.
Необходимые оптимизации:
- Отслеживать значение текущей кратчайшей длины фрагмента, чтобы значение было известно сразу после однократного повторения массива индексов.
- Когда итерация через ваш массив завершает функцию длины фрагмента, если текущий проверяемый фрагмент превосходит длину самой короткой длины фрагмента, которую ранее видели.
- Когда функция длины фрагмента возвращает значение null для того, чтобы не найти все n поисковых слов в остальных элементах массива индекса, сопоставьте длину нулевого фрагмента всем последовательным элементам в массиве индексов.
- Если функция длины фрагмента применяется к метке слова, а метка, следующая за ней, идентична стартовой метке, присвойте нулевое значение стартовой метке и перейдите к следующей метке.
Вычислительная сложность:
Очевидно, что сортировочная часть алгоритма может быть организована в O (n log n).
Здесь, как бы я определил временную сложность второй части алгоритма (любые критические замечания и исправления были бы весьма полезны).
В лучшем случае алгоритм применяет только функцию длины фрагмента к первому элементу в массиве индексов и обнаруживает, что не существует фрагмента, содержащего все слова поиска. Этот сценарий будет вычисляться только в n вычислениях, где n - размер массива индексов. Чуть хуже, чем если бы самый маленький фрагмент оказался равным размеру всего массива. В этом случае вычислительная сложность будет немного меньше 2 n (один раз через массив, чтобы найти наименьшую длину фрагмента, второй раз, чтобы продемонстрировать, что нет других фрагментов). Чем короче средняя вычисленная длина фрагмента, тем больше функция длины фрагмента должна применяться к массиву индексов. Мы можем предположить, что наш худший случайный сценарий будет иметь место, когда функция длины фрагмента должна применяться к каждому элементу в массиве индексов. Чтобы разработать случай, когда функция будет применяться к каждому элементу в массиве индексов, нам нужно создать индексный массив, где средняя длина фрагмента по всему массиву индекса пренебрежимо мала по сравнению с размером массива индексов в целом. Используя этот случай, мы можем вычислить нашу вычислительную сложность как O (C n), где C - некоторая константа, которая значительно меньше n. Давая окончательную вычислительную сложность:
O (n log n + C n)
Где:
< < п
Edit:
AndreyT правильно указывает, что вместо сортировки слова указывается в n log n времени, можно просто слить их (так как вспомогательные массивы уже отсортированы) в n log m time, где m - количество массивов поисковых слов для объединения. Это, очевидно, ускорит алгоритм, это случаи, когда m < п.
Ответ 4
O (n log k), где n - общее число индексов, а k - количество слов. Идея заключается в использовании кучи для определения наименьшего индекса на каждой итерации, а также отслеживания максимального индекса в куче. Я также помещал координаты каждого значения в кучу, чтобы иметь возможность получать следующее значение в постоянное время.
#include <algorithm>
#include <cassert>
#include <limits>
#include <queue>
#include <vector>
using namespace std;
int snippet(const vector< vector<int> >& index) {
// (-index[i][j], (i, j))
priority_queue< pair< int, pair<size_t, size_t> > > queue;
int nmax = numeric_limits<int>::min();
for (size_t i = 0; i < index.size(); ++i) {
if (!index[i].empty()) {
int cur = index[i][0];
nmax = max(nmax, cur);
queue.push(make_pair(-cur, make_pair(i, 0)));
}
}
int result = numeric_limits<int>::max();
while (queue.size() == index.size()) {
int nmin = -queue.top().first;
size_t i = queue.top().second.first;
size_t j = queue.top().second.second;
queue.pop();
result = min(result, nmax - nmin + 1);
j++;
if (j < index[i].size()) {
int next = index[i][j];
nmax = max(nmax, next);
queue.push(make_pair(-next, make_pair(i, j)));
}
}
return result;
}
int main() {
int data[][3] = {{1, 4, 5}, {4, 9, 10}, {5, 6, 15}};
vector<vector<int> > index;
for (int i = 0; i < 3; i++) {
index.push_back(vector<int>(data[i], data[i] + 3));
}
assert(snippet(index) == 2);
}
Ответ 5
Пример реализации в java (проверен только с реализацией в примере, могут быть ошибки). Реализация основана на ответах выше.
import java.util.Arrays;
public class SmallestSnippet {
WordIndex[] words; //merged array of word occurences
public enum Word {W1, W2, W3};
public SmallestSnippet(Integer[] word1, Integer[] word2, Integer[] word3) {
this.words = new WordIndex[word1.length + word2.length + word3.length];
merge(word1, word2, word3);
System.out.println(Arrays.toString(words));
}
private void merge(Integer[] word1, Integer[] word2, Integer[] word3) {
int i1 = 0;
int i2 = 0;
int i3 = 0;
int wordIdx = 0;
while(i1 < word1.length || i2 < word2.length || i3 < word3.length) {
WordIndex wordIndex = null;
Word word = getMin(word1, i1, word2, i2, word3, i3);
if (word == Word.W1) {
wordIndex = new WordIndex(word, word1[i1++]);
}
else if (word == Word.W2) {
wordIndex = new WordIndex(word, word2[i2++]);
}
else {
wordIndex = new WordIndex(word, word3[i3++]);
}
words[wordIdx++] = wordIndex;
}
}
//determine which word has the smallest index
private Word getMin(Integer[] word1, int i1, Integer[] word2, int i2, Integer[] word3,
int i3) {
Word toReturn = Word.W1;
if (i1 == word1.length || (i2 < word2.length && word2[i2] < word1[i1])) {
toReturn = Word.W2;
}
if (toReturn == Word.W1 && i3 < word3.length && word3[i3] < word1[i1])
{
toReturn = Word.W3;
}
else if (toReturn == Word.W2){
if (i2 == word2.length || (i3 < word3.length && word3[i3] < word2[i2])) {
toReturn = Word.W3;
}
}
return toReturn;
}
private Snippet calculate() {
int start = 0;
int end = 0;
int max = words.length;
Snippet minimum = new Snippet(words[0].getIndex(), words[max-1].getIndex());
while (start < max)
{
end = start;
boolean foundAll = false;
boolean found[] = new boolean[Word.values().length];
while (end < max && !foundAll) {
found[words[end].getWord().ordinal()] = true;
boolean complete = true;
for (int i=0 ; i < found.length && complete; i++) {
complete = found[i];
}
if (complete)
{
foundAll = true;
}
else {
if (words[end].getIndex()-words[start].getIndex() == minimum.getLength())
{
// we won't find a minimum no need to search further
break;
}
end++;
}
}
if (foundAll && words[end].getIndex()-words[start].getIndex() < minimum.getLength()) {
minimum.setEnd(words[end].getIndex());
minimum.setStart(words[start].getIndex());
}
start++;
}
return minimum;
}
/**
* @param args
*/
public static void main(String[] args) {
Integer[] word1 = {1,4,5};
Integer[] word2 = {3,9,10};
Integer[] word3 = {2,6,15};
SmallestSnippet smallestSnippet = new SmallestSnippet(word1, word2, word3);
Snippet snippet = smallestSnippet.calculate();
System.out.println(snippet);
}
}
Классы помощников:
public class Snippet {
private int start;
private int end;
//getters, setters etc
public int getLength()
{
return Math.abs(end - start);
}
}
public class WordIndex
{
private SmallestSnippet.Word word;
private int index;
public WordIndex(SmallestSnippet.Word word, int index) {
this.word = word;
this.index = index;
}
}
Ответ 6
О (п)
Pair find(int[][] indices) {
pair.lBound = max int;
pair.rBound = 0;
index = 0;
for i from 0 to indices.lenght{
if(pair.lBound > indices[i][0]){
pair.lBound = indices[i][0]
index = i;
}
if(indices[index].lenght > 0)
pair.rBound = max(pair.rBound, indices[i][0])
}
remove indices[index][0]
return min(pair, find(indices)}
Ответ 7
Другие ответы в порядке, но, как и я, если у вас возникли проблемы с пониманием вопроса, они не очень полезны. Позвольте перефразировать вопрос:
Учитывая три набора целых чисел (назовите их A, B и C), найдите минимальный непрерывный диапазон, который содержит один элемент из каждого набора.
Существует некоторая путаница в том, что такое три набора. Во втором издании книги они обозначены как {1, 4, 5}
, {4, 9, 10}
и {5, 6, 15}
. Однако другая версия, которая была изложена в комментарии выше, это {1, 4, 5}
, {3, 9, 10}
и {2, 6, 15}
. Если одно слово не является суффиксом/префиксом другого, версия 1 невозможна, поэтому давайте перейдем ко второму.
Поскольку картинка стоит тысячи слов, давайте начертим точки:
![enter image description here]()
Просто осмотрев вышесказанное визуально, мы можем увидеть, что есть два ответа на этот вопрос: [1,3]
и [2,4]
, оба размера 3 (три точки в каждом диапазоне).
Теперь алгоритм. Идея состоит в том, чтобы начать с наименьшего допустимого диапазона и постепенно пытаться уменьшить его, перемещая левую границу внутрь. Мы будем использовать индексацию с нуля.
MIN-RANGE(A, B, C)
i = j = k = 0
minSize = +∞
while i, j, k is a valid index of the respective arrays, do
ans = (A[i], B[j], C[k])
size = max(ans) - min(ans) + 1
if (size < minSize), then
minSize = size
fi
x = argmin(ans)
increment x by 1
done
return minSize
где argmin
- это индекс наименьшего элемента в ans
.
+---+---+---+---+--------------------+---------+
| n | i | j | k | (A[i], B[j], C[k]) | minSize |
+---+---+---+---+--------------------+---------+
| 1 | 0 | 0 | 0 | (1, 3, 2) | 3 |
+---+---+---+---+--------------------+---------+
| 2 | 1 | 0 | 0 | (4, 3, 2) | 3 |
+---+---+---+---+--------------------+---------+
| 3 | 1 | 0 | 1 | (4, 3, 6) | 4 |
+---+---+---+---+--------------------+---------+
| 4 | 1 | 1 | 1 | (4, 9, 6) | 6 |
+---+---+---+---+--------------------+---------+
| 5 | 2 | 1 | 1 | (5, 9, 6) | 5 |
+---+---+---+---+--------------------+---------+
| 6 | 3 | 1 | 1 | | |
+---+---+---+---+--------------------+---------+
n
= итерация
На каждом шаге один из трех индексов увеличивается, поэтому алгоритм гарантированно завершится. В худшем случае i
, j
и k
увеличиваются в этом порядке, и алгоритм выполняется за O(n^2)
(в данном случае 9). Для данного примера он заканчивается через 5 итераций.