Problems

Master heap and priority queue data structures by solving these carefully selected problems. Each problem demonstrates different applications of heaps and priority queues and builds your understanding of priority-based algorithms.

Before diving into these problems, make sure you’ve reviewed the Concept Guide to understand when and why to use heap patterns, and the Code Templates for implementation details. These problems are organized by difficulty to help you build your skills progressively.

Problem List

Easy Problems

1. Kth Largest Element in Array

LeetCode 215 | Difficulty: Medium | Acceptance: 62.8%

Brief: Find the kth largest element in an unsorted array.

Why this pattern?: Need to efficiently find the kth largest element without sorting the entire array.

Key Insight: Use a min-heap of size k to track the k largest elements seen so far. The root will always be the kth largest.

  graph LR
    A[Array Elements] --> B[Min-Heap Size k]
    B --> C[Root = kth Largest]
    D[New Element] --> E{Larger than Root?}
    E -->|Yes| F[Replace Root]
    E -->|No| G[Skip Element]
    F --> H[Heapify Down]
function findKthLargest(nums, k) {
    const minHeap = new PriorityQueue(); // Min-heap

    for (const num of nums) {
        minHeap.push(num);

        // Keep only k largest elements
        if (minHeap.heap.length > k) {
            minHeap.pop();
        }
    }

    return minHeap.peek();
}
import heapq

def findKthLargest(nums, k):
    min_heap = []

    for num in nums:
        heapq.heappush(min_heap, num)

        # Keep only k largest elements
        if len(min_heap) > k:
            heapq.heappop(min_heap)

    return min_heap[0]
class Solution {
    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> minHeap = new PriorityQueue<>();

        for (int num : nums) {
            minHeap.offer(num);

            // Keep only k largest elements
            if (minHeap.size() > k) {
                minHeap.poll();
            }
        }

        return minHeap.peek();
    }
}
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int, vector<int>, greater<int>> minHeap;

        for (int num : nums) {
            minHeap.push(num);

            // Keep only k largest elements
            if (minHeap.size() > k) {
                minHeap.pop();
            }
        }

        return minHeap.top();
    }
};
func findKthLargest(nums []int, k int) int {
    minHeap := &IntHeap{}
    heap.Init(minHeap)

    for _, num := range nums {
        heap.Push(minHeap, num)

        // Keep only k largest elements
        if minHeap.Len() > k {
            heap.Pop(minHeap)
        }
    }

    return (*minHeap)[0]
}

type IntHeap []int

func (h IntHeap) Len() int           { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h IntHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }

func (h *IntHeap) Push(x interface{}) {
    *h = append(*h, x.(int))
}

func (h *IntHeap) Pop() interface{} {
    old := *h
    n := len(old)
    x := old[n-1]
    *h = old[0 : n-1]
    return x
}
def find_kth_largest(nums, k)
    min_heap = []

    nums.each do |num|
        min_heap << num
        min_heap.sort!

        # Keep only k largest elements
        if min_heap.length > k
            min_heap.shift
        end
    end

    min_heap[0]
end

2. Last Stone Weight

LeetCode 1046 | Difficulty: Easy | Acceptance: 65.7%

Brief: Smash the two heaviest stones together until one or zero stones remain.

Why this pattern?: Need to repeatedly access the two largest elements efficiently.

Key Insight: Use a max-heap to always access the two heaviest stones. After smashing, add the difference back if it’s non-zero.

  graph TD
    A[All Stones] --> B[Max-Heap]
    B --> C[Extract Two Heaviest]
    C --> D{Same Weight?}
    D -->|Yes| E[Both Destroyed]
    D -->|No| F[Add Difference]
    F --> B
    E --> G{More Stones?}
    G -->|Yes| C
    G -->|No| H[Return Last Stone]
