When preparing for technical interviews, especially for positions at major tech companies like FAANG (Facebook, Amazon, Apple, Netflix, Google), it’s crucial to have a solid understanding of fundamental data structures. Two of the most important and frequently tested data structures are stacks and queues. In this comprehensive guide, we’ll dive deep into these structures, exploring their characteristics, implementations, and common interview questions associated with them.

What are Stacks?

A stack is a linear data structure that follows the Last-In-First-Out (LIFO) principle. This means that the last element added to the stack will be the first one to be removed. Think of a stack of plates – you add plates to the top and remove them from the top as well.

Key Operations of a Stack

  • Push: Add an element to the top of the stack
  • Pop: Remove the top element from the stack
  • Peek or Top: View the top element without removing it
  • isEmpty: Check if the stack is empty

Implementing a Stack in Python

Here’s a simple implementation of a stack in Python using a list:

class Stack:
    def __init__(self):
        self.items = []

    def push(self, item):
        self.items.append(item)

    def pop(self):
        if not self.is_empty():
            return self.items.pop()

    def peek(self):
        if not self.is_empty():
            return self.items[-1]

    def is_empty(self):
        return len(self.items) == 0

    def size(self):
        return len(self.items)

Common Stack Interview Questions

  1. Valid Parentheses: Given a string containing just the characters ‘(‘, ‘)’, ‘{‘, ‘}’, ‘[‘ and ‘]’, determine if the input string is valid.
  2. Implement a Min Stack: Design a stack that supports push, pop, top, and retrieving the minimum element in constant time.
  3. Evaluate Reverse Polish Notation: Evaluate the value of an arithmetic expression in Reverse Polish Notation.
  4. Largest Rectangle in Histogram: Given n non-negative integers representing the histogram’s bar height where the width of each bar is 1, find the area of largest rectangle in the histogram.

What are Queues?

A queue is a linear data structure that follows the First-In-First-Out (FIFO) principle. This means that the first element added to the queue will be the first one to be removed. Think of a queue of people waiting in line – the first person to join the line is the first one to be served.

Key Operations of a Queue

  • Enqueue: Add an element to the rear of the queue
  • Dequeue: Remove the front element from the queue
  • Front: View the front element without removing it
  • isEmpty: Check if the queue is empty

Implementing a Queue in Python

Here’s a simple implementation of a queue in Python using a list:

from collections import deque

class Queue:
    def __init__(self):
        self.items = deque()

    def enqueue(self, item):
        self.items.append(item)

    def dequeue(self):
        if not self.is_empty():
            return self.items.popleft()

    def front(self):
        if not self.is_empty():
            return self.items[0]

    def is_empty(self):
        return len(self.items) == 0

    def size(self):
        return len(self.items)

Common Queue Interview Questions

  1. Implement Stack using Queues: Implement a last-in-first-out (LIFO) stack using only two queues.
  2. Implement Queue using Stacks: Implement a first in first out (FIFO) queue using only two stacks.
  3. Design Circular Queue: Design your implementation of the circular queue.
  4. Sliding Window Maximum: Given an array nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position. Return the max sliding window.

Comparing Stacks and Queues

While both stacks and queues are linear data structures, they have some key differences:

Stack Queue
Last-In-First-Out (LIFO) First-In-First-Out (FIFO)
Elements are added and removed from the same end Elements are added at one end and removed from the other end
Used in depth-first search algorithms Used in breadth-first search algorithms
Can be implemented using a single linked list Typically implemented using a doubly linked list for efficiency

Advanced Concepts: Variations of Stacks and Queues

As you progress in your understanding of these data structures, you’ll encounter various modifications and advanced concepts. Let’s explore some of these:

1. Priority Queue

A priority queue is an abstract data type similar to a regular queue or stack, but where each element has a “priority” associated with it. In a priority queue, an element with high priority is served before an element with low priority. If two elements have the same priority, they are served according to their order in the queue.

Priority queues are often implemented with heaps, which provide efficient O(log n) operations for both insertion and deletion.

Example Implementation in Python:

import heapq

class PriorityQueue:
    def __init__(self):
        self.elements = []
    
    def enqueue(self, item, priority):
        heapq.heappush(self.elements, (priority, item))
    
    def dequeue(self):
        return heapq.heappop(self.elements)[1]

2. Deque (Double-ended Queue)

A deque, short for double-ended queue, is an abstract data type that generalizes a queue, for which elements can be added to or removed from either the front (head) or back (tail). It is also often called a head-tail linked list.

Example Implementation in Python:

from collections import deque

class Deque:
    def __init__(self):
        self.items = deque()

    def add_front(self, item):
        self.items.appendleft(item)

    def add_rear(self, item):
        self.items.append(item)

    def remove_front(self):
        return self.items.popleft()

    def remove_rear(self):
        return self.items.pop()

    def is_empty(self):
        return len(self.items) == 0

3. Circular Queue

A circular queue is a variation of a queue where the last element is connected to the first element to form a circle. This structure is also called a “ring buffer”. It’s particularly useful in situations where we want to use a queue of fixed size.

