Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务,点击查看活动详情。
题目描述
请你设计一个用于存储字符串计数的数据结构,并能够返回计数最小和最大的字符串。
实现 AllOne 类:
- AllOne() 初始化数据结构的对象。
- inc(String key) 字符串 key 的计数增加 1 。如果数据结构中尚不存在 key ,那么插入计数为 1 的 key 。
- dec(String key) 字符串 key 的计数减少 1 。如果 key 的计数在减少后为 0 ,那么需要将这个 key 从数据结构中删除。测试用例保证:在减少计数前,key 存在于数据结构中。
- getMaxKey() 返回任意一个计数最大的字符串。如果没有元素存在,返回一个空字符串 "" 。
- getMinKey() 返回任意一个计数最小的字符串。如果没有元素存在,返回一个空字符串 "" 。
示例 1:
输入:["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"
思路分析
数据结构的题目,之前遇到的也不多,今天刚好每日一题碰上了。对于前面2个条件,inc和dec要在O(1)时间范围内获取,很容易就想到Map,可以满足。但是对于getMaxKey和getMinKey,map就需要遍历了,无法做到O(1),所以除了Map,我们肯定需要附加其他的数据结构。这里我们另外引入双向链表,链表按照key的值从小到大排,因为inc和dec每次都只变化1,所以链表交换元素的个数是有限的,也可以满足O(1)。
有了上面的思路,接下来就是具体实现:
- 定义Node,因为是双向链表,所以属性必须包含前驱pre和后继next,根据题意,还需要引入key和count
- 定义头结点head和尾节点tail
- 定义Map,value为Node
inc
判断map中是否包含,分2种情况
- 包含 node.count++,并且跟后继节点count比较,如果自身count更大,做交换,直到自身count小于等于后继节点count或者已经到链表末尾,没有后继节点
- 不包含 创建node节点,并把node节点插入在链表头部。为什么可以直接插入在头部?因为链表中不可能存在count=0的节点,按照题意这种节点需要从链表删除。
dec
跟inc的做法是类似的。由于题目保证了“在减少计数前,key 存在于数据结构中”,判断起来还更加简单。只不过这次不同的是,如果count减到0,需要删除这个节点,否则就是不断跟前驱节点比较和交换。
getMaxKey
返回tail节点
getMinKey
返回head节点
Java版本代码
class AllOne {
Map<String, Node> map;
Node head;
Node tail;
public AllOne() {
map = new HashMap<>();
head = null;
tail = null;
}
public void inc(String key) {
if (map.containsKey(key)) {
Node node = map.get(key);
node.count++;
while (node.next != null && node.count > node.next.count) {
swap(node, node.next);
}
} else {
Node node = new Node(null, null, key, 1);
map.put(key, node);
if (head == null) {
head = tail = node;
return;
}
// 插入在头部
head.pre = node;
node.next = head;
head = node;
}
}
public void dec(String key) {
// 题意中已经保证key肯定存在,不做额外判断
Node node = map.get(key);
node.count--;
if (node.count == 0) {
// 需要删除节点
Node pre = node.pre;
Node next = node.next;
if (pre != null) {
pre.next = next;
}
if (next != null) {
next.pre = pre;
}
if (head == node) {
head = next;
}
if (tail == node) {
tail = pre;
}
map.remove(key);
} else {
// 需要向左移动节点
while (node.pre != null && node.count < node.pre.count) {
swap(node.pre, node);
}
}
}
public String getMaxKey() {
if (tail != null) {
return tail.key;
}
return "";
}
public String getMinKey() {
if (head != null) {
return head.key;
}
return "";
}
private void swap(Node left, Node right) {
Node leftPre = left.pre;
Node rightNext = right.next;
right.pre = leftPre;
right.next = left;
left.pre = right;
left.next = rightNext;
if (leftPre != null) {
leftPre.next = right;
}
if (rightNext != null) {
rightNext.pre = left;
}
if (head == left) {
head = right;
}
if (tail == right) {
tail = left;
}
}
class Node {
public Node(Node pre, Node next, String key, int count) {
this.pre = pre;
this.next = next;
this.key = key;
this.count = count;
}
Node pre;
Node next;
String key;
int count;
}
}
/**
* Your AllOne object will be instantiated and called as such:
* AllOne obj = new AllOne();
* obj.inc(key);
* obj.dec(key);
* String param_3 = obj.getMaxKey();
* String param_4 = obj.getMinKey();
*/