返回

巧妙应对前中后!Leetcode 1670 设计三合一数据结构

前端

揭秘 Leetcode 1670:掌握前中后三合一数据结构

探索数据结构的迷人世界

在数据结构的浩瀚世界中,探索各种巧妙的数据组织方式是一项令人着迷的追求。Leetcode 1670 题正是这样一道引人入胜的难题,它要求我们设计一种独特的数据结构——前中后三合一数据结构。这种结构支持在前、中、后三个位置进行 push 和 pop 操作,堪称对数据结构能力的全面考验。

认识三合一数据结构

Leetcode 1670 题要求我们设计一个名为 FrontMiddleBackQueue 的特殊数据结构,它必须同时支持以下操作:

  • PushFront(val):将一个元素推入队首。
  • PushMiddle(val):将一个元素推入队中间。
  • PushBack(val):将一个元素推入队尾。
  • PopFront():从队首弹出一​​个元素。
  • PopMiddle():从队中间弹出一​​个元素。
  • PopBack():从队尾弹出一​​个元素。

巧妙的设计

设计这种三合一数据结构的关键在于平衡三个不同位置的 push 和 pop 操作。一种简单的方法是使用三个单独的栈来分别表示队首、队中和队尾。然而,这种方法在 PushMiddle 操作时会面临效率问题,因为我们需要将队尾元素依次出栈,才能插入新元素。

为了解决这个效率问题,我们采用了一种更巧妙的方法,即使用两个循环双向列表来表示队首和队尾,以及一个指针来指示队中间的位置。这样一来,PushMiddle 操作可以高效地插入新元素,而不会影响其他元素的相对位置。

代码实现

class FrontMiddleBackQueue:

    def __init__(self):
        self.left = DoublyLinkedList()  # 队首循环双向列表
        self.right = DoublyLinkedList()  # 队尾循环双向列表
        self.mid = None  # 队中间的指针

    def pushFront(self, val):
        self.left.add_first(val)
        self._adjust_mid()

    def pushMiddle(self, val):
        if self.left.is_empty():
            self.left.add_first(val)
        else:
            self.mid = self.mid or self.left.head
            self.mid = self.mid.next
            self.left.add_before(val, self.mid)

    def pushBack(self, val):
        self.right.add_last(val)
        if self.mid is None:
            self.mid = self.right.head

    def popFront(self):
        if self.left.is_empty():
            return None
        val = self.left.pop_first()
        self._adjust_mid()
        return val

    def popMiddle(self):
        if self.mid is None:
            return None
        val = self.mid.val
        self.mid = self.mid.next if self.mid.next is not None else self.mid.prev
        if self.mid is None:
            self.left = DoublyLinkedList()
            self.right = DoublyLinkedList()
        return val

    def popBack(self):
        if self.right.is_empty():
            return None
        val = self.right.pop_last()
        if self.mid == self.right.tail:
            self.mid = self.mid.prev
        return val

    def _adjust_mid(self):
        if self.mid is not None and self.left.len() == self.right.len():
            self.mid = self.mid.next


# 循环双向列表
class Node:
    def __init__(self, val):
        self.val = val
        self.prev = None
        self.next = None


class DoublyLinkedList:

    def __init__(self):
        self.head = None
        self.tail = None
        self.len = 0

    def is_empty(self):
        return self.head is None

    def add_first(self, val):
        new_node = Node(val)
        if self.is_empty():
            self.head = new_node
            self.tail = new_node
        else:
            new_node.next = self.head
            self.head.prev = new_node
            self.head = new_node
        self.len += 1

    def add_last(self, val):
        new_node = Node(val)
        if self.is_empty():
            self.head = new_node
            self.tail = new_node
        else:
            new_node.prev = self.tail
            self.tail.next = new_node
            self.tail = new_node
        self.len += 1

    def add_before(self, val, node):
        if node is self.head:
            self.add_first(val)
            return
        new_node = Node(val)
        new_node.next = node
        new_node.prev = node.prev
        node.prev.next = new_node
        node.prev = new_node
        if new_node.prev is None:
            self.head = new_node
        self.len += 1

    def pop_first(self):
        if self.is_empty():
            return None
        val = self.head.val
        self.head = self.head.next
        if self.head is None:
            self.tail = None
        else:
            self.head.prev = None
        self.len -= 1
        return val

    def pop_last(self):
        if self.is_empty():
            return None
        val = self.tail.val
        self.tail = self.tail.prev
        if self.tail is None:
            self.head = None
        else:
            self.tail.next = None
        self.len -= 1
        return val

应用场景

三合一数据结构在实际应用中具有丰富的场景,例如:

  • 实现一个调度算法,同时考虑前、中、后三个位置的请求。
  • 管理一个缓存系统,按最久未使用(LRU)原则从前、中、后位置淘汰数据。
  • 设计一个播放列表,允许用户从列表的前、中、后位置添加和删除曲目。

总结

Leetcode 1670 题的解决过程不仅锻炼了我们的数据结构设计能力,更激发了我们对算法效率的思考。通过巧妙地使用循环双向列表,我们实现了对前中后位置 push 和 pop 操作的高效支持。这种三合一数据结构在实际应用中具有广阔的前景,等待着我们的进一步探索和创新。

常见问题解答

  1. 为什么要使用循环双向列表而不是数组?

    • 循环双向列表可以高效地从中间位置插入或删除元素,而无需移动其他元素,而数组则需要移动大量元素来保持顺序。
  2. 如何平衡三个位置的 push 和 pop 操作?

    • 通过使用两个循环双向列表和一个中间指针,我们可以在保持 push 和 pop 操作高效的同时,平衡三个位置的元素。
  3. 三合一数据结构在哪些实际场景中有应用?

    • 调度算法、缓存系统、播放列表等。
  4. 三合一数据结构与普通队列或栈有何区别?

    • 三合一数据结构支持在前、中、后三个位置进行 push 和 pop 操作,而普通队列或栈只能在队首或栈顶进行操作。
  5. 设计三合一数据结构时需要注意哪些关键点?

    • 高效的 push 和 pop 操作、平衡三个位置的元素、可扩展性。