Example Implementation in Python:

class CircularQueue:
    def __init__(self, k):
        self.k = k
        self.queue = [None] * k
        self.front = self.rear = -1

    def enqueue(self, data):
        if ((self.rear + 1) % self.k == self.front):
            print("Queue is full")
        elif (self.front == -1):
            self.front = 0
            self.rear = 0
            self.queue[self.rear] = data
        else:
            self.rear = (self.rear + 1) % self.k
            self.queue[self.rear] = data

    def dequeue(self):
        if (self.front == -1):
            print("Queue is empty")
        elif (self.front == self.rear):
            temp = self.queue[self.front]
            self.front = -1
            self.rear = -1
            return temp
        else:
            temp = self.queue[self.front]
            self.front = (self.front + 1) % self.k
            return temp

Real-world Applications of Stacks and Queues

Understanding the practical applications of these data structures can help reinforce their importance and provide context for their use in coding interviews.

Applications of Stacks

  1. Function Call Stack: Programming languages use a stack to keep track of function calls. When a function is called, a new frame is pushed onto the stack. When the function returns, the frame is popped off the stack.
  2. Undo Mechanism: Many applications use a stack to implement undo functionality. Each action is pushed onto a stack, and when the user wants to undo, the most recent action is popped off the stack and reversed.
  3. Expression Evaluation: Stacks are used to evaluate arithmetic expressions, particularly those in postfix notation.
  4. Backtracking Algorithms: Many backtracking algorithms use a stack to keep track of the current state and possible future states.

Applications of Queues

  1. Task Scheduling: Operating systems often use queues for task scheduling. Processes waiting to be executed are kept in a queue.
  2. Breadth-First Search: In graph algorithms, queues are used to implement breadth-first search.
  3. Printer Queue: Print jobs are typically processed in the order they are received, making a queue an ideal data structure for this purpose.
  4. Buffering: Queues are used in scenarios where there’s a need to manage different rates of data flow between processes, such as in streaming applications.

Advanced Interview Questions on Stacks and Queues

As you progress in your interview preparation, you may encounter more complex problems involving stacks and queues. Here are some advanced questions to challenge yourself:

Stack-based Questions

  1. Basic Calculator: Implement a basic calculator to evaluate a simple expression string.
  2. Longest Valid Parentheses: Given a string containing just the characters ‘(‘ and ‘)’, find the length of the longest valid (well-formed) parentheses substring.
  3. Decode String: Given an encoded string, return its decoded string.
  4. Remove K Digits: Given a non-negative integer num represented as a string, remove k digits from the number so that the new number is the smallest possible.

Queue-based Questions

  1. Task Scheduler: Given a char array representing tasks CPU need to do. It contains capital letters A to Z where different letters represent different tasks. Tasks could be done without original order. Each task could be done in one interval. For each interval, CPU could finish one task or just be idle. However, there is a non-negative cooling interval n that means between two same tasks, there must be at least n intervals that CPU are doing different tasks or just be idle. You need to return the least number of intervals the CPU will take to finish all the given tasks.
  2. Design Hit Counter: Design a hit counter which counts the number of hits received in the past 5 minutes.
  3. Rotten Oranges: In a given grid, each cell can have one of three values: 0 (empty cell), 1 (fresh orange), or 2 (rotten orange). Every minute, any fresh orange that is adjacent (4-directionally) to a rotten orange becomes rotten. Return the minimum number of minutes that must elapse until no cell has a fresh orange.
  4. Design Twitter: Design a simplified version of Twitter where users can post tweets, follow/unfollow another user and is able to see the 10 most recent tweets in the user’s news feed.

Tips for Mastering Stacks and Queues in Interviews

  1. Understand the core principles: Make sure you have a solid grasp of LIFO for stacks and FIFO for queues. This understanding will guide your problem-solving approach.
  2. Practice implementation: Be comfortable implementing these data structures from scratch. You might be asked to do this in an interview.
  3. Recognize patterns: Many problems that can be solved with stacks or queues have certain patterns. For example, problems involving matching pairs (like parentheses) often use stacks.
  4. Time complexity awareness: Understand the time complexity of operations on these data structures. This knowledge is crucial for optimizing your solutions.
  5. Consider edge cases: When solving problems, always consider edge cases like empty stacks/queues or operations that might cause overflow/underflow.
  6. Use built-in implementations wisely: While it’s important to know how to implement these structures from scratch, in real coding scenarios, it’s often more efficient to use language-provided implementations.

Conclusion

Stacks and queues are fundamental data structures that play a crucial role in many algorithms and real-world applications. Mastering these concepts is essential for success in technical interviews, especially when aiming for positions at top tech companies. By understanding their principles, practicing implementations, and solving related problems, you’ll be well-prepared to tackle stack and queue questions in your interviews.

Remember, the key to mastery is consistent practice. Try implementing these data structures in different programming languages, solve a variety of problems, and always strive to optimize your solutions. With dedication and practice, you’ll be well on your way to acing those technical interviews and advancing your programming skills.