{x}
blog image

Design Most Recently Used Queue

Solution Explanation for Design Most Recently Used Queue

This problem requires designing a data structure that simulates a queue but prioritizes the most recently used element. The most straightforward approach uses a list (or array) and manipulates it directly, but this leads to O(n) time complexity for each fetch operation. A more efficient solution utilizes a Binary Indexed Tree (BIT) to achieve a logarithmic time complexity for fetch.

Approach 1: List Manipulation (O(n) for fetch)

This approach is simpler to implement. It directly maintains the queue as a list. When fetch(k) is called, the k-th element is removed from its position and appended to the end.

  • Code (Python):
class MRUQueue:
    def __init__(self, n: int):
        self.q = list(range(1, n + 1))
 
    def fetch(self, k: int) -> int:
        ans = self.q[k - 1]
        self.q.pop(k - 1)  # Remove from original position
        self.q.append(ans) # Add to the end
        return ans
  • Time Complexity: O(n) for fetch. Removing an element from the middle of a list takes O(n) time in the worst case.
  • Space Complexity: O(n) to store the queue.

Approach 2: Binary Indexed Tree (BIT) for Optimized Fetch (O(log n) for fetch)

This approach uses a BIT to efficiently track the positions of elements in the queue. The BIT allows us to find the actual index of the k-th element in O(log n) time.

  • Data Structures:

    • q: A list (or array) storing the elements of the queue.
    • tree: A Binary Indexed Tree to maintain the cumulative count of elements. This helps to find the actual index of the kth element in the queue quickly.
  • Algorithm:

    1. __init__(n): Initialize the queue q with elements from 1 to n and the BIT tree.
    2. fetch(k):
      • Perform binary search on q using the BIT tree to find the index idx of the k-th element. The BIT helps determine which index corresponds to the k-th element after the elements might be reordered.
      • Remove the element from q[idx] and append it to the end of q.
      • Update the BIT tree to reflect the change in the queue.
      • Return the fetched element.
  • Code (Python):

class BinaryIndexedTree:
    # ... (Binary Indexed Tree implementation as shown before) ...
 
class MRUQueue:
    def __init__(self, n: int):
        self.q = list(range(1, n + 1))
        self.tree = BinaryIndexedTree(n + 2010) # +2010 for safety margin
 
    def fetch(self, k: int) -> int:
        l, r = 1, len(self.q)
        while l < r:
            mid = (l + r) // 2
            if mid - self.tree.query(mid) >= k:
                r = mid
            else:
                l = mid + 1
        x = self.q[l-1] # Adjust index since we're using 1-based indexing
        self.q.pop(l - 1)
        self.q.append(x)
        return x
  • Time Complexity: O(log n) for fetch. Binary search takes O(log n) time, and BIT updates also take O(log n) time.
  • Space Complexity: O(n) to store the queue and the BIT.

Comparison:

| Feature | Approach 1 (List) | Approach 2 (BIT) | |----------------|--------------------|--------------------| | fetch Time | O(n) | O(log n) | | Space | O(n) | O(n) | | Implementation | Simpler | More complex |

The Binary Indexed Tree approach provides a significant performance improvement for frequent fetch operations, making it more suitable for larger values of n and numerous calls to fetch. The provided code in different languages showcases the BIT-based solution, though the list-based solution would be a simpler, yet less efficient, alternative.