今天捣鼓一天总算是把整个代码给搞通顺了。整体思路是双端链表+Hash的方式,实现代码为C++;
首先定义双端链表结点的数据结构(与LRU那道题有点类似),我们规定,链表是一个升序链表:
struct Node {
Node() {}
Node(string key) : key(std::move(key)), count(1) {}
// 保存当前的key和count值,其中count默认赋值为1
string key;
int count;
Node *pre = nullptr, *next = nullptr;
// 定义count的自增和自减操作
void inc() { ++count; }
void dec() { --count; }
};
接下来首先看需要用到的数据成员,这里使用unordered_map而不是map,因为map底层是红黑树实现,复杂度为O(logn),不满足我们的要求,而unordered_map底层是hash实现,可以满足:
// 保存key到Node*的映射
unordered_map<string, Node *> key_map;
// 保存count到Node*的映射
unordered_map<int, Node *> count_to_node;
// 用于构成双端链表
Node *head, *tail;
注意这里可以不用max和min表示最大和最小,我们规定:
- head结点的下一个结点count最小
- tail结点的上一个结点count最大
给出了这个定义后,那么getMaxKey()
和getMinKey()
这两个函数就可以先写出来,并且保证都是O(1)的操作:
string getMaxKey() {
return tail->pre == head ? "" : tail->pre->key;
}
string getMinKey() {
return head->next == tail ? "" : head->next->key;
}
接下来我们还需要补充几个有关链表的辅助函数,包括链表结点的删除与插入,函数比较简单,这里就不多做讲解,主要有一点,插入时为了防止出现环,需要在插入时对结点进行检查,如果相同则不需要进行插入:
void delete_node(Node *node) {
// 删除结点
if (node->pre) node->pre->next = node->next;
if (node->next) node->next->pre = node->pre;
}
void insert_before(Node *node, Node *insert) {
insert_after(node, insert->pre);
}
void insert_after(Node *node, Node *insert) {
if (node == insert) return;
delete_node(node);
Node *next = insert->next;
node->pre = insert;
node->next = next;
next->pre = node;
insert->next = node;
}
由于两个map key类型不同,值类型相同,我们还定义一个函数用于判断map中是否存在键:
template<class T>
bool find_node(const unordered_map<T, Node *> &map, const T &key) {
return map.find(key) != map.end();
}
然后是构造函数部分,也比较简单,head和tail的count值主要保证他们一定在常规的count范围之外:
AllOne() {
head = new Node;
head->count = -1;
tail = new Node;
tail->count = INT_MAX - 1;
head->next = tail;
tail->pre = head;
}
简单的热身到这里结束,接下来进入重头戏,也就是inc()
和dec()
两个主要的函数:
在此之前,我们再继续加上一条规定,所有count_to_node
中保存的Node,都是最新插入的Node值,并且该值一定位于一段具有相等的count的连续Node的第一个结点,这里画个图表示:
因此,我们每进行一次结点的插入时,都需要count_to_node[node0->count] = node0
接下来看inc()
部分的代码:
inc()
代码分两种情况进行,即key不存在与key存在:
首先是存在部分:
node = new Node(key);
// 插入结点,初始count为1
key_map.insert({key, node});
/*
这里主要是保证新插入的结点是一段连续相等count结点的第一个(像前面解释的)
*/
if (find_node(count_to_node, 1)) {
// 存在为1的结点,在pre_nodes中插入为1的结点
insert_before(node, count_to_node[1]);
} else {
// 有结点但不存在count为1的结点,或者不存在结点,在head之后插入结点
insert_after(node, head);
}
// 将1更新为最新插入的结点
count_to_node[1] = node;
然后是key不存在的部分:
// 存在结点
node = key_map[key];
int count = node->count;
// 增加前的处理,判断是否最新插入的结点
check_before(node);
// 自增
node->inc();
// 增加后的处理
insert_node(node, node->count);
其中check_before函数主要用于检测当前结点是否是最新插入的结点:
void check_before(const Node *node) {
int count = node->count;
if (count_to_node[count] == node) {
if (node->next->count == count) {
// 存在下一个结点的count与当前结点相等,节点不唯一,用下一个结点替换当前结点
count_to_node[count] = node->next;
} else {
// 不存在下一个结点的count与当前结点相等
// 说明当前count的结点唯一,可以删除
count_to_node.erase(count);
}
}
}
增加后的处理函数如下:
void insert_node(Node *node, int count) {
// 判断count自增后,count_to_node是否存在相关结点
if (find_node(count_to_node, count)) {
// 存在结点,在这个结点之前进行插入
insert_before(node, count_to_node[count]);
// 当前结点变为最先插入的结点
count_to_node[count] = node;
} else {
// 没有找到
count_to_node[count] = node;
if (tail->pre->count < count) {
// 出现大于count最大值的情况就在尾结点前插入
insert_before(node, tail);
}
}
}
注意这个函数中,我们在没有找到节点时,仅当count大于最大值的时候才进行插入操作,这是一个不太好理解的点。
由于这里是自增,且链表升序,如果在
count_to_node
中没有找到对应结点,有两种情况(1)说明node的next结点,其count值要比自增后的count还要大,因此不用变动链表的结构;(2)说明当前结点的count值已经是最大,需要更新链表。
到这里,inc()
部分就算完成了,其实dec()
也差不多,只是多了一种特殊的判断,那就是删除结点的情况,这里直接上完整的dec()
代码:
void dec(string key) {
if (!find_node(key_map, key)) {
// 不在key_map中,直接退出
return;
} else {
Node *node = key_map[key];
// 自减前
check_before(node);
node->dec();
// 为0直接删除
if (node->count == 0) {
delete_node(node);
key_map.erase(node->key);
delete node;
} else {
// 不为0,执行自减后的结点插入
insert_node(node, node->count);
}
}
}
整体代码如下:
#include <string>
#include <unordered_map>
using namespace std;
struct Node {
Node() {}
Node(string key) : key(std::move(key)), count(1) {}
string key;
int count;
Node *pre = nullptr, *next = nullptr;
void inc() { ++count; }
void dec() { --count; }
};
class AllOne {
public:
/** Initialize your data structure here. */
AllOne() {
head = new Node;
head->count = -1;
tail = new Node;
tail->count = INT_MAX - 1;
head->next = tail;
tail->pre = head;
}
/** Inserts a new key <Key> with value 1. Or increments an existing key by 1. */
void inc(string key) {
Node *node;
if (!find_node(key_map, key)) {
node = new Node(key);
key_map.insert({key, node});
if (find_node(count_to_node, 1)) {
insert_before(node, count_to_node[1]);
} else {
insert_after(node, head);
}
count_to_node[1] = node;
} else {
node = key_map[key];
check_before(node);
node->inc();
insert_node(node, node->count);
}
}
void dec(string key) {
if (!find_node(key_map, key)) {
return;
} else {
Node *node = key_map[key];
check_before(node);
node->dec();
if (node->count == 0) {
delete_node(node);
key_map.erase(node->key);
delete node;
} else {
insert_node(node, node->count);
}
}
}
string getMaxKey() {
return tail->pre == head ? "" : tail->pre->key;
}
string getMinKey() {
return head->next == tail ? "" : head->next->key;
}
private:
void delete_node(Node *node) {
if (node->pre) node->pre->next = node->next;
if (node->next) node->next->pre = node->pre;
}
void check_before(const Node *node) {
int count = node->count;
if (count_to_node[count] == node) {
if (node->next->count == count) {
count_to_node[count] = node->next;
} else {
count_to_node.erase(count);
}
}
}
void insert_before(Node *node, Node *insert) {
insert_after(node, insert->pre);
}
void insert_after(Node *node, Node *insert) {
if (node == insert) return;
delete_node(node);
Node *next = insert->next;
node->pre = insert;
node->next = next;
next->pre = node;
insert->next = node;
}
void insert_node(Node *node, int count) {
if (find_node(count_to_node, count)) {
insert_before(node, count_to_node[count]);
count_to_node[count] = node;
} else {
count_to_node[count] = node;
if (tail->pre->count < count) {
insert_before(node, tail);
}
}
}
template<class T>
bool find_node(const unordered_map<T, Node *> &map, const T &key) {
return map.find(key) != map.end();
}
unordered_map<string, Node *> key_map;
unordered_map<int, Node *> count_to_node;
Node *head, *tail;
};
这道题确实有一定难度,做了很久,但是做出来了还是很有成就感的。