function lastStoneWeight(stones) {
    const maxHeap = new PriorityQueue((a, b) => b - a); // Max-heap

    // Add all stones to max-heap
    for (const stone of stones) {
        maxHeap.push(stone);
    }

    while (maxHeap.heap.length > 1) {
        const stone1 = maxHeap.pop();
        const stone2 = maxHeap.pop();

        // If stones have different weights, add difference back
        if (stone1 > stone2) {
            maxHeap.push(stone1 - stone2);
        }
    }

    return maxHeap.isEmpty() ? 0 : maxHeap.peek();
}
import heapq

def lastStoneWeight(stones):
    # Python heapq is min-heap, so negate values for max-heap behavior
    max_heap = [-stone for stone in stones]
    heapq.heapify(max_heap)

    while len(max_heap) > 1:
        stone1 = -heapq.heappop(max_heap)
        stone2 = -heapq.heappop(max_heap)

        # If stones have different weights, add difference back
        if stone1 > stone2:
            heapq.heappush(max_heap, -(stone1 - stone2))

    return 0 if not max_heap else -max_heap[0]
class Solution {
    public int lastStoneWeight(int[] stones) {
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);

        // Add all stones to max-heap
        for (int stone : stones) {
            maxHeap.offer(stone);
        }

        while (maxHeap.size() > 1) {
            int stone1 = maxHeap.poll();
            int stone2 = maxHeap.poll();

            // If stones have different weights, add difference back
            if (stone1 > stone2) {
                maxHeap.offer(stone1 - stone2);
            }
        }

        return maxHeap.isEmpty() ? 0 : maxHeap.peek();
    }
}
class Solution {
public:
    int lastStoneWeight(vector<int>& stones) {
        // C++ priority_queue is max-heap by default
        priority_queue<int> maxHeap;

        // Add all stones to max-heap
        for (int stone : stones) {
            maxHeap.push(stone);
        }

        while (maxHeap.size() > 1) {
            int stone1 = maxHeap.top();
            maxHeap.pop();
            int stone2 = maxHeap.top();
            maxHeap.pop();

            // If stones have different weights, add difference back
            if (stone1 > stone2) {
                maxHeap.push(stone1 - stone2);
            }
        }

        return maxHeap.empty() ? 0 : maxHeap.top();
    }
};
func lastStoneWeight(stones []int) int {
    maxHeap := &IntMaxHeap{}
    heap.Init(maxHeap)

    // Add all stones to max-heap
    for _, stone := range stones {
        heap.Push(maxHeap, stone)
    }

    for maxHeap.Len() > 1 {
        stone1 := heap.Pop(maxHeap).(int)
        stone2 := heap.Pop(maxHeap).(int)

        // If stones have different weights, add difference back
        if stone1 > stone2 {
            heap.Push(maxHeap, stone1-stone2)
        }
    }

    if maxHeap.Len() == 0 {
        return 0
    }
    return heap.Pop(maxHeap).(int)
}

type IntMaxHeap []int

func (h IntMaxHeap) Len() int           { return len(h) }
func (h IntMaxHeap) Less(i, j int) bool { return h[i] > h[j] }
func (h IntMaxHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }

func (h *IntMaxHeap) Push(x interface{}) {
    *h = append(*h, x.(int))
}

func (h *IntMaxHeap) Pop() interface{} {
    old := *h
    n := len(old)
    x := old[n-1]
    *h = old[0 : n-1]
    return x
}
def last_stone_weight(stones)
    # Ruby doesn't have built-in heap, so we'll use array with sort
    stones.sort!

    while stones.length > 1
        stone1 = stones.pop
        stone2 = stones.pop

        # If stones have different weights, add difference back
        if stone1 > stone2
            stones << (stone1 - stone2)
            stones.sort!
        end
    end

    stones.empty? ? 0 : stones[0]
end

3. Top K Frequent Elements

LeetCode 347 | Difficulty: Medium | Acceptance: 64.0%

Brief: Return the k most frequent elements from an array.

Why this pattern?: Need to find top-k elements based on frequency efficiently.

Key Insight: Use a min-heap with [frequency, element] pairs to keep track of the k most frequent elements.

Input Array
    ↓
Count Frequencies
    ↓
Min-Heap with [freq, element] pairs
    ↓
