返回

【全 O(1) 的数据结构,实现字符串计数】

后端

全 O(1) 数据结构:史上最强 LeetCode 难题征服指南

数据结构的奥妙

在计算机科学的浩瀚世界中,数据结构如同魔法般的存在,为我们提供了高效管理和存储数据的工具。今天,我们将踏上一次激动人心的探险之旅,深入浅出地解析一道广受好评的 LeetCode 难题——432. 全 O(1) 的数据结构。

O(1) 的魔力

当你听到 "O(1)" 这个术语时,是否有一种眼前一亮的感觉?没错,O(1) 的时间复杂度意味着你的操作速度不受数据规模大小的影响,它将始终以恒定的时间完成任务。这种极致的效率,怎能不令人激动!

题目简介

现在,让我们揭开这道 LeetCode 难题的神秘面纱:

题目:


* 设计一个用于存储字符串计数的数据结构,并能够返回计数最小和最大的字符串。
  • 方法:
    • 实现 AllOne 类:
      • AllOne():初始化数据结构。
      • inc(String key):字符串 key 的计数增加 1。如果不存在该字符串,则先将其插入,计数设置为 1。
      • dec(String key):字符串 key 的计数减少 1。如果 key 的计数为 1,则将其从数据结构中删除。
      • getMaxKey():返回数据结构中具有最大计数的字符串。如果没有元素,返回一个空字符串。
      • getMinKey():返回数据结构中具有最小计数的字符串。如果没有元素,返回一个空字符串。

数据结构设计

为了满足题目要求,我们需要设计一种强大的数据结构。经过缜密的思考,我们决定采用双向链表和哈希表这两种利器。

  • 双向链表:

    • 用于存储字符串,并将相同计数的字符串放在同一个节点中。
    • 每个节点包含一个字符串和一个计数器。
    • 双向链表中的节点可以快速地插入、删除和查找。
  • 哈希表:

    • 用于存储字符串和对应的节点。
    • 这样,我们可以通过 O(1) 的时间复杂度查找节点。

实现思路

有了数据结构的设计方案,我们就可以构建我们的算法了:

  • 增加计数(inc):

    • 先在哈希表中查找字符串对应的节点。
    • 如果找到,则将节点的计数器加 1。
    • 如果找不到,则创建一个新的节点并将其插入双向链表和哈希表中。
  • 减少计数(dec):

    • 先在哈希表中查找字符串对应的节点。
    • 如果找到,则将节点的计数器减 1。
    • 如果计数器为 0,则将节点从双向链表和哈希表中删除。
  • 获取最大计数(getMaxKey):

    • 返回双向链表中计数最大的节点的字符串。
  • 获取最小计数(getMinKey):

    • 返回双向链表中计数最小的节点的字符串。

代码实现

为了让大家更直观地理解算法,我们提供了 Python 代码实现:

class AllOne:
    def __init__(self):
        self.head = Node()  # 头节点
        self.tail = Node()  # 尾节点
        self.head.next = self.tail
        self.tail.prev = self.head

        # 使用哈希表来存储字符串和对应的节点
        self.hash = {}

    def inc(self, key):
        # 先在哈希表中查找字符串对应的节点
        if key in self.hash:
            node = self.hash[key]
        # 如果找不到,则创建一个新的节点并将其插入双向链表和哈希表中
        else:
            node = Node(key, 1)
            self._insert_node(node)
            self.hash[key] = node

        # 将节点的计数器加 1
        node.count += 1

        # 如果节点的计数器大于下一个节点的计数器,则将节点移到下一个节点的前面
        while node.next.count <= node.count:
            node = self._move_node_forward(node)

    def dec(self, key):
        # 先在哈希表中查找字符串对应的节点
        node = self.hash[key]

        # 将节点的计数器减 1
        node.count -= 1

        # 如果节点的计数器为 0,则将节点从双向链表和哈希表中删除
        if node.count == 0:
            self._remove_node(node)
            del self.hash[key]

        # 如果节点的计数器小于前一个节点的计数器,则将节点移到前一个节点的后面
        while node.prev.count >= node.count:
            node = self._move_node_backward(node)

    def getMaxKey(self):
        # 返回双向链表中计数最大的节点的字符串
        return self.tail.prev.key if self.tail.prev != self.head else ""

    def getMinKey(self):
        # 返回双向链表中计数最小的节点的字符串
        return self.head.next.key if self.head.next != self.tail else ""

    def _insert_node(self, node):
        # 将节点插入到双向链表中
        node.next = self.tail
        node.prev = self.tail.prev
        self.tail.prev.next = node
        self.tail.prev = node

    def _remove_node(self, node):
        # 将节点从双向链表中删除
        node.prev.next = node.next
        node.next.prev = node.prev

    def _move_node_forward(self, node):
        # 将节点移到下一个节点的前面
        node.next = node.next.next
        node.next.prev = node
        node.prev.next = node
        node.prev = node.prev.prev
        return node

    def _move_node_backward(self, node):
        # 将节点移到前一个节点的后面
        node.prev = node.prev.prev
        node.prev.next = node
        node.next.prev = node
        node.next = node.next.next
        return node

class Node:
    def __init__(self, key=None, count=0):
        self.key = key
        self.count = count
        self.next = None
        self.prev = None

总结

通过精心设计的双向链表和哈希表数据结构,我们成功地实现了一个能够在 O(1) 时间复杂度内完成所有操作的数据结构。这个巧妙的算法为我们提供了高效管理字符串计数并轻松获取最大和最小计数字符串的工具。

常见问题解答

  1. 为什么使用双向链表而不是单向链表?

    • 双向链表可以方便地向前和向后移动节点,从而可以快速地调整节点的位置,以保持计数有序。
  2. 哈希表在算法中扮演什么角色?

    • 哈希表可以快速地查找字符串对应的节点,从而可以高效地执行增加和减少计数操作。
  3. 算法如何处理重复的字符串?

    • 如果存在重复的字符串,算法将把它们放在同一个节点中,并累加它们的计数。
  4. 这个算法的实际应用场景有哪些?

    • 这个算法可以用于各种需要存储和快速查找计数数据的场景,例如缓存、频率计数和会话管理。
  5. 这个算法有哪些局限性?

    • 如果存储的字符串数量非常庞大,算法在内存消耗方面可能会受到影响。