All O`one Data Structures

O(1) inc/dec/getMaxKey/getMinKey — doubly-linked list of count buckets, each holding the keys at that count.

Hard
Time O(1) per op Space O(n)
LeetCode
8 min read
design linked-list hash-map

Problem#

Data structure supporting in O(1): inc(key) (add 1 to its count, or set to 1), dec(key) (subtract 1, remove if reaches 0), getMaxKey, getMinKey.

Examples#

  • inc("a"); inc("b"); inc("b"); getMaxKey()"b"; getMinKey()"a".

Constraints#

  • Up to 5 * 10^4 calls; keys are non-empty strings.

Approach#

Doubly-linked list of buckets ordered by count ascending. Each bucket stores its count and a set of keys at that count. Key map: key → (count, bucket iterator). inc/dec moves a key between adjacent buckets, creating/destroying buckets as needed.

Solution#

AllOne
class AllOne {
struct Bucket { int cnt; unordered_set<string> keys; };
list<Bucket> buckets; // ascending by cnt; head = min, tail = max
unordered_map<string, list<Bucket>::iterator> mp;
public:
void inc(string key) {
if (!mp.count(key)) {
if (buckets.empty() || buckets.front().cnt != 1)
buckets.push_front({1, {}});
buckets.front().keys.insert(key);
mp[key] = buckets.begin();
} else {
auto it = mp[key];
auto nxt = next(it);
if (nxt == buckets.end() || nxt->cnt != it->cnt + 1)
nxt = buckets.insert(nxt, {it->cnt + 1, {}});
nxt->keys.insert(key);
mp[key] = nxt;
it->keys.erase(key);
if (it->keys.empty()) buckets.erase(it);
}
}
void dec(string key) {
auto it = mp[key];
if (it->cnt == 1) {
mp.erase(key);
} else {
auto pr = (it == buckets.begin()) ? buckets.end() : prev(it);
if (pr == buckets.end() || pr->cnt != it->cnt - 1)
pr = buckets.insert(it, {it->cnt - 1, {}});
pr->keys.insert(key);
mp[key] = pr;
}
it->keys.erase(key);
if (it->keys.empty()) buckets.erase(it);
}
string getMaxKey() { return buckets.empty() ? "" : *buckets.back().keys.begin(); }
string getMinKey() { return buckets.empty() ? "" : *buckets.front().keys.begin(); }
};

Editorial#

Buckets sorted by count form a sparse “doubly-linked frequency line”. Moving between buckets is always to the immediate neighbor — so even count jumps are O(1). The empty-bucket cleanup keeps the line tight, which is what allows getMin/getMax to be O(1) at the ends.

Complexity#

  • Time: O(1) per op.
  • Space: O(n).

Concept revision#

Search ESC

Keyboard shortcuts

Shortcuts are disabled while typing in inputs.