Heap Size > k?
   /    \
 Yes    No
  ↓      ↓
Remove   Continue
Smallest Adding
Frequency
    ↓
Back to Min-Heap
    ↓
Extract Elements
    ↓
Return Top K
function topKFrequent(nums, k) {
    const freq = new Map();
    const minHeap = new PriorityQueue((a, b) => a[0] - b[0]); // Min-heap by frequency

    // Count frequencies
    for (const num of nums) {
        freq.set(num, (freq.get(num) || 0) + 1);
    }

    // Add to heap [frequency, element] pairs
    for (const [num, count] of freq) {
        minHeap.push([count, num]);

        if (minHeap.heap.length > k) {
            minHeap.pop();
        }
    }

    // Extract elements (frequencies are not needed)
    return minHeap.heap.map(pair => pair[1]);
}
import heapq
from collections import Counter

def topKFrequent(nums, k):
    freq = Counter(nums)
    min_heap = []

    # Add to heap [frequency, element] pairs
    for num, count in freq.items():
        heapq.heappush(min_heap, (count, num))

        # Keep only k most frequent
        if len(min_heap) > k:
            heapq.heappop(min_heap)

    # Extract elements
    return [pair[1] for pair in min_heap]
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> freq = new HashMap<>();
        PriorityQueue<int[]> minHeap = new PriorityQueue<>((a, b) -> a[0] - b[0]);

        // Count frequencies
        for (int num : nums) {
            freq.put(num, freq.getOrDefault(num, 0) + 1);
        }

        // Add to heap [frequency, element] pairs
        for (Map.Entry<Integer, Integer> entry : freq.entrySet()) {
            minHeap.offer(new int[]{entry.getValue(), entry.getKey()});

            if (minHeap.size() > k) {
                minHeap.poll();
            }
        }

        // Extract elements
        int[] result = new int[k];
        for (int i = 0; i < k; i++) {
            result[i] = minHeap.poll()[1];
        }
        return result;
    }
}
class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        unordered_map<int, int> freq;
        priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> minHeap;

        // Count frequencies
        for (int num : nums) {
            freq[num]++;
        }

        // Add to heap [frequency, element] pairs
        for (auto& entry : freq) {
            minHeap.push({entry.second, entry.first});

            if (minHeap.size() > k) {
                minHeap.pop();
            }
        }

        // Extract elements
        vector<int> result;
        while (!minHeap.empty()) {
            result.push_back(minHeap.top().second);
            minHeap.pop();
        }
        return result;
    }
};
func topKFrequent(nums []int, k int) []int {
    freq := make(map[int]int)
    minHeap := &FreqHeap{}
    heap.Init(minHeap)

    // Count frequencies
    for _, num := range nums {
        freq[num]++
    }

    // Add to heap [frequency, element] pairs
    for num, count := range freq {
        heap.Push(minHeap, [2]int{count, num})

        if minHeap.Len() > k {
            heap.Pop(minHeap)
        }
    }

    // Extract elements
    result := make([]int, k)
    for i := 0; i < k; i++ {
        result[i] = heap.Pop(minHeap).([2]int)[1]
    }
    return result
}

type FreqHeap [][2]int

func (h FreqHeap) Len() int           { return len(h) }
func (h FreqHeap) Less(i, j int) bool { return h[i][0] < h[j][0] }
func (h FreqHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }

func (h *FreqHeap) Push(x interface{}) {
    *h = append(*h, x.([2]int))
}

func (h *FreqHeap) Pop() interface{} {
    old := *h
    n := len(old)
    x := old[n-1]
    *h = old[0 : n-1]
    return x
}
def top_k_frequent(nums, k)
    freq = Hash.new(0)
    nums.each { |num| freq[num] += 1 }

    min_heap = []

    # Add to heap [frequency, element] pairs
    freq.each do |num, count|
        min_heap << [count, num]
        min_heap.sort!

        # Keep only k most frequent
        if min_heap.length > k
            min_heap.shift
        end
    end

    # Extract elements
    min_heap.map { |pair| pair[1] }
