“Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务,点击查看活动详情。”
一、题目描述:
432. 全 O(1) 的数据结构
请你设计一个用于存储字符串计数的数据结构,并能够返回计数最小和最大的字符串。
实现 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 次
二、思路分析:
哈希表+双向链表
我们再加入或减少对应字符串的出现次数count时,首先是要知道操作前的count值,因此我们肯定要用到哈希表存储,键值为字符串key
又因为字符串对应的key值是变化的,与之前做的最小栈之类题目输入的固定数值不同,为了维持值变化后任能方便获取最大最小值,我们可以使用双向链表或者最大、最小堆。这里我选择双向链表
创建双向链表类Node,我们希望用它存储字符串和对应的count值,可以通过Node对象方便获取对应的值和字符串。使用node对象组成双向链表,按字符串出现次数升序排列,pre属性指向前一个node对象,next属性指向下一个node对象。
同时进行inc操作时,插入新的node对象是必要的操作,我们编写insert(node2)方法,方便对象node后面插入node2对象。同时dec操作时要删除count为0的值,remove方法也是必要的。
接着我们发现如果只是单纯的插入和删除,不可避免的要判断插入时下一个结点是否为null,删除时前一个结点是否为null,很繁琐。故我们使用root和tail来表示头结点和尾结点。同时我们发现,使用他们还能快捷的知道最大值——tail.pre和最小值——root.next。
node类如下
class Node{
constructor(key,count){
this.count=count||0
this.key=key||''
this.pre=null
this.next=null
}
insert(node){
node.pre=this
node.next=this.next
this.next.pre=node
this.next=node
}
remove(){
this.pre.next=this.next
this.next.pre=this.pre
}
}
接下来我们逐步分析对应四个操作
- inc操作
- 若插入key字符串,不存在在哈希表中,则创建node对象count为1,加入哈希表map,root头结点insert该node对象
- 哈希表中存在,若只有该节点或者已经按升序排列的链表中该节点的下一个结点next的count值大于或者等于count+1,该节点的node对象count直接加一即可
- 否则,我们要找到第一个count值大于或者等于count+1的结点next或者尾结点,在next后面insert新的count+1的node对象,调用remove方法删除旧的node对象,同时更新哈希表key对应的node对象
/**
* @param {string} key
* @return {void}
*/
AllOne.prototype.inc = function(key) {
let [root,map]=[this.root,this.map]
if(map.has(key)){
let node=map.get(key)
let next=node.next
if(next===this.tail||next.count>=node.count+1){
node.count++
}else{
while(next.next!==this.tail&&next.next.count<node.count+1){
next=next.next
}
next.insert(new Node(key,node.count+1))
node.remove()
map.set(key,next.next)
}
}else{
let node=new Node(key,1)
map.set(key,node)
root.insert(node)
}
};
- dec操作
- 只有该节点时,直接node.count--,若为0删除结点
- node.count===1,删除该节点
- 前一个结点pre.count<=node.count-1,直接node.count--,若为0删除结点
- 否则,node.count值肯定是>=2的,找到第一个小于node.count的结点pre或者头结点,在pre后面insert新的count-1的node对象,调用remove方法删除旧的node对象,同时更新哈希表key对应的node对象 代码如下
AllOne.prototype.dec = function(key) {
let [root,map]=[this.root,this.map]
if(map.has(key)){
let node=map.get(key)
let pre=node.pre
if(pre===root||node.count===1||pre.count<=node.count-1){
node.count--
if(node.count===0){
node.remove()
map.delete(key)
}
}else{
while(pre!==root&&pre.count>node.count-1){
pre=pre.pre
}
pre.insert(new Node(key,--node.count))
node.remove()
map.set(key,pre.next)
}
}else{
console.log("未加入就删除")
}
};
-
getMaxKey 判断尾结点tail上一个是否为root,是则表示空链表,没有符合的值返回"",否则返回tail.pre.key
-
getMinKey 判断头结点root下一个是否为tail,是则表示空链表,没有符合的值返回"",否则返回root.next.key
AC 代码
class Node{
constructor(key,count){
this.count=count||0
this.key=key||''
this.pre=null
this.next=null
}
insert(node){
node.pre=this
node.next=this.next
this.next.pre=node
this.next=node
}
remove(){
this.pre.next=this.next
this.next.pre=this.pre
}
}
var AllOne = function() {
this.map=new Map()
this.root=new Node()
this.tail=new Node()
this.tail.pre=this.root
this.root.next=this.tail
};
/**
* @param {string} key
* @return {void}
*/
AllOne.prototype.inc = function(key) {
let [root,map]=[this.root,this.map]
if(map.has(key)){
let node=map.get(key)
let next=node.next
if(next===this.tail||next.count>=node.count+1){
node.count++
}else{
while(next.next!==this.tail&&next.next.count<node.count+1){
next=next.next
}
next.insert(new Node(key,node.count+1))
node.remove()
map.set(key,next.next)
}
}else{
let node=new Node(key,1)
map.set(key,node)
root.insert(node)
}
};
/**
* @param {string} key
* @return {void}
*/
AllOne.prototype.dec = function(key) {
let [root,map]=[this.root,this.map]
if(map.has(key)){
let node=map.get(key)
let pre=node.pre
if(pre===root||node.count===1||pre.count<=node.count-1){
node.count--
if(node.count===0){
node.remove()
map.delete(key)
}
}else{
while(pre!==root&&pre.count>node.count-1){
pre=pre.pre
}
pre.insert(new Node(key,--node.count))
node.remove()
map.set(key,pre.next)
}
}else{
console.log("未加入就删除")
}
};
/**
* @return {string}
*/
AllOne.prototype.getMaxKey = function() {
if(this.tail.pre!==this.root)
return this.tail.pre.key
else return ''
};
/**
* @return {string}
*/
AllOne.prototype.getMinKey = function() {
return this.root.next!==this.tail?this.root.next.key:''
};
/**
* Your AllOne object will be instantiated and called as such:
* var obj = new AllOne()
* obj.inc(key)
* obj.dec(key)
* var param_3 = obj.getMaxKey()
* var param_4 = obj.getMinKey()
*/