Priority Queue
Last updated: Apr 3, 2026Table of Contents
- Overview
- Key Properties
- Implementation
- References
- Problem Categories
- Pattern 1: Top K Elements
- Pattern 2: Merge K Sorted
- Pattern 3: Scheduling & Intervals
- Pattern 4: Sliding Window with Order
- Pattern 5: Graph Algorithms
- Pattern 6: Data Stream & Median
- Pattern 7: Greedy String/Sequence Building with Constraint
- Templates & Algorithms
- Template Comparison Table
- Template 1: Top K Elements Pattern
- Template 2: K-Way Merge Pattern
- Template 3: Two Heaps Pattern (Median Finding)
- Template 4: Interval Scheduling Pattern
- Template 5: Graph Shortest Path (Dijkstra)
- Template 6: Custom Priority Pattern
- Template 7: Greedy String Building with Consecutive Constraint
- Basic Operations
- Python heapq Operations
- Java PriorityQueue Operations
- Problems by Pattern
- Pattern-Based Problem Tables
- Pattern Selection Strategy
- Summary & Quick Reference
- Complexity Quick Reference
- Template Quick Reference
- Common Patterns & Tricks
- Problem-Solving Steps
- Common Mistakes & Tips
- Interview Tips
- Advanced Techniques
- Related Topics
- Classic LC Problems with Java Solutions
- 2-1) Kth Largest Element in an Array (LC 215)
- 2-2) Top K Frequent Elements (LC 347)
- 2-3) Merge K Sorted Lists (LC 23)
- 2-4) Find Median from Data Stream (LC 295)
- 2-5) Meeting Rooms II (LC 253)
- 2-6) Kth Largest Element in a Stream (LC 703)
- 2-7) K Closest Points to Origin (LC 973)
- 2-8) Kth Smallest Element in a Sorted Matrix (LC 378)
- 2-9) Task Scheduler (LC 621)
- 2-10) Reorganize String (LC 767)
- 2-11) Sliding Window Median (LC 480)
- 2-12) Ugly Number II (LC 264)
- 2-13) Network Delay Time (LC 743 - Dijkstra)
- 2-14) Sort Characters By Frequency (LC 451)
- 2-15) Last Stone Weight (LC 1046)
- Quick Reference: PQ Pattern β Problem Mapping
- LC Examples
- 2-1) Kth Largest Element in a Stream (LC 703) β Min-Heap of Size K
- 2-2) Top K Frequent Elements (LC 347) β Min-Heap with Frequency
- 2-3) Merge K Sorted Lists (LC 23) β Min-Heap
Priority Queue (PQ) Data Structure
Overview
Priority Queue is an abstract data type that operates similar to a regular queue but with an added priority element. Elements are served based on their priority rather than the order they were added.
Key Properties
- Time Complexity:
- Insert: O(log n)
- Delete max/min: O(log n)
- Get max/min: O(1)
- Heapify: O(n)
- Space Complexity: O(n)
- Core Idea: Elements with higher priority are served before elements with lower priority
- When to Use: When you need to process elements based on priority/order, not just insertion time
Implementation
- Usually implemented using Binary Heap (min-heap or max-heap)
- Can also use balanced BST or Fibonacci heap for advanced operations
- Python:
heapqmodule (min-heap by default) - Java:
PriorityQueueclass (min-heap by default)
References
Problem Categories
Pattern 1: Top K Elements
- Description: Finding K largest/smallest elements efficiently
- Examples: LC 215, 347, 692, 973, 1985
- Pattern: Use min-heap for K largest, max-heap for K smallest
Pattern 2: Merge K Sorted
- Description: Merging multiple sorted sequences
- Examples: LC 23, 88, 313, 373, 786
- Pattern: Use PQ to track current smallest/largest from each sequence
Pattern 3: Scheduling & Intervals
- Description: Task scheduling and interval processing
- Examples: LC 253, 1094, 1353, 1834, 2402
- Pattern: Sort by start time, use PQ for end times or priorities
Pattern 4: Sliding Window with Order
- Description: Maintaining order statistics in sliding windows
- Examples: LC 239, 480, 703, 1438, 2542
- Pattern: Use PQ to track min/max in current window
Pattern 5: Graph Algorithms
- Description: Shortest path and MST algorithms
- Examples: LC 743, 787, 1514, 1584, 1631
- Pattern: Dijkstraβs algorithm, Primβs algorithm
Pattern 6: Data Stream & Median
- Description: Processing continuous data streams
- Examples: LC 295, 346, 352, 703, 1825
- Pattern: Two-heap technique for median, PQ for percentiles
Pattern 7: Greedy String/Sequence Building with Constraint
- Description: Build a string/sequence greedily using the most frequent element, but skip it when adding it would violate a constraint (e.g., 3 consecutive same chars). Use a max-heap to always have the current most frequent element ready.
- Examples: LC 1405 (Longest Happy String), LC 767 (Reorganize String), LC 621 (Task Scheduler), LC 358 (Rearrange String k Distance Apart)
- Pattern: Max-heap ordered by count; on each step try the top element β if it violates the constraint, temporarily use the 2nd element, then put the 1st back
- Key Trick: Two-case loop
- Case 1 β constraint violated: poll
second, append it, decrement, re-add if > 0; then re-addfirst(it was NOT consumed) - Case 2 β safe: append
first, decrement, re-add if > 0
- Case 1 β constraint violated: poll
Templates & Algorithms
Template Comparison Table
| Template Type | Use Case | Heap Type | Complexity | When to Use |
|---|---|---|---|---|
| Top K Elements | Find K largest/smallest | Min/Max heap | O(n log k) | Fixed K selection |
| K-Way Merge | Merge sorted lists | Min heap | O(n log k) | Multiple sorted sources |
| Two Heaps | Find median | Min + Max heap | O(log n) | Stream median/percentile |
| Interval Scheduling | Process intervals | Min heap | O(n log n) | Meeting rooms, events |
| Graph Shortest Path | Dijkstraβs | Min heap | O(E log V) | Weighted graphs |
| Custom Priority | Complex ordering | Custom comparator | O(log n) | Multi-criteria sorting |
| Greedy + Constraint | Build string avoiding consecutive repeats | Max heap | O(n log k) | Reorganize/happy string |
Template 1: Top K Elements Pattern
# Python - Find K largest elements
def topKElements(nums, k):
import heapq
# Min heap of size k for k largest
min_heap = []
for num in nums:
heapq.heappush(min_heap, num)
if len(min_heap) > k:
heapq.heappop(min_heap)
return min_heap # Contains k largest elements
# With custom key for frequency
def topKFrequent(nums, k):
from collections import Counter
import heapq
count = Counter(nums)
# Use negative count for max heap effect
return heapq.nlargest(k, count.keys(), key=count.get)
// Java - Top K elements with frequency
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();
for (int n : nums) {
map.put(n, map.getOrDefault(n, 0) + 1);
}
// Min heap based on frequency
PriorityQueue<Integer> pq = new PriorityQueue<>(
(a, b) -> map.get(a) - map.get(b)
);
for (int key : map.keySet()) {
pq.add(key);
if (pq.size() > k) {
pq.poll();
}
}
int[] result = new int[k];
for (int i = k - 1; i >= 0; i--) {
result[i] = pq.poll();
}
return result;
}
Template 2: K-Way Merge Pattern
# Python - Merge K sorted lists
def mergeKSortedLists(lists):
import heapq
min_heap = []
# Initialize with first element from each list
for i, lst in enumerate(lists):
if lst:
heapq.heappush(min_heap, (lst[0], i, 0))
result = []
while min_heap:
val, list_idx, elem_idx = heapq.heappop(min_heap)
result.append(val)
# Add next element from same list
if elem_idx + 1 < len(lists[list_idx]):
next_val = lists[list_idx][elem_idx + 1]
heapq.heappush(min_heap, (next_val, list_idx, elem_idx + 1))
return result
// Java - Merge K sorted arrays
public int[] mergeKSortedArrays(int[][] arrays) {
PriorityQueue<int[]> pq = new PriorityQueue<>(
(a, b) -> a[0] - b[0] // Compare values
);
int totalSize = 0;
// Initialize PQ with first element from each array
for (int i = 0; i < arrays.length; i++) {
if (arrays[i].length > 0) {
pq.offer(new int[]{arrays[i][0], i, 0});
totalSize += arrays[i].length;
}
}
int[] result = new int[totalSize];
int idx = 0;
while (!pq.isEmpty()) {
int[] curr = pq.poll();
result[idx++] = curr[0];
int arrIdx = curr[1];
int elemIdx = curr[2];
if (elemIdx + 1 < arrays[arrIdx].length) {
pq.offer(new int[]{
arrays[arrIdx][elemIdx + 1],
arrIdx,
elemIdx + 1
});
}
}
return result;
}
Template 3: Two Heaps Pattern (Median Finding)
# Python - Find median from data stream
class MedianFinder:
def __init__(self):
self.small = [] # Max heap (negate values)
self.large = [] # Min heap
def addNum(self, num):
import heapq
# Add to max heap (small values)
heapq.heappush(self.small, -num)
# Balance: ensure max of small <= min of large
if self.small and self.large and (-self.small[0] > self.large[0]):
val = -heapq.heappop(self.small)
heapq.heappush(self.large, val)
# Balance sizes
if len(self.small) > len(self.large) + 1:
val = -heapq.heappop(self.small)
heapq.heappush(self.large, val)
if len(self.large) > len(self.small) + 1:
val = heapq.heappop(self.large)
heapq.heappush(self.small, -val)
def findMedian(self):
if len(self.small) > len(self.large):
return -self.small[0]
if len(self.large) > len(self.small):
return self.large[0]
return (-self.small[0] + self.large[0]) / 2.0
// Java - Two heaps for median
class MedianFinder {
private PriorityQueue<Integer> small; // Max heap
private PriorityQueue<Integer> large; // Min heap
public MedianFinder() {
small = new PriorityQueue<>(Collections.reverseOrder());
large = new PriorityQueue<>();
}
public void addNum(int num) {
small.offer(num);
// Balance property
if (!small.isEmpty() && !large.isEmpty() &&
small.peek() > large.peek()) {
large.offer(small.poll());
}
// Size property
if (small.size() > large.size() + 1) {
large.offer(small.poll());
}
if (large.size() > small.size() + 1) {
small.offer(large.poll());
}
}
public double findMedian() {
if (small.size() > large.size()) {
return small.peek();
}
if (large.size() > small.size()) {
return large.peek();
}
return (small.peek() + large.peek()) / 2.0;
}
}
Template 4: Interval Scheduling Pattern
# Python - Meeting rooms (minimum rooms needed)
def minMeetingRooms(intervals):
import heapq
if not intervals:
return 0
# Sort by start time
intervals.sort(key=lambda x: x[0])
# Min heap to track end times
heap = []
heapq.heappush(heap, intervals[0][1])
for i in range(1, len(intervals)):
# If current meeting starts after earliest end
if intervals[i][0] >= heap[0]:
heapq.heappop(heap)
# Add current meeting end time
heapq.heappush(heap, intervals[i][1])
return len(heap)
// Java - Interval scheduling
public int minMeetingRooms(int[][] intervals) {
if (intervals.length == 0) return 0;
// Sort by start time
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
// Min heap for end times
PriorityQueue<Integer> pq = new PriorityQueue<>();
pq.offer(intervals[0][1]);
for (int i = 1; i < intervals.length; i++) {
// Room becomes free
if (intervals[i][0] >= pq.peek()) {
pq.poll();
}
pq.offer(intervals[i][1]);
}
return pq.size();
}
Template 5: Graph Shortest Path (Dijkstra)
# Python - Dijkstra's algorithm with PQ
def dijkstra(graph, start, end):
import heapq
# Min heap: (distance, node)
pq = [(0, start)]
distances = {start: 0}
visited = set()
while pq:
curr_dist, curr_node = heapq.heappop(pq)
if curr_node in visited:
continue
visited.add(curr_node)
if curr_node == end:
return curr_dist
for neighbor, weight in graph[curr_node]:
if neighbor not in visited:
new_dist = curr_dist + weight
if neighbor not in distances or new_dist < distances[neighbor]:
distances[neighbor] = new_dist
heapq.heappush(pq, (new_dist, neighbor))
return -1 # Path not found
// Java - Dijkstra with Priority Queue
public int dijkstra(Map<Integer, List<int[]>> graph, int start, int end) {
// Min heap: [distance, node]
PriorityQueue<int[]> pq = new PriorityQueue<>(
(a, b) -> a[0] - b[0]
);
Map<Integer, Integer> distances = new HashMap<>();
Set<Integer> visited = new HashSet<>();
pq.offer(new int[]{0, start});
distances.put(start, 0);
while (!pq.isEmpty()) {
int[] curr = pq.poll();
int currDist = curr[0];
int currNode = curr[1];
if (visited.contains(currNode)) continue;
visited.add(currNode);
if (currNode == end) return currDist;
if (graph.containsKey(currNode)) {
for (int[] edge : graph.get(currNode)) {
int neighbor = edge[0];
int weight = edge[1];
if (!visited.contains(neighbor)) {
int newDist = currDist + weight;
if (!distances.containsKey(neighbor) ||
newDist < distances.get(neighbor)) {
distances.put(neighbor, newDist);
pq.offer(new int[]{newDist, neighbor});
}
}
}
}
}
return -1;
}
Template 6: Custom Priority Pattern
# Python - Custom priority with multiple criteria
class Task:
def __init__(self, name, priority, deadline):
self.name = name
self.priority = priority
self.deadline = deadline
def __lt__(self, other):
# Higher priority first, then earlier deadline
if self.priority != other.priority:
return self.priority > other.priority
return self.deadline < other.deadline
def processTasks(tasks):
import heapq
heap = []
for task in tasks:
heapq.heappush(heap, task)
result = []
while heap:
task = heapq.heappop(heap)
result.append(task.name)
return result
// Java - Custom comparator for complex ordering
class Task {
String name;
int priority;
int deadline;
Task(String name, int priority, int deadline) {
this.name = name;
this.priority = priority;
this.deadline = deadline;
}
}
public List<String> processTasks(List<Task> tasks) {
PriorityQueue<Task> pq = new PriorityQueue<>((a, b) -> {
// Higher priority first
if (a.priority != b.priority) {
return b.priority - a.priority;
}
// Earlier deadline first
return a.deadline - b.deadline;
});
for (Task task : tasks) {
pq.offer(task);
}
List<String> result = new ArrayList<>();
while (!pq.isEmpty()) {
result.add(pq.poll().name);
}
return result;
}
Template 7: Greedy String Building with Consecutive Constraint
// Java - Longest Happy String (LC 1405) / Reorganize String (LC 767)
// IDEA: Max-heap by count; two-case loop:
// Case 1: top char would create 3 consecutive β use 2nd, put 1st back
// Case 2: safe β use top char directly
// time = O((a+b+c) * log(3)) = O(n), space = O(1) heap size bounded by alphabet
class ValCnt {
char val;
int cnt;
ValCnt(char val, int cnt) { this.val = val; this.cnt = cnt; }
}
public String longestDiverseString(int a, int b, int c) {
PriorityQueue<ValCnt> pq = new PriorityQueue<>((x, y) -> y.cnt - x.cnt);
if (a > 0) pq.add(new ValCnt('a', a));
if (b > 0) pq.add(new ValCnt('b', b));
if (c > 0) pq.add(new ValCnt('c', c));
StringBuilder sb = new StringBuilder();
while (!pq.isEmpty()) {
ValCnt first = pq.poll();
int len = sb.length();
// Case 1: adding `first` would create 3 consecutive β use second instead
if (len >= 2
&& sb.charAt(len - 1) == first.val
&& sb.charAt(len - 2) == first.val) {
if (pq.isEmpty()) break; // no alternative β stop
ValCnt second = pq.poll(); // use 2nd most frequent
sb.append(second.val);
second.cnt--;
if (second.cnt > 0) pq.add(second);
pq.add(first); // first was NOT used, put it back
// Case 2: safe to use the most frequent character
} else {
sb.append(first.val);
first.cnt--;
if (first.cnt > 0) pq.add(first);
}
}
return sb.toString();
}
Key Observations:
- Always greedily pick the most frequent (max-heap ensures this).
- When the constraint is about to be violated, temporarily skip the top element and use the next β then put the top back unchanged.
firstis only consumed in Case 2; in Case 1 it is re-inserted untouched.- Works for any βat most K consecutiveβ constraint by changing the look-back window check.
Variant: Reorganize String (LC 767) β at most 1 consecutive
// Only Case 1 check changes: len >= 1 && sb.charAt(len-1) == first.val
// Everything else is identical to the template above.
Basic Operations
Python heapq Operations
import heapq
# Create heap (min-heap by default)
heap = []
# Push element
heapq.heappush(heap, 5)
# Pop smallest
smallest = heapq.heappop(heap)
# Push and pop in one operation
val = heapq.heappushpop(heap, 3) # Push 3, then pop smallest
# Pop and push in one operation
val = heapq.heapreplace(heap, 3) # Pop smallest, then push 3
# Get smallest without removing
smallest = heap[0] if heap else None
# Convert list to heap in-place
nums = [3, 1, 4, 1, 5]
heapq.heapify(nums) # O(n) time
# Get n largest/smallest
largest_k = heapq.nlargest(k, nums)
smallest_k = heapq.nsmallest(k, nums)
# Max heap trick (negate values)
max_heap = []
heapq.heappush(max_heap, -5) # Push
max_val = -heapq.heappop(max_heap) # Pop
Java PriorityQueue Operations
import java.util.*;
// Create PQ (min-heap by default)
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
// Create max-heap
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
// Add elements
minHeap.offer(5);
minHeap.add(3); // Same as offer
// Remove and return smallest/largest
Integer smallest = minHeap.poll();
// Peek without removing
Integer top = minHeap.peek();
// Check if empty
boolean isEmpty = minHeap.isEmpty();
// Get size
int size = minHeap.size();
// Clear all elements
minHeap.clear();
// Custom comparator
PriorityQueue<int[]> customPQ = new PriorityQueue<>(
(a, b) -> a[0] - b[0] // Compare first element
);
Problems by Pattern
Pattern-Based Problem Tables
Top K Elements Problems
| Problem | LC # | Key Technique | Difficulty |
|---|---|---|---|
| Kth Largest Element in an Array | 215 | Quick select or min-heap | Medium |
| Top K Frequent Elements | 347 | Frequency map + heap | Medium |
| Top K Frequent Words | 692 | Custom comparator | Medium |
| K Closest Points to Origin | 973 | Distance calculation | Medium |
| Find K Pairs with Smallest Sums | 373 | K-way merge pattern | Medium |
| Kth Smallest Element in a Sorted Matrix | 378 | Binary search or heap | Medium |
| Find K-th Smallest Pair Distance | 719 | Binary search + sliding | Hard |
| K-th Smallest Prime Fraction | 786 | Binary search or heap | Medium |
Merge K Sorted Problems
| Problem | LC # | Key Technique | Difficulty |
|---|---|---|---|
| Merge k Sorted Lists | 23 | K-way merge | Hard |
| Merge Sorted Array | 88 | Two pointers | Easy |
| Smallest Range Covering K Lists | 632 | Multi-pointer + heap | Hard |
| Find K Pairs with Smallest Sums | 373 | Heap with pairs | Medium |
| Super Ugly Number | 313 | Multiple pointers | Medium |
Scheduling & Interval Problems
| Problem | LC # | Key Technique | Difficulty |
|---|---|---|---|
| Meeting Rooms II | 253 | Sort + min heap | Medium |
| Task Scheduler | 621 | Greedy + counting | Medium |
| Reorganize String | 767 | Max heap + greedy | Medium |
| Car Pooling | 1094 | Timeline events | Medium |
| Maximum Events That Can Be Attended | 1353 | Sort + greedy | Medium |
| Single-Threaded CPU | 1834 | Two heaps | Medium |
| Maximum Number of Tasks You Can Assign | 2071 | Binary search + greedy | Hard |
Sliding Window with Order Problems
| Problem | LC # | Key Technique | Difficulty |
|---|---|---|---|
| Sliding Window Maximum | 239 | Deque or heap | Hard |
| Sliding Window Median | 480 | Two heaps | Hard |
| Kth Largest Element in a Stream | 703 | Min heap of size k | Easy |
| Longest Continuous Subarray | 1438 | Two deques | Medium |
| Maximum Score of a Good Subarray | 1793 | Monotonic stack | Hard |
Graph Algorithm Problems
| Problem | LC # | Key Technique | Difficulty |
|---|---|---|---|
| Network Delay Time | 743 | Dijkstraβs | Medium |
| Cheapest Flights Within K Stops | 787 | Modified Dijkstra | Medium |
| Path with Maximum Probability | 1514 | Dijkstra variant | Medium |
| Path With Minimum Effort | 1631 | Binary search + DFS/Dijkstra | Medium |
| Min Cost to Connect All Points | 1584 | Primβs MST | Medium |
| Swim in Rising Water | 778 | Binary search or Dijkstra | Hard |
| Reachable Nodes In Subdivided Graph | 882 | Dijkstraβs | Hard |
Data Stream & Median Problems
| Problem | LC # | Key Technique | Difficulty |
|---|---|---|---|
| Find Median from Data Stream | 295 | Two heaps | Hard |
| Moving Average from Data Stream | 346 | Queue | Easy |
| Data Stream as Disjoint Intervals | 352 | TreeMap or heap | Hard |
| Kth Largest Element in a Stream | 703 | Min heap | Easy |
| Finding MK Average | 1825 | Multiset simulation | Hard |
Greedy + Consecutive Constraint Problems
| Problem | LC # | Key Technique | Difficulty |
|---|---|---|---|
| Longest Happy String | 1405 | Max heap + greedy two-case loop | Medium |
| Reorganize String | 767 | Max heap + greedy (no adjacent same) | Medium |
| Task Scheduler | 621 | Max heap + greedy cooldown | Medium |
| Rearrange String k Distance Apart | 358 | Max heap + greedy (k-distance) | Hard |
| Distant Barcodes | 1054 | Max heap + greedy (no adjacent same) | Medium |
Pattern Selection Strategy
Problem Analysis Flowchart:
1. Do you need to find K largest/smallest elements?
βββ YES β Use Top K Elements pattern
β βββ K largest β Min heap of size K
β βββ K smallest β Max heap of size K
βββ NO β Continue to 2
2. Are you merging multiple sorted sequences?
βββ YES β Use K-Way Merge pattern
β βββ Track position in each sequence
βββ NO β Continue to 3
3. Is it a scheduling/interval problem?
βββ YES β Use Interval Scheduling pattern
β βββ Sort by start time
β βββ Use heap for end times
βββ NO β Continue to 4
4. Do you need to maintain order in sliding window?
βββ YES β Use Sliding Window with Order
β βββ Consider deque vs heap trade-offs
βββ NO β Continue to 5
5. Is it a graph shortest path problem?
βββ YES β Use Dijkstra pattern with PQ
β βββ Min heap with distances
βββ NO β Continue to 6
6. Processing data stream for statistics?
βββ YES β Use Data Stream pattern
β βββ Median β Two heaps
β βββ Top K β Fixed size heap
βββ NO β Continue to 7
7. Build string/sequence with "no K consecutive same" constraint?
βββ YES β Use Greedy + Constraint pattern
β βββ Max-heap ordered by frequency
β βββ Case 1 (constraint violated): use 2nd element, put 1st back
β βββ Case 2 (safe): use 1st element
βββ NO β Use Custom Priority pattern
Summary & Quick Reference
Complexity Quick Reference
| Operation | Time Complexity | Space | Notes |
|---|---|---|---|
| Insert | O(log n) | O(1) | Heap rebalancing |
| Extract min/max | O(log n) | O(1) | Heap rebalancing |
| Peek min/max | O(1) | O(1) | Direct access |
| Build heap | O(n) | O(n) | Bottom-up heapify |
| K-way merge | O(n log k) | O(k) | k = number of lists |
| Top K elements | O(n log k) | O(k) | Maintain heap of size k |
| Heap sort | O(n log n) | O(1) | In-place sorting |
Template Quick Reference
| Template | Pattern | Key Code |
|---|---|---|
| Top K | Min heap for K largest | if len(heap) > k: heappop(heap) |
| K-Way Merge | Track indices | (val, list_idx, elem_idx) |
| Two Heaps | Balance sizes | if len(small) > len(large) + 1 |
| Intervals | Sort + heap | sort by start, heap for end times |
| Dijkstra | Min distance | (distance, node) in heap |
| Custom | Comparator | __lt__ in Python, Comparator in Java |
| Greedy+Constraint | Two-case loop | case1: use 2nd, put 1st back; case2: use 1st |
Common Patterns & Tricks
Heap Direction Trick
# Python max heap using negation
max_heap = []
heapq.heappush(max_heap, -value) # Push
max_value = -heapq.heappop(max_heap) # Pop
K-Size Maintenance
# Maintain exactly k elements
if len(heap) > k:
heapq.heappop(heap)
# Now heap contains k largest (min-heap) or k smallest (max-heap)
Lazy Deletion Pattern
# Mark as deleted without immediate removal
deleted = set()
while heap and heap[0] in deleted:
heapq.heappop(heap)
Two Heaps Balance
# Keep sizes balanced within 1
if len(small) > len(large) + 1:
large.push(small.pop())
if len(large) > len(small) + 1:
small.push(large.pop())
Problem-Solving Steps
-
Identify the Pattern
- Is it about K elements? β Top K pattern
- Multiple sorted sources? β K-way merge
- Need median/percentiles? β Two heaps
- Graph shortest path? β Dijkstra with PQ
-
Choose Heap Type
- K largest β Min heap of size K
- K smallest β Max heap of size K
- Median β Two heaps (max + min)
-
Design the Element
- Simple value or tuple?
- What to compare? (value, index, custom)
- Need to track source? (for merging)
-
Handle Edge Cases
- Empty heap checks
- K > n scenarios
- Duplicate elements
- Custom comparator edge cases
Common Mistakes & Tips
π« Common Mistakes:
- Using max heap for K largest (should use min heap)
- Forgetting to maintain heap size β€ K
- Not handling empty heap before peek
- Wrong comparator direction
- Not considering duplicates in custom comparators
- In greedy+constraint pattern: forgetting to re-add
firstback to PQ in Case 1 (it was NOT consumed)
β Best Practices:
- Always check heap empty before peek/pop
- Use tuples for complex comparisons
- Maintain heap invariants after each operation
- Consider space-time trade-offs for large K
- Use heapify for batch initialization
Interview Tips
-
Clarify Requirements
- Ask about K relative to N
- Confirm if duplicates are allowed
- Check if online/offline algorithm needed
-
Optimize for K
- K small β Heap approach O(n log k)
- K β n β Quick select O(n) average
- K = 1 β Simple scan O(n)
-
Explain Trade-offs
- Heap: Good for streaming, K << n
- Sorting: Simple but O(n log n)
- Quick select: Best average case but unstable
-
Implementation Details
- Python: heapq is min-heap only
- Java: PriorityQueue with Comparator
- Remember heap is not sorted internally
Advanced Techniques
Indexed Priority Queue
- Allows decrease-key operation
- Useful for Dijkstra optimization
- Track element positions in heap
Fibonacci Heap
- O(1) amortized insert, decrease-key
- O(log n) extract-min
- Complex implementation, rarely used
Binary Heap Variants
- d-ary heap: Better cache performance
- Binomial heap: Better merge operation
- Pairing heap: Simple, good practical performance
Related Topics
- Binary Heap: Implementation details and properties
- Sorting: Heap sort algorithm
- Graph Algorithms: Dijkstra, Primβs MST
- Greedy Algorithms: Often use PQ for optimal selection
- Data Streams: Real-time processing with PQ
Classic LC Problems with Java Solutions
2-1) Kth Largest Element in an Array (LC 215)
// Java
// LC 215 - Find the kth largest element in an unsorted array
// IDEA: Use min-heap of size K
// Time: O(N log K), Space: O(K)
public int findKthLargest(int[] nums, int k) {
// Min heap - keeps k largest elements
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
for (int num : nums) {
minHeap.offer(num);
// Maintain size k
if (minHeap.size() > k) {
minHeap.poll(); // Remove smallest
}
}
// Top of heap is kth largest
return minHeap.peek();
}
// Alternative: Quick Select (O(N) average)
public int findKthLargest_QuickSelect(int[] nums, int k) {
// Convert to find (n-k)th smallest (0-indexed)
int targetIdx = nums.length - k;
return quickSelect(nums, 0, nums.length - 1, targetIdx);
}
private int quickSelect(int[] nums, int left, int right, int k) {
if (left == right) return nums[left];
int pivotIdx = partition(nums, left, right);
if (k == pivotIdx) {
return nums[k];
} else if (k < pivotIdx) {
return quickSelect(nums, left, pivotIdx - 1, k);
} else {
return quickSelect(nums, pivotIdx + 1, right, k);
}
}
private int partition(int[] nums, int left, int right) {
int pivot = nums[right];
int i = left;
for (int j = left; j < right; j++) {
if (nums[j] <= pivot) {
swap(nums, i, j);
i++;
}
}
swap(nums, i, right);
return i;
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
2-2) Top K Frequent Elements (LC 347)
// Java
// LC 347 - Return the k most frequent elements
// IDEA: HashMap for frequency + Min heap of size K
// Time: O(N log K), Space: O(N)
public int[] topKFrequent(int[] nums, int k) {
// Step 1: Count frequency
Map<Integer, Integer> freqMap = new HashMap<>();
for (int num : nums) {
freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
}
// Step 2: Min heap based on frequency (keep k largest frequencies)
PriorityQueue<Integer> minHeap = new PriorityQueue<>(
(a, b) -> freqMap.get(a) - freqMap.get(b)
);
for (int num : freqMap.keySet()) {
minHeap.offer(num);
if (minHeap.size() > k) {
minHeap.poll(); // Remove element with smallest frequency
}
}
// Step 3: Build result
int[] result = new int[k];
for (int i = k - 1; i >= 0; i--) {
result[i] = minHeap.poll();
}
return result;
}
// Alternative: Bucket Sort (O(N) time)
public int[] topKFrequent_BucketSort(int[] nums, int k) {
Map<Integer, Integer> freqMap = new HashMap<>();
for (int num : nums) {
freqMap.put(num, freqMap.getOrDefault(num, 0) + 1);
}
// Buckets: index = frequency, value = list of numbers with that frequency
List<Integer>[] buckets = new List[nums.length + 1];
for (int i = 0; i < buckets.length; i++) {
buckets[i] = new ArrayList<>();
}
for (Map.Entry<Integer, Integer> entry : freqMap.entrySet()) {
buckets[entry.getValue()].add(entry.getKey());
}
// Collect k most frequent from highest frequency bucket
int[] result = new int[k];
int idx = 0;
for (int i = buckets.length - 1; i >= 0 && idx < k; i--) {
for (int num : buckets[i]) {
result[idx++] = num;
if (idx == k) break;
}
}
return result;
}
2-3) Merge K Sorted Lists (LC 23)
// Java
// LC 23 - Merge k sorted linked lists
// IDEA: Min heap to always get the smallest node
// Time: O(N log K) where N = total nodes, K = number of lists
// Space: O(K) for the heap
public ListNode mergeKLists(ListNode[] lists) {
if (lists == null || lists.length == 0) {
return null;
}
// Min heap: compare by node value
PriorityQueue<ListNode> minHeap = new PriorityQueue<>(
(a, b) -> a.val - b.val
);
// Add first node of each list to heap
for (ListNode node : lists) {
if (node != null) {
minHeap.offer(node);
}
}
// Dummy head for result
ListNode dummy = new ListNode(0);
ListNode curr = dummy;
while (!minHeap.isEmpty()) {
// Get smallest node
ListNode smallest = minHeap.poll();
curr.next = smallest;
curr = curr.next;
// Add next node from same list
if (smallest.next != null) {
minHeap.offer(smallest.next);
}
}
return dummy.next;
}
// Alternative: Divide and Conquer
public ListNode mergeKLists_DivideConquer(ListNode[] lists) {
if (lists == null || lists.length == 0) return null;
return mergeRange(lists, 0, lists.length - 1);
}
private ListNode mergeRange(ListNode[] lists, int start, int end) {
if (start == end) return lists[start];
int mid = start + (end - start) / 2;
ListNode left = mergeRange(lists, start, mid);
ListNode right = mergeRange(lists, mid + 1, end);
return mergeTwoLists(left, right);
}
private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dummy = new ListNode(0);
ListNode curr = dummy;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
curr.next = l1;
l1 = l1.next;
} else {
curr.next = l2;
l2 = l2.next;
}
curr = curr.next;
}
curr.next = (l1 != null) ? l1 : l2;
return dummy.next;
}
2-4) Find Median from Data Stream (LC 295)
// Java
// LC 295 - Design a data structure to find median from a stream
// IDEA: Two heaps - max heap for smaller half, min heap for larger half
// Time: O(log N) for addNum, O(1) for findMedian
// Space: O(N)
class MedianFinder {
// Max heap for smaller half (largest of small at top)
private PriorityQueue<Integer> small;
// Min heap for larger half (smallest of large at top)
private PriorityQueue<Integer> large;
public MedianFinder() {
small = new PriorityQueue<>(Collections.reverseOrder());
large = new PriorityQueue<>();
}
public void addNum(int num) {
// Add to small (max heap) first
small.offer(num);
// Balance property: max of small <= min of large
if (!small.isEmpty() && !large.isEmpty() &&
small.peek() > large.peek()) {
large.offer(small.poll());
}
// Size property: sizes differ by at most 1
if (small.size() > large.size() + 1) {
large.offer(small.poll());
}
if (large.size() > small.size() + 1) {
small.offer(large.poll());
}
}
public double findMedian() {
if (small.size() > large.size()) {
return small.peek();
}
if (large.size() > small.size()) {
return large.peek();
}
// Equal sizes - average of two middle elements
return (small.peek() + large.peek()) / 2.0;
}
}
/**
* Dry Run Example:
* addNum(1): small=[1], large=[] β median=1
* addNum(2): small=[1], large=[2] β median=1.5
* addNum(3): small=[1], large=[2,3] β rebalance β small=[2,1], large=[3] β median=2
* addNum(4): small=[2,1], large=[3,4] β median=2.5
*/
2-5) Meeting Rooms II (LC 253)
// Java
// LC 253 - Find minimum number of conference rooms required
// IDEA: Sort by start time, use min heap to track end times
// Time: O(N log N), Space: O(N)
public int minMeetingRooms(int[][] intervals) {
if (intervals == null || intervals.length == 0) {
return 0;
}
// Sort by start time
Arrays.sort(intervals, (a, b) -> a[0] - b[0]);
// Min heap to track end times of ongoing meetings
PriorityQueue<Integer> endTimes = new PriorityQueue<>();
// First meeting
endTimes.offer(intervals[0][1]);
for (int i = 1; i < intervals.length; i++) {
int start = intervals[i][0];
int end = intervals[i][1];
// If current meeting starts after earliest ending meeting
// Reuse that room (remove from heap)
if (start >= endTimes.peek()) {
endTimes.poll();
}
// Add current meeting's end time
endTimes.offer(end);
}
// Heap size = number of rooms needed
return endTimes.size();
}
// Alternative: Two Arrays (Chronological Ordering)
public int minMeetingRooms_TwoArrays(int[][] intervals) {
int n = intervals.length;
int[] starts = new int[n];
int[] ends = new int[n];
for (int i = 0; i < n; i++) {
starts[i] = intervals[i][0];
ends[i] = intervals[i][1];
}
Arrays.sort(starts);
Arrays.sort(ends);
int rooms = 0;
int endPtr = 0;
for (int i = 0; i < n; i++) {
if (starts[i] < ends[endPtr]) {
rooms++; // Need new room
} else {
endPtr++; // Reuse room
}
}
return rooms;
}
2-6) Kth Largest Element in a Stream (LC 703)
// Java
// LC 703 - Design a class to find kth largest element in a stream
// IDEA: Min heap of size K - top is always kth largest
// Time: O(log K) for add, Space: O(K)
class KthLargest {
private PriorityQueue<Integer> minHeap;
private int k;
public KthLargest(int k, int[] nums) {
this.k = k;
this.minHeap = new PriorityQueue<>();
for (int num : nums) {
add(num);
}
}
public int add(int val) {
minHeap.offer(val);
// Maintain size k
if (minHeap.size() > k) {
minHeap.poll();
}
// Top of min heap is kth largest
return minHeap.peek();
}
}
/**
* Example:
* KthLargest kthLargest = new KthLargest(3, [4, 5, 8, 2]);
* Heap after init: [4, 5, 8] (size 3, sorted as min-heap)
*
* kthLargest.add(3); // heap=[4,5,8], return 4
* kthLargest.add(5); // heap=[5,5,8], return 5
* kthLargest.add(10); // heap=[5,8,10], return 5
* kthLargest.add(9); // heap=[8,9,10], return 8
* kthLargest.add(4); // heap=[8,9,10], return 8
*/
2-7) K Closest Points to Origin (LC 973)
// Java
// LC 973 - Find K closest points to origin (0,0)
// IDEA: Max heap of size K (to keep K smallest distances)
// Time: O(N log K), Space: O(K)
public int[][] kClosest(int[][] points, int k) {
// Max heap based on distance (squared, no need for sqrt)
PriorityQueue<int[]> maxHeap = new PriorityQueue<>(
(a, b) -> (b[0]*b[0] + b[1]*b[1]) - (a[0]*a[0] + a[1]*a[1])
);
for (int[] point : points) {
maxHeap.offer(point);
if (maxHeap.size() > k) {
maxHeap.poll(); // Remove farthest point
}
}
// Convert heap to result array
int[][] result = new int[k][2];
for (int i = 0; i < k; i++) {
result[i] = maxHeap.poll();
}
return result;
}
// Alternative: Min heap (push all, pop k)
public int[][] kClosest_MinHeap(int[][] points, int k) {
PriorityQueue<int[]> minHeap = new PriorityQueue<>(
(a, b) -> (a[0]*a[0] + a[1]*a[1]) - (b[0]*b[0] + b[1]*b[1])
);
for (int[] point : points) {
minHeap.offer(point);
}
int[][] result = new int[k][2];
for (int i = 0; i < k; i++) {
result[i] = minHeap.poll();
}
return result;
}
2-8) Kth Smallest Element in a Sorted Matrix (LC 378)
// Java
// LC 378 - Find kth smallest element in n x n sorted matrix
// IDEA: Min heap with (value, row, col)
// Time: O(K log N), Space: O(N)
public int kthSmallest(int[][] matrix, int k) {
int n = matrix.length;
// Min heap: [value, row, col]
PriorityQueue<int[]> minHeap = new PriorityQueue<>(
(a, b) -> a[0] - b[0]
);
// Add first element of each row
for (int i = 0; i < Math.min(n, k); i++) {
minHeap.offer(new int[]{matrix[i][0], i, 0});
}
int result = 0;
// Pop k times
for (int i = 0; i < k; i++) {
int[] curr = minHeap.poll();
result = curr[0];
int row = curr[1];
int col = curr[2];
// Add next element from same row
if (col + 1 < n) {
minHeap.offer(new int[]{matrix[row][col + 1], row, col + 1});
}
}
return result;
}
// Alternative: Binary Search
public int kthSmallest_BinarySearch(int[][] matrix, int k) {
int n = matrix.length;
int lo = matrix[0][0];
int hi = matrix[n - 1][n - 1];
while (lo < hi) {
int mid = lo + (hi - lo) / 2;
int count = countLessOrEqual(matrix, mid);
if (count < k) {
lo = mid + 1;
} else {
hi = mid;
}
}
return lo;
}
private int countLessOrEqual(int[][] matrix, int target) {
int n = matrix.length;
int count = 0;
int row = n - 1;
int col = 0;
// Start from bottom-left corner
while (row >= 0 && col < n) {
if (matrix[row][col] <= target) {
count += row + 1; // All elements in this column up to row
col++;
} else {
row--;
}
}
return count;
}
2-9) Task Scheduler (LC 621)
// Java
// LC 621 - Return minimum intervals to finish all tasks with cooldown
// IDEA: Greedy - always execute most frequent task
// Time: O(N), Space: O(1) - only 26 letters
public int leastInterval(char[] tasks, int n) {
// Count frequency of each task
int[] freq = new int[26];
for (char task : tasks) {
freq[task - 'A']++;
}
// Max heap for task frequencies
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());
for (int f : freq) {
if (f > 0) {
maxHeap.offer(f);
}
}
int time = 0;
while (!maxHeap.isEmpty()) {
List<Integer> temp = new ArrayList<>();
// Process n+1 tasks (cooldown cycle)
for (int i = 0; i <= n; i++) {
if (!maxHeap.isEmpty()) {
int f = maxHeap.poll();
if (f > 1) {
temp.add(f - 1); // Task still has remaining count
}
}
time++;
// If all tasks done, break
if (maxHeap.isEmpty() && temp.isEmpty()) {
break;
}
}
// Add back remaining tasks
for (int f : temp) {
maxHeap.offer(f);
}
}
return time;
}
// Mathematical Approach (O(N) time, O(1) space)
public int leastInterval_Math(char[] tasks, int n) {
int[] freq = new int[26];
int maxFreq = 0;
int maxCount = 0;
for (char task : tasks) {
freq[task - 'A']++;
if (freq[task - 'A'] > maxFreq) {
maxFreq = freq[task - 'A'];
maxCount = 1;
} else if (freq[task - 'A'] == maxFreq) {
maxCount++;
}
}
// (maxFreq - 1) full cycles + last partial cycle
int partCount = (maxFreq - 1) * (n + 1) + maxCount;
// Result is max of calculated intervals or total tasks
return Math.max(partCount, tasks.length);
}
2-10) Reorganize String (LC 767)
// Java
// LC 767 - Rearrange string so no adjacent characters are same
// IDEA: Max heap - always pick most frequent, alternate placement
// Time: O(N log 26) = O(N), Space: O(26) = O(1)
public String reorganizeString(String s) {
// Count frequency
int[] freq = new int[26];
for (char c : s.toCharArray()) {
freq[c - 'a']++;
}
// Check if possible: no char should appear more than (n+1)/2 times
int n = s.length();
for (int f : freq) {
if (f > (n + 1) / 2) {
return "";
}
}
// Max heap: [frequency, char]
PriorityQueue<int[]> maxHeap = new PriorityQueue<>(
(a, b) -> b[0] - a[0]
);
for (int i = 0; i < 26; i++) {
if (freq[i] > 0) {
maxHeap.offer(new int[]{freq[i], i});
}
}
StringBuilder sb = new StringBuilder();
while (maxHeap.size() >= 2) {
// Take two most frequent characters
int[] first = maxHeap.poll();
int[] second = maxHeap.poll();
sb.append((char) (first[1] + 'a'));
sb.append((char) (second[1] + 'a'));
// Put back if remaining
if (--first[0] > 0) maxHeap.offer(first);
if (--second[0] > 0) maxHeap.offer(second);
}
// Handle last character if any
if (!maxHeap.isEmpty()) {
sb.append((char) (maxHeap.poll()[1] + 'a'));
}
return sb.toString();
}
2-11) Sliding Window Median (LC 480)
// Java
// LC 480 - Return median of each sliding window of size k
// IDEA: Two heaps (TreeMap/Multiset for lazy removal)
// Time: O(N log K), Space: O(K)
public double[] medianSlidingWindow(int[] nums, int k) {
// Use TreeMap to support removal
TreeMap<Integer, Integer> small = new TreeMap<>(Collections.reverseOrder()); // max heap
TreeMap<Integer, Integer> large = new TreeMap<>(); // min heap
int smallSize = 0, largeSize = 0;
double[] result = new double[nums.length - k + 1];
for (int i = 0; i < nums.length; i++) {
// Add to appropriate heap
if (small.isEmpty() || nums[i] <= small.firstKey()) {
add(small, nums[i]);
smallSize++;
} else {
add(large, nums[i]);
largeSize++;
}
// Rebalance
while (smallSize > largeSize + 1) {
int val = small.firstKey();
remove(small, val);
smallSize--;
add(large, val);
largeSize++;
}
while (largeSize > smallSize) {
int val = large.firstKey();
remove(large, val);
largeSize--;
add(small, val);
smallSize++;
}
// Window is full
if (i >= k - 1) {
// Calculate median
if (k % 2 == 1) {
result[i - k + 1] = small.firstKey();
} else {
result[i - k + 1] = ((double) small.firstKey() + large.firstKey()) / 2.0;
}
// Remove element leaving window
int toRemove = nums[i - k + 1];
if (toRemove <= small.firstKey()) {
remove(small, toRemove);
smallSize--;
} else {
remove(large, toRemove);
largeSize--;
}
}
}
return result;
}
private void add(TreeMap<Integer, Integer> map, int val) {
map.put(val, map.getOrDefault(val, 0) + 1);
}
private void remove(TreeMap<Integer, Integer> map, int val) {
int count = map.get(val);
if (count == 1) {
map.remove(val);
} else {
map.put(val, count - 1);
}
}
2-12) Ugly Number II (LC 264)
// Java
// LC 264 - Find nth ugly number (only prime factors 2, 3, 5)
// IDEA: Min heap to generate ugly numbers in order
// Time: O(N log N), Space: O(N)
public int nthUglyNumber(int n) {
PriorityQueue<Long> minHeap = new PriorityQueue<>();
Set<Long> seen = new HashSet<>();
minHeap.offer(1L);
seen.add(1L);
int[] primes = {2, 3, 5};
long ugly = 1;
for (int i = 0; i < n; i++) {
ugly = minHeap.poll();
for (int prime : primes) {
long next = ugly * prime;
if (!seen.contains(next)) {
seen.add(next);
minHeap.offer(next);
}
}
}
return (int) ugly;
}
// Three Pointers Approach (O(N) time, O(N) space)
public int nthUglyNumber_ThreePointers(int n) {
int[] ugly = new int[n];
ugly[0] = 1;
int p2 = 0, p3 = 0, p5 = 0;
for (int i = 1; i < n; i++) {
int next2 = ugly[p2] * 2;
int next3 = ugly[p3] * 3;
int next5 = ugly[p5] * 5;
int next = Math.min(next2, Math.min(next3, next5));
ugly[i] = next;
// Move pointers (handle duplicates)
if (next == next2) p2++;
if (next == next3) p3++;
if (next == next5) p5++;
}
return ugly[n - 1];
}
2-13) Network Delay Time (LC 743 - Dijkstra)
// Java
// LC 743 - Find time for all nodes to receive signal
// IDEA: Dijkstra's shortest path algorithm
// Time: O(E log V), Space: O(V + E)
public int networkDelayTime(int[][] times, int n, int k) {
// Build adjacency list
Map<Integer, List<int[]>> graph = new HashMap<>();
for (int[] time : times) {
graph.computeIfAbsent(time[0], x -> new ArrayList<>())
.add(new int[]{time[1], time[2]});
}
// Min heap: [distance, node]
PriorityQueue<int[]> minHeap = new PriorityQueue<>(
(a, b) -> a[0] - b[0]
);
int[] dist = new int[n + 1];
Arrays.fill(dist, Integer.MAX_VALUE);
dist[k] = 0;
minHeap.offer(new int[]{0, k});
while (!minHeap.isEmpty()) {
int[] curr = minHeap.poll();
int d = curr[0];
int node = curr[1];
// Skip if already processed with shorter distance
if (d > dist[node]) continue;
if (graph.containsKey(node)) {
for (int[] edge : graph.get(node)) {
int neighbor = edge[0];
int weight = edge[1];
int newDist = d + weight;
if (newDist < dist[neighbor]) {
dist[neighbor] = newDist;
minHeap.offer(new int[]{newDist, neighbor});
}
}
}
}
// Find max distance (time for all nodes to receive)
int maxDist = 0;
for (int i = 1; i <= n; i++) {
if (dist[i] == Integer.MAX_VALUE) {
return -1; // Unreachable node
}
maxDist = Math.max(maxDist, dist[i]);
}
return maxDist;
}
2-14) Sort Characters By Frequency (LC 451)
// Java
// LC 451 - Sort characters in string by frequency (descending)
// IDEA: Count frequency, use max heap to build result
// Time: O(N log K) where K = unique chars, Space: O(N)
public String frequencySort(String s) {
// Count frequency
Map<Character, Integer> freq = new HashMap<>();
for (char c : s.toCharArray()) {
freq.put(c, freq.getOrDefault(c, 0) + 1);
}
// Max heap based on frequency
PriorityQueue<Character> maxHeap = new PriorityQueue<>(
(a, b) -> freq.get(b) - freq.get(a)
);
maxHeap.addAll(freq.keySet());
// Build result
StringBuilder sb = new StringBuilder();
while (!maxHeap.isEmpty()) {
char c = maxHeap.poll();
int count = freq.get(c);
for (int i = 0; i < count; i++) {
sb.append(c);
}
}
return sb.toString();
}
// Bucket Sort Alternative (O(N) time)
public String frequencySort_Bucket(String s) {
Map<Character, Integer> freq = new HashMap<>();
for (char c : s.toCharArray()) {
freq.put(c, freq.getOrDefault(c, 0) + 1);
}
// Bucket: index = frequency
List<Character>[] buckets = new List[s.length() + 1];
for (int i = 0; i < buckets.length; i++) {
buckets[i] = new ArrayList<>();
}
for (Map.Entry<Character, Integer> entry : freq.entrySet()) {
buckets[entry.getValue()].add(entry.getKey());
}
StringBuilder sb = new StringBuilder();
for (int i = buckets.length - 1; i >= 0; i--) {
for (char c : buckets[i]) {
for (int j = 0; j < i; j++) {
sb.append(c);
}
}
}
return sb.toString();
}
2-15) Last Stone Weight (LC 1046)
// Java
// LC 1046 - Smash two heaviest stones, return remaining weight
// IDEA: Max heap - always get two largest
// Time: O(N log N), Space: O(N)
public int lastStoneWeight(int[] stones) {
// Max heap
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(
Collections.reverseOrder()
);
for (int stone : stones) {
maxHeap.offer(stone);
}
while (maxHeap.size() > 1) {
int stone1 = maxHeap.poll(); // Heaviest
int stone2 = maxHeap.poll(); // Second heaviest
if (stone1 != stone2) {
maxHeap.offer(stone1 - stone2); // Remaining weight
}
// If equal, both destroyed
}
return maxHeap.isEmpty() ? 0 : maxHeap.peek();
}
Quick Reference: PQ Pattern β Problem Mapping
| Pattern | Classic Problems | Key Insight |
|---|---|---|
| Top K | LC 215, 347, 692, 973, 1046 | Min heap size K for K largest |
| K-Way Merge | LC 23, 378, 373 | Track (value, source_index) |
| Two Heaps | LC 295, 480 | Max heap for small, min heap for large |
| Scheduling | LC 253, 621, 767 | Sort by start/priority, heap for end times |
| Dijkstra | LC 743, 787, 1514, 1631 | Min heap with (distance, node) |
| Stream | LC 703, 295, 346 | Fixed size heap or two heaps |
| Ugly Numbers | LC 264, 313, 373 | Generate in sorted order |
| Greedy+Constraint | LC 1405, 767, 621, 358, 1054 | Max-heap + two-case loop (use 2nd if constraint violated, put 1st back) |
LC Examples
2-1) Kth Largest Element in a Stream (LC 703) β Min-Heap of Size K
Maintain a min-heap of size k; the top is always the kth largest element.
// LC 703 - Kth Largest Element in a Stream
// IDEA: Min-heap of size k β top = kth largest
// time = O(N log k), space = O(k)
class KthLargest {
PriorityQueue<Integer> heap;
int k;
public KthLargest(int k, int[] nums) {
this.k = k;
this.heap = new PriorityQueue<>();
for (int num : nums) add(num);
}
public int add(int val) {
heap.offer(val);
if (heap.size() > k) heap.poll();
return heap.peek();
}
}
2-2) Top K Frequent Elements (LC 347) β Min-Heap with Frequency
Count frequencies with HashMap, then maintain min-heap of size k by frequency.
// LC 347 - Top K Frequent Elements
// IDEA: HashMap for frequency + min-heap of size k ordered by frequency
// time = O(N log k), space = O(N)
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> freq = new HashMap<>();
for (int num : nums) freq.merge(num, 1, Integer::sum);
PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> freq.get(a) - freq.get(b));
for (int key : freq.keySet()) {
pq.offer(key);
if (pq.size() > k) pq.poll();
}
int[] ans = new int[k];
for (int i = k - 1; i >= 0; i--) ans[i] = pq.poll();
return ans;
}
2-3) Merge K Sorted Lists (LC 23) β Min-Heap
Use min-heap to always extract the global minimum node across all lists.
// LC 23 - Merge K Sorted Lists
// IDEA: Min-heap ordered by node value; pop min, push its next
// time = O(N log k), space = O(k) N = total nodes, k = number of lists
public ListNode mergeKLists(ListNode[] lists) {
PriorityQueue<ListNode> pq = new PriorityQueue<>((a, b) -> a.val - b.val);
for (ListNode node : lists) if (node != null) pq.offer(node);
ListNode dummy = new ListNode(0), curr = dummy;
while (!pq.isEmpty()) {
curr.next = pq.poll();
curr = curr.next;
if (curr.next != null) pq.offer(curr.next);
}
return dummy.next;
}