Алгоритм Z-функции через бин поиск занимает очень много памяти


Пытаюсь решить задачу Z-функции за NlogN (знаю, что есть метод и за N, но пытаюсь реализовать именно NlogN). При больших входных данных кушает очень много оперативной памяти. Думал это рекурсия уходит в бесконечность, но вроде нет (Visual Studio тоже не ругался на бесконечный цикл и зависал от потребляемой памяти). N в 10000 символов решает за 2 секунды, что тоже долго, в 20000 символов 8 секунд, больше 30000 начинает зависать.

Алгоритм решается через бинарный поиск, т.е. на каждой позиции рассматриваем префикс и суффикс равные максимальной длины суффикса. Если они не равны, длина уменьшается вдвое, либо берется позиция между последним хорошим и последним плохим результатами. Если равны и разница между проверяемой длиной и последним плохим результатом 1, то взвращаем длину, иначе проверяем диапазон между длиной и последним плохим результатом.

Собственно код : Вычисление хэшей

class HashStr {
private:
    const long long x_ = 257, p = 1000000007;
    long long *h, *x, n;

public:
    // высчитывает хэш строки
    HashStr(string str)
    {
        n = str.size();
        h = new long long[n + 1]; // hashes for string
        x = new long long[n + 1];
        h[0] = 0;
        x[0] = 1;

        for (int i = 1; i < n + 1; i++)
        {
            h[i] = (h[i - 1] * x_ + int(str[i - 1])) % p;
            x[i] = (x[i - 1] * x_) % p;
        }
    }

    // Считает равны ли хэши подстрок
    bool Is_substrs_equal(int len, int from1, int from2)
    {
        if (len + from1 > n || len + from2 > n)
        {
            cout << "Error: Range out!" << endl;
            return false;
        }
        // if (len > 0)
        //     cout << "Error : Len is not positive!" << endl;
        return (h[from1 + len] + h[from2] * x[len]) % p == (h[from2 + len] + h[from1] * x[len]) % p;
    }
};

Сам алгоритм :

int FindZBinSearch(HashStr &hash, const int len, const int pos, const int last_good_res = 0, const int last_bad_res = 0)
{
    constexpr int StartPosition = 0; // начальная позиция префикса всегда 0
    if (len == 0)
        return 0;

    // сравнение строк
    if (!hash.Is_substrs_equal(len, StartPosition, pos))
    {
        // если не равны строки
        int mid = last_good_res > 0 ? last_good_res + (len - last_good_res) / 2 : len / 2; // и у нас еще нет хорошего результата, то длину делим пополам
                                                                                           // если есть хороший результат, мы берем позицию между хорошим результатом и нынешней длиной
        return FindZBinSearch(hash, mid, pos, last_good_res, len);                         // рекурсивно ищем дальше, меняя только длину подстрок и плохой результат
    }
    else
    {
        // если же подстроки равны
        if (last_bad_res == 0) // и нам повезло и сразу целиком они равны, то просто пишем целиком длину
            return len;
        if (last_bad_res - len == 1) // нам не повезло, но разница между хорошей и плохой строкой в 1 символ, то нынешняя длина - это наш результат
            return len;

        int mid = len + (last_bad_res - len) / 2;                 // если же вообще не повезло, то находим середину между последним плохим результатом и нынешней длиной
        return FindZBinSearch(hash, mid, pos, len, last_bad_res); // снова делем тоже самое но уже длина строк проверяется между длиной и плохим результатом, а хороший результат - это нынешняя длина
    }

    return -1;
}

int FindZ(string str, int position)
{
    HashStr hash(str);                        // вычисляем хэши
    unsigned int len = str.size() - position; // максимальная длина от данной позиции и до конца строки
    return FindZBinSearch(hash, len, position);
}

vector<int> z_function_per_log(string str)
{
    int n = str.size();
    vector<int> res(n, 0);

    for (int i = 1; i < n; i++)
    {
        res[i] = FindZ(str, i);
    }

    return res;
}

20000 символов введите сюда описание изображения 10000 символов

введите сюда описание изображения


Ответы (0 шт):