Сжатие координат - Алгоритмика
Сжатие координат

Сжатие координат

автор Сергей Слотин
обновлено апр. 20, 2022

Часто бывает полезно преобразовать последовательность чисел либо каких-то других объектов в промежуток последовательных целых чисел — например, чтобы использовать её элементы как индексы в массиве либо какой-нибудь другой структуре.

Эта задача эквивалентна нумерации элементов множества, что можно сделать за $O(n)$ через хеш-таблицу:

vector<int> compress(vector<int> a) {
    unordered_map<int, int> m;

    for (int &x : a) {
        if (m.count(x))
            x = m[x];
        else
            m[x] = m.size();
    }

    return a;
}

Элементам будут присвоены номера в порядке их первого вхождения в последовательность. Если нужно сохранить порядок, присвоив меньшим элементам меньшие номера, то задача становится чуть сложнее, и её можно решить разными способами.

Как вариант, можно отсортировать массив, а затем два раза пройтись по нему с хэш-таблицей — в первый раз заполняя её, а во второй раз сжимая сам массив:

vector<int> compress(vector<int> a) {
    vector<int> b = a;
    sort(b.begin(), b.end());

    unordered_map<int, int> m;

    for (int x : b)
        if (!m.count(x))
            m[x] = m.size();

    for (int &x : a)
        x = m[x];

    return a;
}

Также можно выкинуть из отсортированного массива дупликаты (за линейное время), а затем использовать его для нахождения индекса каждого элемента исходного массива бинарным поиском:

vector<int> compress(vector<int> a) {
    vector<int> b = a;

    sort(b.begin(), b.end());
    b.erase(unique(b.begin(), b.end()), b.end());

    for (int &x : a)
        x = int(lower_bound(b.begin(), b.end(), x) - b.begin());

    return a;
}

Оба подхода работают за $O(n \log n)$. Используйте тот, который больше нравится.