end

Medium Problems

4. Find Median from Data Stream

LeetCode 295 | Difficulty: Hard | Acceptance: 51.7%

Brief: Implement a data structure that supports adding numbers and finding the median efficiently.

Why this pattern?: Need to maintain running median of streaming data with O(log n) operations.

Key Insight: Use two heaps - max-heap for lower half and min-heap for upper half. Balance them to keep median accessible.

  graph TD
    A[New Number] --> B{Compare with Max-Heap Top}
    B -->|Smaller| C[Add to Max-Heap]
    B -->|Larger| D[Add to Min-Heap]
    C --> E[Balance Heaps]
    D --> E
    E --> F{Heaps Balanced?}
    F -->|Yes| G[Find Median]
    F -->|No| H[Move Element]
    H --> E
class MedianFinder {
    constructor() {
        this.maxHeap = new PriorityQueue((a, b) => b - a); // Lower half
        this.minHeap = new PriorityQueue(); // Upper half
    }

    addNum(num) {
        // Add to max-heap, then move largest to min-heap
        this.maxHeap.push(num);
        this.minHeap.push(this.maxHeap.pop());

        // Balance: max-heap should have equal or one more element
        if (this.maxHeap.heap.length < this.minHeap.heap.length) {
            this.maxHeap.push(this.minHeap.pop());
        }
    }

    findMedian() {
        if (this.maxHeap.heap.length > this.minHeap.heap.length) {
            return this.maxHeap.peek();
        } else {
            return (this.maxHeap.peek() + this.minHeap.peek()) / 2;
        }
    }
}
import heapq

class MedianFinder:
    def __init__(self):
        self.max_heap = []  # Lower half (negated for max-heap)
        self.min_heap = []  # Upper half

    def addNum(self, num):
        # Add to max-heap, then move largest to min-heap
        heapq.heappush(self.max_heap, -num)
        heapq.heappush(self.min_heap, -heapq.heappop(self.max_heap))

        # Balance: max-heap should have equal or one more element
        if len(self.max_heap) < len(self.min_heap):
            heapq.heappush(self.max_heap, -heapq.heappop(self.min_heap))

    def findMedian(self):
        if len(self.max_heap) > len(self.min_heap):
            return -self.max_heap[0]
        else:
            return (-self.max_heap[0] + self.min_heap[0]) / 2
class MedianFinder {
    private PriorityQueue<Integer> maxHeap; // Lower half
    private PriorityQueue<Integer> minHeap; // Upper half

    public MedianFinder() {
        maxHeap = new PriorityQueue<>((a, b) -> b - a);
        minHeap = new PriorityQueue<>();
    }

    public void addNum(int num) {
        // Add to max-heap, then move largest to min-heap
        maxHeap.offer(num);
        minHeap.offer(maxHeap.poll());

        // Balance: max-heap should have equal or one more element
        if (maxHeap.size() < minHeap.size()) {
            maxHeap.offer(minHeap.poll());
        }
    }

    public double findMedian() {
        if (maxHeap.size() > minHeap.size()) {
            return maxHeap.peek();
        } else {
            return (maxHeap.peek() + minHeap.peek()) / 2.0;
        }
    }
}
class MedianFinder {
private:
    priority_queue<int> maxHeap; // Lower half
    priority_queue<int, vector<int>, greater<int>> minHeap; // Upper half

public:
    void addNum(int num) {
        // Add to max-heap, then move largest to min-heap
        maxHeap.push(num);
        minHeap.push(maxHeap.top());
        maxHeap.pop();

        // Balance: max-heap should have equal or one more element
        if (maxHeap.size() < minHeap.size()) {
            maxHeap.push(minHeap.top());
            minHeap.pop();
        }
    }

    double findMedian() {
        if (maxHeap.size() > minHeap.size()) {
            return maxHeap.top();
        } else {
            return (maxHeap.top() + minHeap.top()) / 2.0;
        }
    }
};
type MedianFinder struct {
    maxHeap *IntMaxHeap
    minHeap *IntHeap
}

