LeetCode 432.全O(1)的数据结构(哈希表+手写双向链表)


题目描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

哈希表+手写双向链表

首先,确定好合适的数据结构:双向链表和哈希表(和LeetCode 460. LFU缓存类似的思路,自定义节点并手写双向链表来实现)

  • 链表中的每个节点存储一个字符串集合 keys \textit{keys} keys一个正整数 cnt \textit{cnt} cnt,表示 keys \textit{keys} keys 中的字符串均出现 cnt \textit{cnt} cnt 次。链表从头到尾的每个节点的 cnt \textit{cnt} cnt 值单调递增(但不一定连续)。此外,每个节点还需存储指向上一个节点的指针 prev \textit{prev} prev 和指向下一个节点的指针 next \textit{next} next

  • 用一个哈希表 nodes \textit{nodes} nodes 维护每个字符串当前所处的链表节点。以字符串为哈希表的键,字符串所在 Node 作为值

其次,定义好数据结构后,对于每一个函数操作分情况讨论即可:

  • 对于 inc 函数:
    • key 不在链表中:
      • 若链表为空或头节点的 c n t > 1 cnt>1 cnt>1,则先插入一个 cnt = 1 \textit{cnt}=1 cnt=1 的新节点至链表头部,然后将 key \textit{key} key 插入到新加的头节点的 keys \textit{keys} keys
      • 否则,直接将key放入root.next.keys中,并更新 nodeskey所在的节点
    • key 在链表中:设 key 所在节点为 cur
      • cur.next 为空或 cur . next . cnt > cur . cnt + 1 \textit{cur}.\textit{next}.\textit{cnt}>\textit{cur}.\textit{cnt}+1 cur.next.cnt>cur.cnt+1,则先插入一个 cnt = cur . cnt + 1 \textit{cnt}=\textit{cur}.\textit{cnt}+1 cnt=cur.cnt+1 的新节点至 cur \textit{cur} cur 之后,然后将 key \textit{key} key 插入到 cur.next.keys
      • 否则,直接将key放入cur.next.keys中,并更新 nodeskey所在的节点
      • 最后,将 keycur.keys 中移除,若移除后 cur.keys 为空,则将 cur从链表中移除。
    • 更新nodeskey所处的节点
  • 对于 dec 函数,测试用例保证 key 在链表中:
    • key 仅出现一次:将其从 nodes中移除
    • key 出现不止一次:设 key 所在节点为 cur
      • cur.prev 为空或 cur . prev . cnt < cur . cnt − 1 \textit{cur}.\textit{prev}.\textit{cnt}<\textit{cur}.\textit{cnt}-1 cur.prev.cnt<cur.cnt1,则先插入一个 cnt = cur . cnt − 1 \textit{cnt}=\textit{cur}.\textit{cnt}-1 cnt=cur.cnt1 的新节点至 cur之前,然后将 key插入到新加节点的keys
      • 否则,直接将key放入cur.prev.keys中,并更新 nodeskey所在的节点
    • 最后,将 keycur.keys 中移除,若移除后 cur.keys 为空,则将 cur 从链表中移除。
  • 对于 getMaxKey 函数:
    • 在链表不为空时,返回链表尾节点的 keys 中的任一元素,
    • 否则返回空字符串
  • 对于 getMinKey 函数:
    • 在链表不为空时,返回链表头节点的 keys 中的任一元素,
    • 否则返回空字符串

整体的存储结构示意图:

在这里插入图片描述

class AllOne {

    // 双向链表节点
    class Node {
        Node prev;
        Node next;
        Set<String> keys;
        int cnt;

        public Node() {
            this("", 0);
        }
        public Node(String key, int cnt) {
            this.cnt =cnt;
            keys = new HashSet<>();
            keys.add(key);
        }

        // 在 this 后插入节点 node 并返回节点 node 
        public Node insert(Node node) {
            // 插入 node
            node.prev = this;
            node.next = this.next;
            // 修改原节点指向
            node.prev.next = node;
            node.next.prev = node;
            return node;
        }

        // 在链表中删除 this 节点
        public void remove() {
            this.prev.next = this.next;
            this.next.prev = this.prev;
        }
    }

    // 以字符串为哈希表的键,字符串所在 Node 作为值
    Map<String, Node> nodes;

    // 哨兵节点
    Node root;
    
    public AllOne() {
        root = new Node();
        // 初始化链表哨兵,下面判断节点的 next 若为 root,则表示 next 为空(prev 同理)
        root.prev = root;
        root.next = root;

        nodes = new HashMap<>();
    }
    
    public void inc(String key) {
        // 分情况讨论
        // key 在链表中
        if (nodes.containsKey(key)) {
            Node cur = nodes.get(key);
            Node nxt = cur.next;
            if (nxt == root || nxt.cnt > cur.cnt + 1) {
                // 在 cur 节点后插入新节点
                nodes.put(key, cur.insert(new Node(key, cur.cnt + 1)));
            } else {
                // 将 key 放入 cur.next 节点
                nxt.keys.add(key);
                // 更新 map 中 key 所在的节点
                nodes.put(key, nxt);
            }
            // 移除 cur 节点中的 key
            cur.keys.remove(key);
            if (cur.keys.isEmpty()) {
                // 移除链表中的 cur 节点
                cur.remove();
            }
        } else {   // key 不在链表中 头一次插入
            if (root.next == root || root.next.cnt > 1) {
                // 链表从头到尾的每个节点的 cnt 值单调递增(但不一定连续)
                // 在 root 节点后插入新节点
                nodes.put(key, root.insert(new Node(key, 1)));
            } else {
                // 将 key 放入 root.next 节点
                root.next.keys.add(key);
                // 更新 map 中 key 所在的节点
                nodes.put(key, root.next);
            }
        }
    }
    
    public void dec(String key) {
        Node cur = nodes.get(key);
        // 分情况讨论        
        if (cur.cnt == 1) {
            // key 仅出现一次,将其移出 nodes
            nodes.remove(key);
        } else {
            Node pre = cur.prev;
            if (pre == root || pre.cnt < cur.cnt - 1) {
                // 在 cur.prev 节点后插入新节点
                nodes.put(key, cur.prev.insert(new Node(key, cur.cnt - 1)));
            } else {
                // 将 key 放入 cur.prev 节点
                pre.keys.add(key);
                // 更新 map 中 key 所在的节点
                nodes.put(key, pre);
            }
        }
        // 移除 cur 节点中的 key
        cur.keys.remove(key);
        if (cur.keys.isEmpty()) {
            // 移除链表中的 cur 节点
            cur.remove();
        }
    }
    
    public String getMaxKey() {
        Node node = root.prev;
        for (String str : node.keys) {
            // 若链表不为空,返回 keys 中任一元素
            return str;
        }
        return "";
    }
    
    public String getMinKey() {
        Node node = root.next;
        for (String str : node.keys) {
            return str;
        }
        return "";
    }
}

/**
 * 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();
 */
  • 时间复杂度:所有操作均为 O ( 1 ) O(1) O(1),这里将字符串长度视作常数

  • 空间复杂度: O ( I ) O(I) O(I),其中 I I I 是调用 inc 的次数。最坏情况下每次调用 inc 传入的字符串均不相同,需要 O ( I ) O(I) O(I)大小的哈希表来存储所有字符串

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xylitolz

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值