Количество подстрок, не содержащих заданные строки (большое ограничение)

Недавно я обнаружил интересную проблему в Интернете. Краткое заявление в следующем:

Обратите внимание, что общее ограничение по времени не должно быть равно 1,00 с (сложность по времени ‹ 10^8).

Теперь студент А нашел строку, состоящую только из символов нижнего регистра. Он хочет вырезать подстроку студенту Б в подарок. У студента Б есть список строк, которые он считает «уродливыми». Можете ли вы помочь студенту А найти количество способов вырезать подстроку, не содержащую «уродливых» строк. (Помните, что одна и та же подстрока, но с другой позиции, также учитывается).

Example:
Student A: abcdabcdab
Ugly strings: cd, da

Output: 17

Explanation:
    The 17 cuttings are "a" (appears 3 times), "ab" (appears 3 times), 
    "abc" (appears 2 times), "bc" (appears 2 times), "b" (appears 3 times),
    "c" (appears 2 times) and "d" (appears 2 times)

Сначала я думал, что это простая проблема, но это ограничение довольно велико. Максимальная длина строки студента А составляет 100 000, в то время как некрасивых строк может быть не более 500 000 с максимальной длиной 500 000.

Я пытаюсь решить эту проблему, используя суффикс trie, но с треском провалился из-за ограничения памяти. Может кто подскажет возможный путь решения проблемы. Это какая-то сложная проблема, связанная со структурами данных, например. массив суффиксов

Рекомендуется код на любом языке программирования с надлежащим описанием. Потому что я считаю, что лучше, если есть действительно код для изучения.


person Brian Lee    schedule 01.12.2017    source источник


Ответы (1)


Поскольку одинаковые подстроки, начинающиеся с разных позиций, считаются разными подстроками, максимальное количество подстрок в строке длины n равно n*(n + 1)/2. (n подстрок, начинающихся с позиции 0, n-1 подстрок, начинающихся с позиция 1 и так далее).

Если уродливая строка содержится в подстроке длины q, начинающейся с позиции p, все подстроки, начинающиеся с p и имеющие длину > q, также будут содержать эту уродливую строку.

Если уродливая строка длиннее самой подстроки, она не будет совпадать.

Моя первая попытка будет выглядеть так:

String ugly[]; // is provided somehow; at most 500000 with max length of 500000
String student; // the String to cut into substrings, max length 100000
long num = 0;

ugly.sort(); // by length

for (int start = 0; start < student.size() - 1, ++start) {
    for (int end = start + 1; end < student.size(); ++end) {
        String s = student.substr(start, end);
        int lgth = s.size();
        int u = 0;
        while (lgth >= ugly[u].size()) {
            if (s.contains(ugly[u])) break;
            ++u;
        }
        if (lgth < ugly[u].size()) {
            ++num; // we checked all potentially matching uglies
        } else {
            break; // leave the inner loop and 
                   // start with the next position
        }
    }
}

Моя вторая попытка будет использовать другой подход. Я бы начал разрабатывать это, если бы эту задачу нужно было выполнять более чем несколько раз.

Если у меня есть строка student и уродливая строка длины p, которая где-то совпадает, строка student может быть разделена на две части: первая часть, которая заканчивается первыми p-1 символами уродливой строки, и вторая часть, которая начинается с последние символы p-1 уродливой строки.

Это можно повторять до тех пор, пока уродливая строка нигде не будет совпадать. Затем у нас есть несколько подстрок и уродливая строка, которая не соответствует ни одной из них. Следовательно, эту уродливую строку можно отбросить.

Повторяя это для всех уродливых строк, вы получите список «самых длинных» подстрок, которые не соответствуют ни одной из уродливых строк. Теперь вы можете перебрать этот список и добавить длину * (длина + 1)/2 к конечному результату.

Так это будет выглядеть

String ugly[]; // as before
String student; // as before
long num = 0;
Vector substrs = new Vector();