func Constructor() MedianFinder {
    return MedianFinder{
        maxHeap: &IntMaxHeap{},
        minHeap: &IntHeap{},
    }
}

func (mf *MedianFinder) AddNum(num int) {
    heap.Push(mf.maxHeap, num)
    heap.Push(mf.minHeap, heap.Pop(mf.maxHeap).(int))

    // Balance: max-heap should have equal or one more element
    if mf.maxHeap.Len() < mf.minHeap.Len() {
        heap.Push(mf.maxHeap, heap.Pop(mf.minHeap).(int))
    }
}

func (mf *MedianFinder) FindMedian() float64 {
    if mf.maxHeap.Len() > mf.minHeap.Len() {
        return float64((*mf.maxHeap)[0])
    } else {
        return float64((*mf.maxHeap)[0]+(*mf.minHeap)[0]) / 2.0
    }
}
class MedianFinder
    def initialize
        @max_heap = []  # Lower half
        @min_heap = []  # Upper half
    end

    def add_num(num)
        # Add to max-heap, then move largest to min-heap
        @max_heap << num
        @max_heap.sort!.reverse!
        @min_heap << @max_heap.shift
        @min_heap.sort!

        # Balance: max-heap should have equal or one more element
        if @max_heap.length < @min_heap.length
            @max_heap << @min_heap.shift
            @max_heap.sort!.reverse!
        end
    end

    def find_median
        if @max_heap.length > @min_heap.length
            @max_heap[0]
        else
            (@max_heap[0] + @min_heap[0]) / 2.0
        end
    end
end

5. Merge k Sorted Lists

LeetCode 23 | Difficulty: Hard | Acceptance: 47.4%

Brief: Merge k sorted linked lists into one sorted list.

Why this pattern?: Need to efficiently find the smallest element among k lists.

Key Insight: Use a min-heap to always access the smallest available element from all lists.

  graph TD
    A[First Node of Each List] --> B[Min-Heap]
    B --> C[Extract Minimum]
    C --> D[Add to Result]
    D --> E[Add Next from Same List]
    E --> B
    F{All Lists Processed?} -->|No| C
    F -->|Yes| G[Return Result]
function mergeKLists(lists) {
    class ListNode {
        constructor(val = 0, next = null) {
            this.val = val;
            this.next = next;
        }
    }

    const minHeap = new PriorityQueue((a, b) => a.val - b.val);

    // Add first node from each non-empty list
    for (const list of lists) {
        if (list) minHeap.push(list);
    }

    const dummy = new ListNode();
    let current = dummy;

    while (!minHeap.isEmpty()) {
        const node = minHeap.pop();
        current.next = node;
        current = current.next;

        // Add next node from same list
        if (node.next) {
            minHeap.push(node.next);
        }
    }

    return dummy.next;
}
# Definition for singly-linked list.
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

def mergeKLists(lists):
    import heapq

    min_heap = []

    # Add first node from each non-empty list
    for i, head in enumerate(lists):
        if head:
            heapq.heappush(min_heap, (head.val, i, head))

    dummy = ListNode()
    current = dummy

    while min_heap:
        val, i, node = heapq.heappop(min_heap)
        current.next = node
        current = current.next

        # Add next node from same list
        if node.next:
            heapq.heappush(min_heap, (node.next.val, i, node.next))

    return dummy.next
class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        PriorityQueue<ListNode> minHeap = new PriorityQueue<>((a, b) -> a.val - b.val);

        // Add first node from each non-empty list
        for (ListNode head : lists) {
            if (head != null) {
                minHeap.offer(head);
            }
        }

        ListNode dummy = new ListNode(0);
        ListNode current = dummy;

        while (!minHeap.isEmpty()) {
            ListNode node = minHeap.poll();
            current.next = node;
            current = current.next;

            // Add next node from same list
            if (node.next != null) {
                minHeap.offer(node.next);
            }
        }

        return dummy.next;
    }
}
class Solution {
public:
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        auto cmp = [](ListNode* a, ListNode* b) { return a->val > b->val; };
        priority_queue<ListNode*, vector<ListNode*>, decltype(cmp)> minHeap(cmp);

