class Bucket:
    def __init__(self,cnt = 0):
        self.cnt = cnt
        self.keySet = set()
        self.pre = None 
        self.nxt = None 
        
    def _get_any_key(self):
        if len(self.keySet)!=0:
            res = self.keySet.pop()
            self.keySet.add(res)
            return res 
        else:
            return None
        
# max is on the tail while min is on the head 
class AllOne:

    def __init__(self):
        self.head = Bucket() # head maintains max val, tail maintains min val 
        self.tail = Bucket()
        self.cnt_bucket_map = {}
        self.key_cnt_map = {}
        self.head.nxt = self.tail
        self.tail.pre = self.head
        
    def _change_key(self,key,offset):
        precnt = self.key_cnt_map[key]
        # update key_cnt map 
        self.key_cnt_map[key] =  precnt + offset
        cur_bucket = self.cnt_bucket_map[precnt]
        # update bucket 
        newcnt = precnt + offset
        
        # check if new bucket in bucket list 
        if newcnt in self.cnt_bucket_map:
            new_bucket = self.cnt_bucket_map[newcnt]
        else:
            new_bucket = Bucket(newcnt)
            # update cnt_bucket map 
            self.cnt_bucket_map[newcnt] = new_bucket
            if offset == 1: 
                self._insert_after(cur_bucket,new_bucket)
            else:
                self._insert_after(cur_bucket.pre,new_bucket)
        # add key to new bucket  
        new_bucket.keySet.add(key)
       # print(f"cnt is {new_bucket.cnt} set is {new_bucket.keySet}")
        # remove key from old bucket 
        self._remove_from_bucket(cur_bucket,key)
        #print(f" remove  {key} from bucket {cur_bucket.cnt}")
       # print(f" after remove  {key} from bucket {cur_bucket.cnt}, bucket {cur_bucket.cnt} contains {cur_bucket.keySet}")

    def inc(self, key: str) -> None:
        precnt = self.key_cnt_map.get(key,-1)
        if precnt != -1: #seen before 
            self._change_key(key,1)
        else: # not seen 
            self.key_cnt_map[key] = 1
            if self.head.nxt.cnt != 1: # min bucket bigger than 1 
                self._insert_after(self.head,Bucket(1))
            self.head.nxt.keySet.add(key)
            self.cnt_bucket_map[1] = self.head.nxt
        
    def dec(self, key: str) -> None:
        if key in self.key_cnt_map:
            cnt = self.key_cnt_map[key]
            bucket = self.cnt_bucket_map[cnt]
            if cnt == 1:
                self.key_cnt_map.pop(key)
                self._remove_from_bucket(bucket,key)
            else:
                self._change_key(key,-1)
            
        
    def getMaxKey(self) -> str:
        return self.tail.pre._get_any_key() if self.tail.pre != self.head else ''
        

    def getMinKey(self) -> str:
        #print(self.head.nxt.keySet)
        return self.head.nxt._get_any_key() if self.head.nxt != self.tail else ''
    
    
    def _remove_from_bucket(self,pre_bucket,key):
        pre_bucket.keySet.remove(key)
        if len(pre_bucket.keySet) == 0:
            self.cnt_bucket_map.pop(pre_bucket.cnt)
            self._remove_bucket_from_list(pre_bucket)
            
    def _remove_bucket_from_list(self,bucket):
        bucket.pre.nxt = bucket.nxt
        bucket.nxt.pre = bucket.pre
        bucket.pre = None
        bucket.nxt = None 
        
    def _insert_after(self,pre_node,new_node):
        new_node.pre = pre_node  # new_node.pre may run into NoneType object if 
        new_node.nxt = pre_node.nxt
        pre_node.nxt.pre = new_node #if new_node.nxt else None 
        pre_node.nxt = new_node
        
    # def _insert_before(self,pre_node,new_node):
    #     new_node.pre = pre_node.pre
    #     new_node.nxt = pre_node
    #     pre_node.pre.nxt = new_node
    #     pre_node.pre = new_node 
# Your AllOne object will be instantiated and called as such:
# obj = AllOne()
# obj.inc(key)
# obj.dec(key)
# param_3 = obj.getMaxKey()
# param_4 = obj.getMinKey()