昨天手机上提交的,时间匆忙,没来得及写,今日补上哦!!
请你设计一个用于存储字符串计数的数据结构,并能够返回计数最小和最大的字符串。
实现 AllOne 类:
AllOne() 初始化数据结构的对象。
inc(String key) 字符串 key 的计数增加 1 。如果数据结构中尚不存在 key ,那么插入计数为 1 的 key 。
dec(String key) 字符串 key 的计数减少 1 。如果 key 的计数在减少后为 0 ,那么需要将这个 key 从数据结构中删除。测试用例保证:在减少计数前,key 存在于数据结构中。
getMaxKey() 返回任意一个计数最大的字符串。如果没有元素存在,返回一个空字符串 “” 。
getMinKey() 返回任意一个计数最小的字符串。如果没有元素存在,返回一个空字符串 “” 。
示例:
输入
[“AllOne”, “inc”, “inc”, “getMaxKey”, “getMinKey”, “inc”, “getMaxKey”, “getMinKey”]
[[], [“hello”], [“hello”], [], [], [“leet”], [], []]
输出
[null, null, null, “hello”, “hello”, null, “hello”, “leet”]
解释
AllOne allOne = new AllOne();
allOne.inc(“hello”);
allOne.inc(“hello”);
allOne.getMaxKey(); // 返回 “hello”
allOne.getMinKey(); // 返回 “hello”
allOne.inc(“leet”);
allOne.getMaxKey(); // 返回 “hello”
allOne.getMinKey(); // 返回 “leet”
提示:
1 <= key.length <= 10
key 由小写英文字母组成
测试用例保证:在每次调用 dec 时,数据结构中总存在 key
最多调用 inc、dec、getMaxKey 和 getMinKey 方法 5 * 104 次
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/all-oone-data-structure
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
这个题目的确很难,读题明白意思就有些头大,不过看例子是不错的想法。让我想到了【146. LRU 缓存机制】,感兴趣的可以看这个面试题
定义一个节点类 Node,除了包含用于实现双向链表的 left 和 right 以外,还包含一个数值类型的变量 cnt, 用于记录该节点存储的是计数次数为多少的元素,以及一个 Set 类型的容器,用于支持 O(1) 插入和删除元素,记作 set。
同时为了快速知道某个字符串属于哪个 Node,我们还需要开一个「哈希表」进行定位(以字符串为哈希表的键,字符串所在 Node 作为值),当定位到字符串对应的 Node 之后则可以利用双向链表的 O(1) 增加/修改/删除。
在双向链表中,起始只有两个哨兵节点 hh和 tt ,当进行若干 inc/dec 操作后的基本形态为:
对应几个操作:
inc/dec 操作:当对一个字符串 key 进行「增加计数」或「减少计数」时,先在哈希表中看 key 是否存在:
若存在:根据其所属的 Node 的计数 cnt 为多少,并结合当前是「增加计数」还是「减少计数」来决定是找 Node 的「右节点」还是「左节点」,同时检查相邻节点的计数值 cnt 是否为目标值,对应要检查数值是 cnt + 1cnt+1 和 cnt - 1cnt−1:
若相邻节点的 cnt 为目标值:即目标节点存在,将 key 从原 Node 的 set 集合中移除,并添加到目标节点的集合中,更新哈希表;
若相邻节点的 cnt 不是目标值:则需要创建相应的目标节点,并构建双向链表关系,把 key 存入新创建的目标节点,更新哈希表。
若不存在(只能是 inc 操作):查找是否存在 cnt = 1cnt=1 的节点(也就是检查 hh.right 节点的计数值):
如果存在 cnt = 1cnt=1 的目标节点:将 key 添加到目标节点的 set 集合中,更新哈希表;
若不存在 cnt = 1cnt=1 的目标节点:创建相应的节点,并构建双向关系,并构建双向链表关系,把 key 存入新创建的目标节点,更新哈希表。
getMaxKey/getMinKey 操作:分别从 tt.left 和 hh.right 中尝试查找,如果存在非哨兵节点,则从节点的 set 集合中取任意元素进行返回,否则返回空串。
最后,为了确保 getMaxKey/getMinKey 操作能够严格 O(1)O(1),我们在进行 inc/dec 操作时我们需要对一些 set 容量为 00 的节点进行释放,即解除其所在双向链表的关系。
作者:AC_OIer
链接:https://leetcode-cn.com/problems/all-oone-data-structure/solution/by-ac_oier-t26d/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
struct Node {
string s;
int v;
Node *pre, *nxt;
Node(string s):s(s), v(0), pre(nullptr), nxt(nullptr) {};
};
class AllOne {
vector<pair<Node*, Node*>> mp1;
map<string, Node*> mp2;
Node *head, *tail;
public:
AllOne():mp1(50002) {
head = mp1[0].second = new Node("");
tail = mp1.back().first = new Node("");
head->nxt = tail;
tail->pre = head;
}
// 将 it 节点插入 tar 节点后面
void insert(Node *it, Node *tar) {
it->nxt = tar->nxt;
it->pre = tar;
it->nxt->pre = it->pre->nxt = it;
auto &[a, b] = mp1[it->v];
if (!a && !b) {
a = b = it;
} else {
a = it;
}
}
// 将 it 节点从双向链表中删除
void erase(Node *it) {
it->pre->nxt = it->nxt;
it->nxt->pre = it->pre;
auto &[a, b] = mp1[it->v];
if (a == it && b == it) {
a = b = nullptr;
} else if (a == it) {
a = it->nxt;
} else if (b == it) {
b = it->pre;
}
}
void inc(string key) {
Node *cur = mp2.count(key) ? mp2[key] : new Node(key);
if (cur->v) {
erase(cur);
cur->v++;
if (mp1[cur->v - 1].second) {
insert(cur, mp1[cur->v - 1].second);
} else {
insert(cur, cur->pre);
}
} else {
mp2[key] = cur;
cur->v++;
insert(cur, head);
}
}
void dec(string key) {
Node *cur = mp2[key];
erase(cur);
if (cur->v == 1) {
mp2.extract(key);
delete cur;
} else {
cur->v--;
if (mp1[cur->v].first) {
insert(cur, mp1[cur->v].first->pre);
} else {
insert(cur, cur->pre);
}
}
}
string getMaxKey() {
return tail->pre->s;
}
string getMinKey() {
return head->nxt->s;
}
};