        // Add first node from each non-empty list
        for (ListNode* head : lists) {
            if (head) {
                minHeap.push(head);
            }
        }

        ListNode dummy(0);
        ListNode* current = &dummy;

        while (!minHeap.empty()) {
            ListNode* node = minHeap.top();
            minHeap.pop();
            current->next = node;
            current = current->next;

            // Add next node from same list
            if (node->next) {
                minHeap.push(node->next);
            }
        }

        return dummy.next;
    }
};
func mergeKLists(lists []*ListNode) *ListNode {
    minHeap := &ListNodeHeap{}
    heap.Init(minHeap)

    // Add first node from each non-empty list
    for _, head := range lists {
        if head != nil {
            heap.Push(minHeap, head)
        }
    }

    dummy := &ListNode{}
    current := dummy

    for minHeap.Len() > 0 {
        node := heap.Pop(minHeap).(*ListNode)
        current.Next = node
        current = current.Next

        // Add next node from same list
        if node.Next != nil {
            heap.Push(minHeap, node.Next)
        }
    }

    return dummy.Next
}

type ListNode struct {
    Val  int
    Next *ListNode
}

type ListNodeHeap []*ListNode

func (h ListNodeHeap) Len() int           { return len(h) }
func (h ListNodeHeap) Less(i, j int) bool { return h[i].Val < h[j].Val }
func (h ListNodeHeap) Swap(i, j int)      { h[i], h[j] = h[j], h[i] }

func (h *ListNodeHeap) Push(x interface{}) {
    *h = append(*h, x.(*ListNode))
}

func (h *ListNodeHeap) Pop() interface{} {
    old := *h
    n := len(old)
    x := old[n-1]
    *h = old[0 : n-1]
    return x
}
class ListNode
    attr_accessor :val, :next

    def initialize(val = 0, next = nil)
        @val = val
        @next = next
    end
end

def merge_k_lists(lists)
    min_heap = []

    # Add first node from each non-empty list
    lists.each do |head|
        if head
            min_heap << [head.val, head]
            min_heap.sort!
        end
    end

    dummy = ListNode.new(0)
    current = dummy

    while !min_heap.empty?
        val, node = min_heap.shift
        current.next = node
        current = current.next

        # Add next node from same list
        if node.next
            min_heap << [node.next.val, node.next]
            min_heap.sort!
        end
    end

    dummy.next
end

Hard Problems

6. Sliding Window Maximum

LeetCode 239 | Difficulty: Hard | Acceptance: 45.3%

Brief: Find the maximum value in each sliding window of size k.

Why this pattern?: Need to efficiently maintain maximum in a sliding window with O(n) time.

Key Insight: Use a deque (monotonic queue) to maintain indices in decreasing order of values.

  graph TD
    A[New Element] --> B[Remove Out-of-Window Elements]
    B --> C[Remove Smaller Elements]
    C --> D[Add New Element]
    D --> E[Front is Maximum]
    E --> F[Add to Result]