ugly.sort(); // by length
substrs.add(student);

void splitStr(String str2split, String pattern, Vector result)
{
    if (str2split.size() < pattern.size()) {
        result.add(str2split);
        return;
    } else {
        int pos = str2split.contains(pattern); // returns position, -1 if not found
        if (pos >= 0) { // found
            String s1 = str2split.substr(0, pos + pattern.size() - 1);
            String s2 = str2split.substr(pos + 1, str2split.size());
            // add s1 and repeat split on s2
            result.add(s1);
            splitStr(s2, pattern, result);
        } else {
            // not found, entire string is ok
            result.add(str2split);
        }
    }
}

for (int u = 0; u < ugly.size(); ++u) {
    Vector newSubstrs = new Vector();
    String ugly2test = ugly[u];
    for (int i = 0; i < substrs.size(); ++i) {
        String t = substrs.get(i);
        splitStr(t, ugly2test, newSubstrs);
    }
    substrs = newSubstrs;
}

for (int i = 0; i < substrs.size(); ++i) {
    String s = substrs.get(i);
    num += s.size() * (s.size() + 1) / 2;
}

Примечание: это в основном идея. Я не тестировал какой-либо код (который похож на Java, но, вероятно, не будет компилироваться), я только перевел свою идею простого текста в какой-то псевдокод.

person Ronald    schedule 01.12.2017
comment
Временная сложность кода Ur в худшем случае составляет O (500000 * 500000 * 100000), я думаю, что это превысит лимит времени - person Brian Lee; 01.12.2017
comment
Что ж, если n равно длине строки, которую нужно разбить на подстроки, а m равно количеству уродливых шаблонов, количество операций равно O (n² m). (м * п * (п + 1) / 2). (Я не учел поиск, который добавляет еще одно n, следовательно, O (n ^ 4)). Но обнаружение уродливых шаблонов значительно ускоряет процесс. Есть вещи, которые вы можете сделать, чтобы уменьшить количество уродливых паттернов (например, можно исключить более длинные паттерны, которые содержат более короткий паттерн), но я также могу предположить, что набор уродливых паттернов нельзя уменьшить. - person Ronald; 01.12.2017
comment
Вы можете воспользоваться уродливыми шаблонами с одним символом. Вы можете вырезать их все из исходной строки, получив набор подстрок. Каждый из них может быть обработан с помощью моего алгоритма. Из-за уменьшенной длины это будет намного быстрее. - person Ronald; 01.12.2017
comment
Я думаю, что только часть для исчерпания возможной подстроки уже приведет к превышению лимита времени. (О (N ^ 2)) - person Brian Lee; 02.12.2017
comment
Ну, как-нибудь придется их посчитать. Ясно, что если у вас есть подстрока студента длины p, которая не соответствует ни одному из уродливых шаблонов, вы можете добавить p(p+1)/2 к результату. Так что, возможно, движение назад (начните со всей подстроки, начинающейся с некоторой позиции, и сделайте ее короче, если у вас есть попадание (используйте позицию попадания)) ускорит процесс. - person Ronald; 03.12.2017
comment
С другой стороны, мой алгоритм не так уж и плох. Как только уродливый шаблон совпадет, он продолжит со следующей позиции. До тех пор проверяются только короткие уродливые шаблоны. Тест contains() можно заменить на endWith(), воспользовавшись тем фактом, что последняя итерация ничего не нашла. Это значительно снижает накладные расходы на поиск. - person Ronald; 03.12.2017
comment
Я не знаю, заметили ли вы, что я добавил второй подход в свой ответ. Сложность по-прежнему квадратична или хуже, но этого нельзя избежать. Вам всегда придется проверять, по крайней мере, все уродливые шаблоны, чтобы они присутствовали в вашей строке из n символов хотя бы один раз. Невозможно избежать O (нм). - person Ronald; 05.12.2017