[英雄星球六月集训LeetCode解题日报] 第13日 双向链表
一、 432. 全 O(1) 的数据结构
1. 题目描述
请你设计一个用于存储字符串计数的数据结构,并能够返回计数最小和最大的字符串。
实现 AllOne 类:
AllOne() 初始化数据结构的对象。
inc(String key) 字符串 key 的计数增加 1 。如果数据结构中尚不存在 key ,那么插入计数为 1 的 key 。
dec(String key) 字符串 key 的计数减少 1 。如果 key 的计数在减少后为 0 ,那么需要将这个 key 从数据结构中删除。测试用例保证:在减少计数前,key 存在于数据结构中。
getMaxKey() 返回任意一个计数最大的字符串。如果没有元素存在,返回一个空字符串 “” 。
getMinKey() 返回任意一个计数最小的字符串。如果没有元素存在,返回一个空字符串 “” 。
注意:每个函数都应当满足 O(1) 平均时间复杂度。
2. 思路分析
这种模拟题就随便水水,过了之后再看看题解学习别人的奇思妙想。
我的实现是双向链表+两个哈希表:
-
一个哈希表储存
单词次数
; 另一个储存单词在链表的哪个节点
里,否则每次新加或删除已存在的key都要去链表遍历。 -
双向链表按词频顺序储存单词。
-
双向链表增加head和tail虚拟节点,便于编码。
-
初始化让head指向tail,同时当head指向tail时表示链表为空。
-
那么getMIN和getMAX非常好写,就是返回头尾节点即可。O(1)
-
然后实现在列表里添加和删除节点的方法。
-
增加单词时,如果是新词,词频必是1,创建一个新节点,插到链表头即可O(1);否则摘除本节点a,向后寻找O(n),找到第一个词频大于它的节点b,把a插到b前边。
-
删除单词时,如果词频为0直接删除O(1);否则摘除本节点a,向前寻找 O(n) 到第一个词频小于它的节点b,把a插到b后边。
-
过辣!☀️
看了题解,链表维护的是词频,每个节点有一个set储存当前词频的单词,这样向后寻找必只需要跳一次,才能做到全O(1).✈
后来按照题解实现了一次,又用sortedlist写了个 *O(lgn)*的,还是库函数厉害。
3. 代码实现
双向链表O(n)
class Double_list_node:
def __init__(self,val='',pre=None,next=None):
self.val,self.pre,self.next = val,pre,next
class AllOne:
def __init__(self):
self.cnt = defaultdict(int)
self.cursor = defaultdict(None)
self.head = Double_list_node()
self.tail = Double_list_node()
self.head.next = self.tail
self.tail.pre = self.head
def delete_node(self,cur):
cur.pre.next = cur.next
cur.next.pre = cur.pre
def add_node_back(self,where,target):
target.pre = where
target.next = where.next
target.pre.next = target
target.next.pre = target
def inc(self, key: str) -> None:
cnt = self.cnt
tail = self.tail
cnt[key] += 1
this_cnt = cnt[key]
if this_cnt == 1: # 新来的,创建节点,填到最前边即可
node = Double_list_node(key)
self.add_node_back(self.head,node)
self.cursor[key] = node
else: # 不是新来的,本节点摘出来,向后遍历到第一个计数大于它的位置,插到这个位置前边。
node = self.cursor[key]
cur = node.next
self.delete_node(node)
while cur != tail and cnt[cur.val] < this_cnt:
cur = cur.next
self.add_node_back(cur.pre,node)
def dec(self, key: str) -> None:
cnt = self.cnt
cursor = self.cursor
head = self.head
cnt[key] -= 1
this_cnt = cnt[key]
node = cursor[key]
if this_cnt == 0:
self.delete_node(node)
del cnt[key]
del cursor[key]
else:
cur = node.pre
self.delete_node(node)
while cur != head and cnt[cur.val] > this_cnt:
cur = cur.pre
self.add_node_back(cur,node)
def getMaxKey(self) -> str:
if self.head.next == self.tail:
return ''
return self.tail.pre.val
def getMinKey(self) -> str:
if self.head.next == self.tail:
return ''
return self.head.next.val
双向链表O(1)
class Double_list_node:
def __init__(self,count=0,s = set(),pre=None,next=None):
self.count,self.s,self.pre,self.next = count,s,pre,next
class AllOne:
def __init__(self):
self.cursor = defaultdict(None)
self.head = Double_list_node()
self.tail = Double_list_node()
self.head.next = self.tail
self.tail.pre = self.head
def delete_node(self,cur):
cur.pre.next = cur.next
cur.next.pre = cur.pre
def add_node_back(self,where,target):
target.pre = where
target.next = where.next
target.pre.next = target
target.next.pre = target
def inc(self, key: str) -> None:
cursor = self.cursor
head = self.head
tail = self.tail
node = cursor.get(key)
if not node: # key不存在,插到词频1节点里
if head.next == tail or head.next.count >1: # 没有节点,创建一个词频1的节点。
self.add_node_back(head,Double_list_node(1,{key}))
else:
head.next.s.add(key)
cursor[key] = head.next
else: # key存在,向右移动一位,如果没了或词频不止大1则创建
nxt = node.next
node.s.remove(key)
if nxt == tail or nxt.count > node.count + 1:
self.add_node_back(node,Double_list_node(node.count + 1,{key}))
else:
nxt.s.add(key)
if not node.s:
self.delete_node(node)
cursor[key] = node.next
def dec(self, key: str) -> None:
cursor = self.cursor
head = self.head
tail = self.tail
node = cursor.get(key)
# 题目保证key存在,就不判断node了
node.s.remove(key)
if node.count == 1: # 如果没了要删掉指针
del cursor[key]
else: # 向前移动一位,如果没了或词频不止小一则创建
pre = node.pre
if pre == head or pre.count < node.count -1:
self.add_node_back(pre,Double_list_node(node.count - 1,{key}))
else:
pre.s.add(key)
cursor[key] = node.pre
if not node.s:
self.delete_node(node)
def getMaxKey(self) -> str:
if self.head.next == self.tail:
return ''
for s in self.tail.pre.s:
return s
def getMinKey(self) -> str:
if self.head.next == self.tail:
return ''
for s in self.head.next.s:
return s
sortedlistO(lgn)
class Double_list_node:
def __init__(self,count=0,s = set(),pre=None,next=None):
self.count,self.s,self.pre,self.next = count,s,pre,next
class AllOne:
def __init__(self):
from sortedcontainers import SortedList
self.keys = SortedList()
self.cnt = defaultdict(int)
def inc(self, key: str) -> None:
keys = self.keys
cnt = self.cnt
cnt[key] += 1
if cnt[key] == 1:
keys.add((cnt[key],key))
else:
keys.remove((cnt[key]-1,key))
keys.add((cnt[key],key))
def dec(self, key: str) -> None:
keys = self.keys
cnt = self.cnt
keys.remove((cnt[key],key))
cnt[key] -= 1
if cnt[key] > 0:
keys.add((cnt[key],key))
else:
del cnt[key]
def getMaxKey(self) -> str:
if not self.keys:
return ''
return self.keys[-1][1]
def getMinKey(self) -> str:
if not self.keys:
return ''
return self.keys[0][1]