Skip to main content

Command Palette

Search for a command to run...

Solving Leetcode Interviews in Seconds with AI: Design Front Middle Back Queue

Updated
4 min read

Introduction

In this blog post, we will explore how to solve the LeetCode problem "1670" using AI. LeetCode is a popular platform for preparing for coding interviews, and with the help of AI tools like Chatmagic, we can generate solutions quickly and efficiently - helping you pass the interviews and get the job offer without having to study for months.

Problem Statement

Design a queue that supports push and pop operations in the front, middle, and back. Implement the FrontMiddleBack class: FrontMiddleBack() Initializes the queue. void pushFront(int val) Adds val to the front of the queue. void pushMiddle(int val) Adds val to the middle of the queue. void pushBack(int val) Adds val to the back of the queue. int popFront() Removes the front element of the queue and returns it. If the queue is empty, return -1. int popMiddle() Removes the middle element of the queue and returns it. If the queue is empty, return -1. int popBack() Removes the back element of the queue and returns it. If the queue is empty, return -1. Notice that when there are two middle position choices, the operation is performed on the frontmost middle position choice. For example: Pushing 6 into the middle of [1, 2, 3, 4, 5] results in [1, 2, 6, 3, 4, 5]. Popping the middle from [1, 2, 3, 4, 5, 6] returns 3 and results in [1, 2, 4, 5, 6]. Example 1: Input: ["FrontMiddleBackQueue", "pushFront", "pushBack", "pushMiddle", "pushMiddle", "popFront", "popMiddle", "popMiddle", "popBack", "popFront"] [[], [1], [2], [3], [4], [], [], [], [], []] Output: [null, null, null, null, null, 1, 3, 4, 2, -1] Explanation: FrontMiddleBackQueue q = new FrontMiddleBackQueue(); q.pushFront(1); // [1] q.pushBack(2); // [1, 2] q.pushMiddle(3); // [1, 3, 2] q.pushMiddle(4); // [1, 4, 3, 2] q.popFront(); // return 1 -> [4, 3, 2] q.popMiddle(); // return 3 -> [4, 2] q.popMiddle(); // return 4 -> [2] q.popBack(); // return 2 -> [] q.popFront(); // return -1 -> [] (The queue is empty) Constraints: 1 <= val <= 109 At most 1000 calls will be made to pushFront, pushMiddle, pushBack, popFront, popMiddle, and popBack.

Explanation

Here's a breakdown of the solution and the Python code:

  • Approach:

    • Maintain two doubly linked lists: left and right. The left list stores the first half (or slightly more) elements of the queue, and the right list stores the remaining elements.
    • After each operation, balance the sizes of left and right to ensure the "middle" is correctly positioned for pushMiddle and popMiddle. Specifically, len(left) should either be equal to len(right) or len(right) + 1.
    • Doubly linked lists facilitate O(1) insertion and deletion at any point, essential for efficiency.
  • Complexity:

    • Runtime: O(1) for all operations.
    • Space: O(N), where N is the number of elements in the queue.

Code

    class Node:
    def __init__(self, val):
        self.val = val
        self.next = None
        self.prev = None

class DoublyLinkedList:
    def __init__(self):
        self.head = None
        self.tail = None
        self.size = 0

    def append(self, val):
        new_node = Node(val)
        if not self.head:
            self.head = new_node
            self.tail = new_node
        else:
            new_node.prev = self.tail
            self.tail.next = new_node
            self.tail = new_node
        self.size += 1

    def prepend(self, val):
        new_node = Node(val)
        if not self.head:
            self.head = new_node
            self.tail = new_node
        else:
            new_node.next = self.head
            self.head.prev = new_node
            self.head = new_node
        self.size += 1

    def insert_middle(self, val):
        if not self.head:
            self.append(val)
            return

        mid_index = (self.size + 1) // 2  # Integer division gives the correct middle
        if mid_index == 1:
            self.prepend(val)
            return

        current = self.head
        for _ in range(mid_index - 1):
            current = current.next

        new_node = Node(val)
        new_node.next = current
        new_node.prev = current.prev

        if current.prev:
            current.prev.next = new_node
        else:
            self.head = new_node

        current.prev = new_node
        self.size += 1

    def pop_front(self):
        if not self.head:
            return -1

        val = self.head.val
        if self.head == self.tail:
            self.head = None
            self.tail = None
        else:
            self.head = self.head.next
            self.head.prev = None
        self.size -= 1
        return val

    def pop_back(self):
        if not self.head:
            return -1

        val = self.tail.val
        if self.head == self.tail:
            self.head = None
            self.tail = None
        else:
            self.tail = self.tail.prev
            self.tail.next = None
        self.size -= 1
        return val

    def pop_middle(self):
        if not self.head:
            return -1

        mid_index = (self.size + 1) // 2 #Integer division as required

        if self.size == 1:
            val = self.head.val
            self.head = None
            self.tail = None
            self.size = 0
            return val

        current = self.head
        for _ in range(mid_index-1):
            current = current.next

        val = current.val

        if current.prev:
            current.prev.next = current.next
        else:
            self.head = current.next

        if current.next:
            current.next.prev = current.prev
        else:
            self.tail = current.prev

        self.size -= 1
        return val

    def get_size(self):
        return self.size


class FrontMiddleBackQueue:

    def __init__(self):
        self.left = DoublyLinkedList()
        self.right = DoublyLinkedList()

    def pushFront(self, val: int) -> None:
        self.left.prepend(val)
        self.balance()

    def pushMiddle(self, val: int) -> None:
        if self.left.get_size() == self.right.get_size():
            self.left.append(val)
        else:
            self.right.prepend(val)
        self.balance()

    def pushBack(self, val: int) -> None:
        self.right.append(val)
        self.balance()

    def popFront(self) -> int:
        if not self.left.head and not self.right.head:
            return -1

        val = self.left.pop_front()
        if val == -1:
            val = self.right.pop_front()

        self.balance()
        return val

    def popMiddle(self) -> int:
        if not self.left.head and not self.right.head:
            return -1

        if self.left.get_size() == self.right.get_size():
            val = self.left.pop_back()
        else:
            val = self.left.pop_back()

        self.balance()
        return val

    def popBack(self) -> int:
        val = self.right.pop_back()
        if val == -1:
            val = self.left.pop_back()

        self.balance()
        return val

    def balance(self):
        while self.left.get_size() < self.right.get_size():
            self.left.append(self.right.pop_front())
        while self.left.get_size() > self.right.get_size() + 1:
            self.right.prepend(self.left.pop_back())

More from this blog

C

Chatmagic blog

2894 posts