Ежедневный бит (е) C ++ # 35, Общая проблема интервью C ++: структура данных ранжирования all-O (1)
Реализуйте структуру данных O(1), то есть структуру данных, в которой все операции выполняются за O(1) (амортизированное) время.
Структура данных должна хранить ключи и их целочисленные значения и предлагать следующий интерфейс.
increment(key)
увеличить значение ключа, если он существует, или вставить ключ со значением 1, если он не существуетdecrement(key)
уменьшить значение ключа, если он существует, или, если значение уже равно 1, удалить егоmin()
вернуть один из минимальных ключей или пустую строку, если ключей нетmax()
вернуть один из максимальных ключей или пустую строку, если ключей нет
Прежде чем продолжить чтение решения, попробуйте решить его самостоятельно. Вот ссылка на проводник компилятора с коротким тестовым примером: https://compiler-explorer.com/z/vaPsGsxze.
Решение
При разработке структуры данных с особыми требованиями к сложности стоит учитывать, какие микрооперации нам потребуются и какие инструменты мы можем использовать для их достижения.
Для операций min()
и max()
нам нужно будет сохранить нашу структуру данных в каком-то отсортированном порядке. Это связано с тем, что у нас нет возможности пересчитать минимум после удаления элемента min или max.
В связи с этим у нас есть дополнительное требование. Увеличение (или уменьшение) ключа может потребовать перемещения ключа перед многими другими ключами с тем же значением. Только одна структура данных поддерживает такую операцию, std::list
.
Чтобы сохранить ключи отсортированными, мы можем сгруппировать ключи по значению в сегменты. Каждое увеличение или уменьшение может привести к созданию новой корзины или, если целевая корзина уже существует, к простому перемещению.
Наконец, нам нужен способ перехода к определенной клавише. std::list
предоставляет стабильные итераторы, поэтому нам нужно только сопоставление ключа с конкретным итератором. Мы можем использовать std::unordered_map
для этого.
struct ConstantRanking { // Increment a key // - if the key doesn't exists, insert it with value 1 instead void increment(const std::string& key) { // Check if the key exists auto it = lookup.find(key); if (it == lookup.end()) { push_front(key); // insert return; } auto [it_item, it_bucket] = it->second; int value = it_item->second; // Get/create a bucket for value+1 auto next = next_bucket(it_bucket); // Remove from the bucket for value remove(it_bucket, it_item); // Add it to the bucket for value+1 add(next, key, value + 1); } // Decrement a key // - if the key already has value 1, remove it void decrement(const std::string& key) { // Check if the key exists auto it = lookup.find(key); if (it == lookup.end()) { return; } auto [it_item, it_bucket] = it->second; int value = it_item->second; if (value == 1) { // Key already at value 1, remove lookup.erase(key); remove(it_bucket, it_item); } else { // Get/create a bucket for value-1 auto prev = prev_bucket(it_bucket); // Remove from the bucket for value remove(it_bucket, it_item); // Add it to the bucket for value-1 add(prev, key, value - 1); } } // Get (one of) the maximum keys // - if empty, return an empty string std::string max() const { if (storage.empty()) return ""; return storage.back().back().first; } // Get (one of) the minimum keys // - if empty, return an empty string std::string min() const { if (storage.empty()) return ""; return storage.front().front().first; } private: using Item = std::pair<std::string, int>; using Bucket = std::list<Item>; using ItemIt = std::list<Item>::iterator; using BucketIt = std::list<Bucket>::iterator; std::list<Bucket> storage; std::unordered_map<std::string, std::pair<ItemIt, BucketIt>> lookup; // Helper: get (or make) next bucket BucketIt next_bucket(BucketIt it) { int value = it->front().second; auto next = std::next(it); // There is no next bucket or the next bucket // doesn't contain value + 1 items. if (next == storage.end() || next->front().second != value + 1) return storage.insert(next,Bucket{}); return next; } // Helper: get (or make) previous bucket BucketIt prev_bucket(BucketIt it) { int value = it->front().second; // There is no previous bucket or the previous bucket // doesn't contain value - 1 items. if (it == storage.begin() || std::prev(it)->front().second != value - 1) return storage.insert(it,Bucket{}); return std::prev(it); } // Helper: remove item from bucket void remove(BucketIt bucket, ItemIt item) { bucket->erase(item); if (bucket->empty()) storage.erase(bucket); } // Helper: add item to bucket void add(BucketIt bucket, const std::string& key, int value) { bucket->push_front(Item{key,value}); lookup[key] = std::pair{bucket->begin(), bucket}; } // Helper: add a new item with value 1 void push_front(const std::string& key) { if (storage.size() == 0 || storage.front().front().second != 1) storage.push_front(std::list<Item>{}); add(storage.begin(), key, 1); } };