function maxSlidingWindow(nums, k) {
    const result = [];
    const deque = []; // Store indices

    for (let i = 0; i < nums.length; i++) {
        // Remove elements out of current window
        while (deque.length > 0 && deque[0] <= i - k) {
            deque.shift();
        }

        // Remove elements smaller than current element
        while (deque.length > 0 && nums[deque[deque.length - 1]] <= nums[i]) {
            deque.pop();
        }

        deque.push(i);

        // Add to result when window is complete
        if (i >= k - 1) {
            result.push(nums[deque[0]]);
        }
    }

    return result;
}
def maxSlidingWindow(nums, k):
    from collections import deque

    result = []
    dq = deque()  # Store indices

    for i in range(len(nums)):
        # Remove elements out of current window
        while dq and dq[0] <= i - k:
            dq.popleft()

        # Remove elements smaller than current element
        while dq and nums[dq[-1]] <= nums[i]:
            dq.pop()

        dq.append(i)

        # Add to result when window is complete
        if i >= k - 1:
            result.append(nums[dq[0]])

    return result
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int[] result = new int[nums.length - k + 1];
        Deque<Integer> deque = new ArrayDeque<>();

        for (int i = 0; i < nums.length; i++) {
            // Remove elements out of current window
            while (!deque.isEmpty() && deque.peekFirst() <= i - k) {
                deque.pollFirst();
            }

            // Remove elements smaller than current element
            while (!deque.isEmpty() && nums[deque.peekLast()] <= nums[i]) {
                deque.pollLast();
            }

            deque.offerLast(i);

            // Add to result when window is complete
            if (i >= k - 1) {
                result[i - k + 1] = nums[deque.peekFirst()];
            }
        }

        return result;
    }
}
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> result;
        deque<int> dq;  // Store indices

        for (int i = 0; i < nums.size(); i++) {
            // Remove elements out of current window
            while (!dq.empty() && dq.front() <= i - k) {
                dq.pop_front();
            }

            // Remove elements smaller than current element
            while (!dq.empty() && nums[dq.back()] <= nums[i]) {
                dq.pop_back();
            }

            dq.push_back(i);

            // Add to result when window is complete
            if (i >= k - 1) {
                result.push_back(nums[dq.front()]);
            }
        }

        return result;
    }
};
func maxSlidingWindow(nums []int, k int) []int {
    result := make([]int, 0, len(nums)-k+1)
    dq := make([]int, 0)

    for i := 0; i < len(nums); i++ {
        // Remove elements out of current window
        for len(dq) > 0 && dq[0] <= i-k {
            dq = dq[1:]
        }

        // Remove elements smaller than current element
        for len(dq) > 0 && nums[dq[len(dq)-1]] <= nums[i] {
            dq = dq[:len(dq)-1]
        }

        dq = append(dq, i)

        // Add to result when window is complete
        if i >= k-1 {
            result = append(result, nums[dq[0]])
        }
    }

    return result
}
def max_sliding_window(nums, k)
    result = []
    dq = []  # Store indices

    nums.each_with_index do |num, i|
        # Remove elements out of current window
        while !dq.empty? && dq.first <= i - k
            dq.shift
        end

        # Remove elements smaller than current element
        while !dq.empty? && nums[dq.last] <= num
            dq.pop
        end

        dq << i

        # Add to result when window is complete
        if i >= k - 1
            result << nums[dq.first]
        end
    end

    result
end

Recommended Study Order

  1. Start with Easy Problems: Build confidence with basic heap operations

    • Kth Largest Element (understand min-heap for max elements)
    • Last Stone Weight (max-heap operations)
    • Top K Frequent Elements (frequency-based heap usage)
  2. Progress to Medium Problems: Apply heaps to more complex scenarios

    • Find Median from Data Stream (two-heap pattern)
    • Merge k Sorted Lists (heap with linked lists)
  3. Tackle Hard Problems: Master advanced heap applications

    • Sliding Window Maximum (monotonic queue/deque)

Common Patterns to Recognize

  • Top-K elements → Heap of size k with min/max
  • Running median → Two heaps (max + min)
  • K-way merge → Priority queue with list heads
  • Scheduling/priority tasks → Custom priority queue
  • Shortest path algorithms → Dijkstra with priority queue

Interview Tips

  • Choose heap type wisely - Min-heap for max elements, max-heap for min elements
  • Handle indexing carefully - Parent/child calculations: floor((i-1)/2), 2i+1, 2i+2
  • Consider built-ins - PriorityQueue (Java), heapq (Python) for efficiency
  • Time vs space tradeoffs - Heaps are memory efficient for priority operations
  • Custom comparators - For objects, tuples with multiple priority criteria

Ready to test your skills? Review our Code Templates if you need implementation details, then try solving these problems on your own!

Remember: Heaps maintain the heap property through bubble up/down operations. Always restore the property after insertions and extractions to keep the data structure valid.