leetcode-全 O(1) 的数据结构

196 阅读3分钟

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();
 */