Heaps & Priority Queues: Hướng Dẫn Toàn Diện

Giới Thiệu
Heap là cấu trúc dữ liệu bạn sẽ dùng mỗi khi cần truy cập nhanh phần tử nhỏ nhất hoặc lớn nhất trong một collection. Nó đứng đằng sau mọi thứ từ giải thuật Dijkstra tìm đường đi ngắn nhất (mà bạn đã thấy trong Graphs & Graph Algorithms) đến task scheduler của hệ điều hành, event-driven simulations, và pattern "top K" thống trị các câu hỏi phỏng vấn.
Khác với Binary Search Tree — nơi mọi node duy trì thứ tự nghiêm ngặt left < parent < right — heap chỉ đảm bảo rằng root là phần tử nhỏ nhất (hoặc lớn nhất). Ràng buộc nhẹ nhàng này chính là thứ làm heap hiệu quả đến vậy: insert và extract chạy trong O(log n), còn peek phần tử đầu là O(1).
Trong coding interviews, các bài toán heap xuất hiện trong khoảng 20-25% các buổi phỏng vấn kỹ thuật — và chúng thường kết hợp với các pattern khác (graphs, sliding windows, greedy algorithms). Tin vui là: API của heap rất nhỏ gọn, và khi bạn nhận ra pattern rồi thì solution đến rất tự nhiên.
Bạn Sẽ Học Được Gì
✅ Hiểu heap properties, các loại heap, và cách biểu diễn bằng array
✅ Cài đặt MinHeap và MaxHeap từ đầu
✅ Nắm vững các thao tác heap: insert, extract, heapify
✅ Xây dựng Priority Queue dùng heap
✅ Cài đặt giải thuật Heap Sort
✅ Biết khi nào dùng heap vs BST vs sorted array
✅ Nhận diện các heap pattern trong bài toán phỏng vấn
✅ Giải 8 bài toán heap kinh điển từng bước
Yêu Cầu Trước
- Hiểu về Ký hiệu Big O & Phân tích Độ phức tạp
- Quen thuộc với Các Pattern Giải Quyết Vấn Đề
- Hoàn thành Trees & Binary Search Trees (heap là cây chuyên biệt)
- Kiến thức lập trình cơ bản với TypeScript hoặc Python
1. Nền Tảng Về Heap
1.1 Heap Là Gì?
Heap là một complete binary tree thỏa mãn heap property — mọi node cha đều được sắp xếp tương đối với các node con. Cây luôn được lấp đầy từng level, từ trái sang phải, không có khoảng trống.
Hai property chính biến một cây thành heap:
- Shape property: Cây là complete binary tree — mọi level được lấp đầy, ngoại trừ level cuối có thể chỉ lấp từ trái sang phải.
- Heap property: Mọi parent nhỏ hơn (min heap) hoặc lớn hơn (max heap) các children.
1.2 Min Heap vs Max Heap
| Property | Min Heap | Max Heap |
|---|---|---|
| Root | Phần tử nhỏ nhất | Phần tử lớn nhất |
| Quy tắc parent-child | Parent ≤ Children | Parent ≥ Children |
| Peek min | O(1) | O(n) |
| Peek max | O(n) | O(1) |
| Dùng phổ biến | Priority queues, Dijkstra | Heap sort, max-priority queues |
Quan trọng: Heap không đảm bảo thứ tự giữa các siblings hoặc giữa các level — chỉ giữa parent và child. Phần tử nhỏ thứ hai có thể nằm ở bất kỳ đâu trong level thứ hai.
1.3 Biểu Diễn Bằng Array
Cách hiệu quả nhất để lưu heap là trong một mảng phẳng. Vì cây là complete, không có khoảng trống — mỗi index map chính xác với một node.
Với node tại index i (bắt đầu từ 0):
| Quan hệ | Công thức |
|---|---|
| Parent | Math.floor((i - 1) / 2) |
| Left child | 2 * i + 1 |
| Right child | 2 * i + 2 |
Array: [1, 3, 2, 7, 6, 4, 5]
Index: 0 1 2 3 4 5 6
Ánh xạ sang cây:
1 (i=0)
/ \
3 2
(i=1) (i=2)
/ \ / \
7 6 4 5
(3) (4) (5) (6)Tại sao dùng array?
- Không có overhead pointer (không cần tham chiếu left/right)
- Cache-friendly — bộ nhớ liên tục
- Tính toán index là O(1)
1.4 Ứng Dụng Thực Tế
| Ứng dụng | Loại Heap | Tại sao |
|---|---|---|
| Giải thuật Dijkstra | Min heap | Luôn mở rộng node chưa thăm có chi phí thấp nhất |
| Task scheduler | Min/Max heap | Thực thi task có priority cao nhất trước |
| Event-driven simulation | Min heap | Xử lý event sớm nhất trước |
| Heap sort | Max heap | Sắp xếp in-place O(n log n) |
| Duy trì median | Hai heaps | Theo dõi running median hiệu quả |
| Merge K sorted lists | Min heap | Luôn chọn phần tử nhỏ nhất từ đầu tất cả lists |
| Top-K elements | Min heap (size K) | Duy trì K phần tử lớn nhất đã thấy |
| Huffman coding | Min heap | Xây dựng mã prefix tối ưu |
2. Các Thao Tác Trên Heap
Mỗi heap hỗ trợ ba thao tác chính: insert, extract, và heapify. Cả ba đều dựa vào hai thủ tục helper — siftUp và siftDown.
2.1 Sift Up (Đẩy Lên)
Sau khi chèn phần tử mới vào cuối mảng, nó có thể vi phạm heap property. Sift up hoán đổi phần tử với parent liên tục cho đến khi property được khôi phục.
Các bước:
- Bắt đầu tại phần tử mới chèn
- So sánh với parent
- Nếu sai thứ tự, hoán đổi với parent
- Lặp lại cho đến khi phần tử đạt đúng vị trí hoặc đến root
Time: O(log n) — tối đa bằng chiều cao cây.
2.2 Sift Down (Đẩy Xuống)
Sau khi extract root (và thay bằng phần tử cuối), root mới có thể vi phạm heap property. Sift down hoán đổi phần tử với child nhỏ hơn (min heap) hoặc child lớn hơn (max heap) liên tục.
Các bước:
- Bắt đầu tại root
- So sánh với cả hai children
- Nếu sai thứ tự, hoán đổi với child nhỏ hơn/lớn hơn
- Lặp lại cho đến khi phần tử đạt đúng vị trí hoặc là leaf
Time: O(log n) — tối đa bằng chiều cao cây.
2.3 Insert
- Thêm phần tử vào cuối mảng (giữ shape property)
- Sift up để khôi phục heap property
Insert 0 vào min heap [1, 3, 2, 7, 6, 4, 5]:
Bước 1: Thêm vào cuối → [1, 3, 2, 7, 6, 4, 5, 0]
Bước 2: Sift up 0
- 0 < 7 (parent)? Có → swap → [1, 3, 2, 0, 6, 4, 5, 7]
- 0 < 3 (parent)? Có → swap → [1, 0, 2, 3, 6, 4, 5, 7]
- 0 < 1 (parent)? Có → swap → [0, 1, 2, 3, 6, 4, 5, 7]
- Đã ở root → xong2.4 Extract Min/Max
- Lưu root (phần tử min hoặc max)
- Di chuyển phần tử cuối lên root
- Sift down để khôi phục heap property
- Trả về phần tử đã lưu
Extract min từ [1, 3, 2, 7, 6, 4, 5]:
Bước 1: Lưu 1, đưa phần tử cuối lên root → [5, 3, 2, 7, 6, 4]
Bước 2: Sift down 5
- Children: 3, 2. Child nhỏ hơn: 2 → 5 > 2? Có → swap → [2, 3, 5, 7, 6, 4]
- Children: 4. Child nhỏ hơn: 4 → 5 > 4? Có → swap → [2, 3, 4, 7, 6, 5]
- Không có children → xong
Kết quả: 12.5 Build Heap (Heapify)
Cho một mảng chưa sắp xếp, xây dựng heap hợp lệ tại chỗ.
Cách naïve: Insert từng phần tử → O(n log n).
Cách tối ưu (giải thuật Floyd): Bắt đầu từ node non-leaf cuối cùng và sift down mỗi node. Cách này chạy trong O(n) — không phải O(n log n) — vì hầu hết nodes nằm gần đáy và sift rất ít.
Tại sao O(n)? Khoảng nửa số nodes là leaf (không cần sift), một phần tư sift tối đa 1 level, một phần tám sift tối đa 2 levels, và cứ thế. Tổng hội tụ về O(n).
2.6 Tóm Tắt Complexity
| Thao tác | Time | Space |
|---|---|---|
| Insert | O(log n) | O(1) |
| Extract Min/Max | O(log n) | O(1) |
| Peek Min/Max | O(1) | O(1) |
| Build Heap | O(n) | O(1) |
| Search | O(n) | O(1) |
| Xóa tùy ý | O(n) tìm + O(log n) xóa | O(1) |
3. Cài Đặt: MinHeap
3.1 Cài Đặt TypeScript
// TypeScript - MinHeap Implementation
class MinHeap {
private heap: number[] = [];
get size(): number {
return this.heap.length;
}
peek(): number | undefined {
return this.heap[0];
}
insert(val: number): void {
this.heap.push(val);
this.siftUp(this.heap.length - 1);
}
extractMin(): number | undefined {
if (this.heap.length === 0) return undefined;
if (this.heap.length === 1) return this.heap.pop();
const min = this.heap[0];
this.heap[0] = this.heap.pop()!;
this.siftDown(0);
return min;
}
private siftUp(i: number): void {
while (i > 0) {
const parent = Math.floor((i - 1) / 2);
if (this.heap[i] >= this.heap[parent]) break;
[this.heap[i], this.heap[parent]] = [this.heap[parent], this.heap[i]];
i = parent;
}
}
private siftDown(i: number): void {
const n = this.heap.length;
while (true) {
let smallest = i;
const left = 2 * i + 1;
const right = 2 * i + 2;
if (left < n && this.heap[left] < this.heap[smallest]) {
smallest = left;
}
if (right < n && this.heap[right] < this.heap[smallest]) {
smallest = right;
}
if (smallest === i) break;
[this.heap[i], this.heap[smallest]] = [this.heap[smallest], this.heap[i]];
i = smallest;
}
}
// Build heap from array in O(n)
static fromArray(arr: number[]): MinHeap {
const heap = new MinHeap();
heap.heap = [...arr];
// Bắt đầu từ node non-leaf cuối cùng
for (let i = Math.floor(arr.length / 2) - 1; i >= 0; i--) {
heap.siftDown(i);
}
return heap;
}
}
// Sử dụng
const heap = new MinHeap();
heap.insert(5);
heap.insert(3);
heap.insert(8);
heap.insert(1);
console.log(heap.peek()); // 1
console.log(heap.extractMin()); // 1
console.log(heap.extractMin()); // 3
console.log(heap.size); // 2
// Build từ array
const heap2 = MinHeap.fromArray([7, 3, 9, 1, 5, 2]);
console.log(heap2.extractMin()); // 1
console.log(heap2.extractMin()); // 2
console.log(heap2.extractMin()); // 33.2 Cài Đặt Python
# Python - MinHeap Implementation
class MinHeap:
def __init__(self):
self.heap: list[int] = []
@property
def size(self) -> int:
return len(self.heap)
def peek(self) -> int | None:
return self.heap[0] if self.heap else None
def insert(self, val: int) -> None:
self.heap.append(val)
self._sift_up(len(self.heap) - 1)
def extract_min(self) -> int | None:
if not self.heap:
return None
if len(self.heap) == 1:
return self.heap.pop()
min_val = self.heap[0]
self.heap[0] = self.heap.pop()
self._sift_down(0)
return min_val
def _sift_up(self, i: int) -> None:
while i > 0:
parent = (i - 1) // 2
if self.heap[i] >= self.heap[parent]:
break
self.heap[i], self.heap[parent] = self.heap[parent], self.heap[i]
i = parent
def _sift_down(self, i: int) -> None:
n = len(self.heap)
while True:
smallest = i
left = 2 * i + 1
right = 2 * i + 2
if left < n and self.heap[left] < self.heap[smallest]:
smallest = left
if right < n and self.heap[right] < self.heap[smallest]:
smallest = right
if smallest == i:
break
self.heap[i], self.heap[smallest] = self.heap[smallest], self.heap[i]
i = smallest
@classmethod
def from_array(cls, arr: list[int]) -> "MinHeap":
heap = cls()
heap.heap = arr[:]
for i in range(len(arr) // 2 - 1, -1, -1):
heap._sift_down(i)
return heap
# Sử dụng
heap = MinHeap()
heap.insert(5)
heap.insert(3)
heap.insert(8)
heap.insert(1)
print(heap.peek()) # 1
print(heap.extract_min()) # 1
print(heap.extract_min()) # 3
print(heap.size) # 2
# Build từ array
heap2 = MinHeap.from_array([7, 3, 9, 1, 5, 2])
print(heap2.extract_min()) # 1
print(heap2.extract_min()) # 2
print(heap2.extract_min()) # 34. Cài Đặt: MaxHeap
MaxHeap giống hệt MinHeap nhưng với phép so sánh đảo ngược — parent phải lớn hơn hoặc bằng children.
4.1 Cài Đặt TypeScript
// TypeScript - MaxHeap Implementation
class MaxHeap {
private heap: number[] = [];
get size(): number {
return this.heap.length;
}
peek(): number | undefined {
return this.heap[0];
}
insert(val: number): void {
this.heap.push(val);
this.siftUp(this.heap.length - 1);
}
extractMax(): number | undefined {
if (this.heap.length === 0) return undefined;
if (this.heap.length === 1) return this.heap.pop();
const max = this.heap[0];
this.heap[0] = this.heap.pop()!;
this.siftDown(0);
return max;
}
private siftUp(i: number): void {
while (i > 0) {
const parent = Math.floor((i - 1) / 2);
if (this.heap[i] <= this.heap[parent]) break; // So sánh đảo ngược
[this.heap[i], this.heap[parent]] = [this.heap[parent], this.heap[i]];
i = parent;
}
}
private siftDown(i: number): void {
const n = this.heap.length;
while (true) {
let largest = i;
const left = 2 * i + 1;
const right = 2 * i + 2;
if (left < n && this.heap[left] > this.heap[largest]) {
largest = left;
}
if (right < n && this.heap[right] > this.heap[largest]) {
largest = right;
}
if (largest === i) break;
[this.heap[i], this.heap[largest]] = [this.heap[largest], this.heap[i]];
i = largest;
}
}
}
// Sử dụng
const maxHeap = new MaxHeap();
maxHeap.insert(5);
maxHeap.insert(3);
maxHeap.insert(8);
maxHeap.insert(1);
console.log(maxHeap.peek()); // 8
console.log(maxHeap.extractMax()); // 8
console.log(maxHeap.extractMax()); // 54.2 Cài Đặt Python
# Python - MaxHeap Implementation
class MaxHeap:
def __init__(self):
self.heap: list[int] = []
@property
def size(self) -> int:
return len(self.heap)
def peek(self) -> int | None:
return self.heap[0] if self.heap else None
def insert(self, val: int) -> None:
self.heap.append(val)
self._sift_up(len(self.heap) - 1)
def extract_max(self) -> int | None:
if not self.heap:
return None
if len(self.heap) == 1:
return self.heap.pop()
max_val = self.heap[0]
self.heap[0] = self.heap.pop()
self._sift_down(0)
return max_val
def _sift_up(self, i: int) -> None:
while i > 0:
parent = (i - 1) // 2
if self.heap[i] <= self.heap[parent]: # So sánh đảo ngược
break
self.heap[i], self.heap[parent] = self.heap[parent], self.heap[i]
i = parent
def _sift_down(self, i: int) -> None:
n = len(self.heap)
while True:
largest = i
left = 2 * i + 1
right = 2 * i + 2
if left < n and self.heap[left] > self.heap[largest]:
largest = left
if right < n and self.heap[right] > self.heap[largest]:
largest = right
if largest == i:
break
self.heap[i], self.heap[largest] = self.heap[largest], self.heap[i]
i = largest
# Sử dụng
max_heap = MaxHeap()
max_heap.insert(5)
max_heap.insert(3)
max_heap.insert(8)
max_heap.insert(1)
print(max_heap.peek()) # 8
print(max_heap.extract_max()) # 8
print(max_heap.extract_max()) # 5Mẹo pro: Trong Python, bạn có thể giả lập max heap bằng module heapq sẵn có bằng cách đảo dấu giá trị:
# Python - Max heap dùng heapq (trick đảo dấu)
import heapq
max_heap = []
heapq.heappush(max_heap, -5)
heapq.heappush(max_heap, -3)
heapq.heappush(max_heap, -8)
print(-heapq.heappop(max_heap)) # 8 (lớn nhất)
print(-heapq.heappop(max_heap)) # 55. Priority Queue
Priority queue là kiểu dữ liệu trừu tượng mà mỗi phần tử có một priority, và các phần tử được dequeue theo thứ tự priority — không phải FIFO như queue thông thường.
Heap là cách cài đặt tiêu chuẩn cho priority queue vì nó cung cấp O(log n) insert và O(log n) extract trong khi duy trì thứ tự priority.
5.1 Cài Đặt TypeScript
// TypeScript - Priority Queue (Min Priority = highest priority)
interface PQItem<T> {
value: T;
priority: number;
}
class PriorityQueue<T> {
private heap: PQItem<T>[] = [];
get size(): number {
return this.heap.length;
}
isEmpty(): boolean {
return this.heap.length === 0;
}
enqueue(value: T, priority: number): void {
this.heap.push({ value, priority });
this.siftUp(this.heap.length - 1);
}
dequeue(): T | undefined {
if (this.heap.length === 0) return undefined;
if (this.heap.length === 1) return this.heap.pop()!.value;
const top = this.heap[0].value;
this.heap[0] = this.heap.pop()!;
this.siftDown(0);
return top;
}
peek(): T | undefined {
return this.heap[0]?.value;
}
private siftUp(i: number): void {
while (i > 0) {
const parent = Math.floor((i - 1) / 2);
if (this.heap[i].priority >= this.heap[parent].priority) break;
[this.heap[i], this.heap[parent]] = [this.heap[parent], this.heap[i]];
i = parent;
}
}
private siftDown(i: number): void {
const n = this.heap.length;
while (true) {
let smallest = i;
const left = 2 * i + 1;
const right = 2 * i + 2;
if (left < n && this.heap[left].priority < this.heap[smallest].priority) {
smallest = left;
}
if (right < n && this.heap[right].priority < this.heap[smallest].priority) {
smallest = right;
}
if (smallest === i) break;
[this.heap[i], this.heap[smallest]] = [this.heap[smallest], this.heap[i]];
i = smallest;
}
}
}
// Sử dụng
const pq = new PriorityQueue<string>();
pq.enqueue("low priority task", 10);
pq.enqueue("critical bug", 1);
pq.enqueue("medium task", 5);
console.log(pq.dequeue()); // "critical bug" (priority 1)
console.log(pq.dequeue()); // "medium task" (priority 5)
console.log(pq.dequeue()); // "low priority task" (priority 10)5.2 Cài Đặt Python
# Python - Priority Queue dùng heapq
import heapq
from dataclasses import dataclass, field
from typing import Any
@dataclass(order=True)
class PQItem:
priority: int
value: Any = field(compare=False)
class PriorityQueue:
def __init__(self):
self.heap: list[PQItem] = []
@property
def size(self) -> int:
return len(self.heap)
def is_empty(self) -> bool:
return len(self.heap) == 0
def enqueue(self, value: Any, priority: int) -> None:
heapq.heappush(self.heap, PQItem(priority, value))
def dequeue(self) -> Any | None:
if not self.heap:
return None
return heapq.heappop(self.heap).value
def peek(self) -> Any | None:
return self.heap[0].value if self.heap else None
# Sử dụng
pq = PriorityQueue()
pq.enqueue("low priority task", 10)
pq.enqueue("critical bug", 1)
pq.enqueue("medium task", 5)
print(pq.dequeue()) # "critical bug" (priority 1)
print(pq.dequeue()) # "medium task" (priority 5)
print(pq.dequeue()) # "low priority task" (priority 10)5.3 Queue vs Priority Queue
| Feature | Queue (FIFO) | Priority Queue |
|---|---|---|
| Thứ tự | Vào trước, Ra trước | Priority cao nhất trước |
| Enqueue | O(1) | O(log n) |
| Dequeue | O(1) | O(log n) |
| Use case | BFS, task scheduling (công bằng) | Dijkstra, task scheduling (ưu tiên) |
| Cài đặt | Array / Linked list | Heap |
6. Module heapq Của Python
Python cung cấp min heap sẵn có qua module heapq. Nó thao tác trực tiếp trên list thông thường — không cần class đặc biệt.
# Python - heapq module essentials
import heapq
# Tạo heap từ list (tại chỗ, O(n))
nums = [7, 3, 9, 1, 5, 2]
heapq.heapify(nums)
print(nums) # [1, 3, 2, 7, 5, 9] — min heap hợp lệ
# Push và pop
heapq.heappush(nums, 0)
print(heapq.heappop(nums)) # 0
# Push và pop cùng lúc (hiệu quả hơn)
result = heapq.heapreplace(nums, 10) # Pop nhỏ nhất, rồi push 10
print(result) # 1
# Lấy K nhỏ nhất / lớn nhất
data = [7, 3, 9, 1, 5, 2, 8, 4, 6]
print(heapq.nsmallest(3, data)) # [1, 2, 3]
print(heapq.nlargest(3, data)) # [9, 8, 7]
# Heap với tuples (sắp xếp theo phần tử đầu)
tasks = []
heapq.heappush(tasks, (2, "medium task"))
heapq.heappush(tasks, (1, "urgent task"))
heapq.heappush(tasks, (3, "low task"))
print(heapq.heappop(tasks)) # (1, "urgent task")Các hàm chính:
| Hàm | Mô tả | Time |
|---|---|---|
heapify(list) | Chuyển list thành heap tại chỗ | O(n) |
heappush(heap, item) | Thêm item vào heap | O(log n) |
heappop(heap) | Lấy item nhỏ nhất ra | O(log n) |
heapreplace(heap, item) | Pop rồi push (hiệu quả) | O(log n) |
heappushpop(heap, item) | Push rồi pop (hiệu quả) | O(log n) |
nsmallest(k, iterable) | Trả về K phần tử nhỏ nhất | O(n log k) |
nlargest(k, iterable) | Trả về K phần tử lớn nhất | O(n log k) |
Lưu ý: JavaScript / TypeScript không có heap sẵn có. Bạn phải tự cài đặt hoặc dùng thư viện.
7. Heap Sort
Heap sort dùng max heap để sắp xếp mảng tại chỗ với O(n log n) time và O(1) space thừa.
Giải thuật:
- Build max heap từ mảng — O(n)
- Lặp lại extract max và đặt vào cuối — O(n log n)
- Hoán đổi root (max) với phần tử cuối
- Giảm heap size đi 1
- Sift down root mới
7.1 Cài Đặt TypeScript
// TypeScript - Heap Sort
function heapSort(arr: number[]): number[] {
const n = arr.length;
// Build max heap - bắt đầu từ node non-leaf cuối
for (let i = Math.floor(n / 2) - 1; i >= 0; i--) {
siftDown(arr, n, i);
}
// Extract từng phần tử
for (let end = n - 1; end > 0; end--) {
// Đưa root hiện tại (max) ra cuối
[arr[0], arr[end]] = [arr[end], arr[0]];
// Sift down trên heap đã thu nhỏ
siftDown(arr, end, 0);
}
return arr;
}
function siftDown(arr: number[], heapSize: number, i: number): void {
while (true) {
let largest = i;
const left = 2 * i + 1;
const right = 2 * i + 2;
if (left < heapSize && arr[left] > arr[largest]) largest = left;
if (right < heapSize && arr[right] > arr[largest]) largest = right;
if (largest === i) break;
[arr[i], arr[largest]] = [arr[largest], arr[i]];
i = largest;
}
}
// Sử dụng
console.log(heapSort([7, 3, 9, 1, 5, 2, 8, 4, 6]));
// [1, 2, 3, 4, 5, 6, 7, 8, 9]7.2 Cài Đặt Python
# Python - Heap Sort
def heap_sort(arr: list[int]) -> list[int]:
n = len(arr)
# Build max heap
for i in range(n // 2 - 1, -1, -1):
sift_down(arr, n, i)
# Extract từng phần tử
for end in range(n - 1, 0, -1):
arr[0], arr[end] = arr[end], arr[0]
sift_down(arr, end, 0)
return arr
def sift_down(arr: list[int], heap_size: int, i: int) -> None:
while True:
largest = i
left = 2 * i + 1
right = 2 * i + 2
if left < heap_size and arr[left] > arr[largest]:
largest = left
if right < heap_size and arr[right] > arr[largest]:
largest = right
if largest == i:
break
arr[i], arr[largest] = arr[largest], arr[i]
i = largest
# Sử dụng
print(heap_sort([7, 3, 9, 1, 5, 2, 8, 4, 6]))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]7.3 Tính Chất Heap Sort
| Tính chất | Giá trị |
|---|---|
| Time (mọi trường hợp) | O(n log n) |
| Space | O(1) — tại chỗ |
| Stable? | Không |
| Adaptive? | Không (luôn O(n log n)) |
Heap sort vs các thuật sắp xếp O(n log n) khác:
- vs Quick Sort: Heap sort đảm bảo O(n log n) worst case (quick sort là O(n²) worst case), nhưng quick sort thường nhanh hơn trong practice nhờ cache locality
- vs Merge Sort: Heap sort chạy tại chỗ (O(1) space) trong khi merge sort cần O(n) space, nhưng merge sort là stable
8. Heaps vs Các Cấu Trúc Dữ Liệu Khác
| Thao tác | Heap | BST (cân bằng) | Sorted Array |
|---|---|---|---|
| Tìm min | O(1) | O(log n) | O(1) |
| Tìm max | O(1) — max heap | O(log n) | O(1) |
| Insert | O(log n) | O(log n) | O(n) |
| Extract min/max | O(log n) | O(log n) | O(1) hoặc O(n) |
| Search | O(n) | O(log n) | O(log n) |
| Build từ array | O(n) | O(n log n) | O(n log n) |
| Space | O(n) | O(n) + pointers | O(n) |
Khi nào dùng cái nào:
- Heap: Khi bạn chỉ cần min hoặc max — không cần cả hai, không cần search tùy ý
- BST: Khi cần các thao tác có thứ tự (predecessor, successor, range queries)
- Sorted Array: Khi dữ liệu tĩnh và chỉ cần tra cứu
9. Các Bài Toán Phỏng Vấn Kinh Điển
Bài 1: Phần Tử Lớn Thứ K Trong Mảng
Cho mảng số nguyên
numsvà số nguyênk, trả về phần tử lớn thứ k. Lưu ý đây là thứ k trong thứ tự sắp xếp, không phải phần tử distinct thứ k.
Approach: Duy trì min heap có size K. Root luôn là phần tử lớn thứ K.
// TypeScript - Kth Largest Element
function findKthLargest(nums: number[], k: number): number {
const heap = new MinHeap();
for (const num of nums) {
heap.insert(num);
if (heap.size > k) {
heap.extractMin();
}
}
return heap.peek()!;
}
// Sử dụng
console.log(findKthLargest([3, 2, 1, 5, 6, 4], 2)); // 5
console.log(findKthLargest([3, 2, 3, 1, 2, 4, 5, 5, 6], 4)); // 4# Python - Kth Largest Element
import heapq
def find_kth_largest(nums: list[int], k: int) -> int:
heap = []
for num in nums:
heapq.heappush(heap, num)
if len(heap) > k:
heapq.heappop(heap)
return heap[0]
# One-liner
def find_kth_largest_v2(nums: list[int], k: int) -> int:
return heapq.nlargest(k, nums)[-1]
print(find_kth_largest([3, 2, 1, 5, 6, 4], 2)) # 5
print(find_kth_largest([3, 2, 3, 1, 2, 4, 5, 5, 6], 4)) # 4Time: O(n log k). Space: O(k).
Bài 2: Top K Phần Tử Xuất Hiện Nhiều Nhất
Cho mảng số nguyên
numsvà số nguyênk, trả về k phần tử xuất hiện nhiều nhất.
Approach: Đếm tần suất, rồi dùng min heap size K để theo dõi top K phần tử có frequency cao nhất.
// TypeScript - Top K Frequent Elements
function topKFrequent(nums: number[], k: number): number[] {
const freq = new Map<number, number>();
for (const num of nums) {
freq.set(num, (freq.get(num) || 0) + 1);
}
// Min heap of [frequency, number]
const heap: [number, number][] = [];
const siftUp = (i: number) => {
while (i > 0) {
const parent = Math.floor((i - 1) / 2);
if (heap[i][0] >= heap[parent][0]) break;
[heap[i], heap[parent]] = [heap[parent], heap[i]];
i = parent;
}
};
const siftDown = (i: number) => {
while (true) {
let smallest = i;
const l = 2 * i + 1, r = 2 * i + 2;
if (l < heap.length && heap[l][0] < heap[smallest][0]) smallest = l;
if (r < heap.length && heap[r][0] < heap[smallest][0]) smallest = r;
if (smallest === i) break;
[heap[i], heap[smallest]] = [heap[smallest], heap[i]];
i = smallest;
}
};
for (const [num, count] of freq) {
heap.push([count, num]);
siftUp(heap.length - 1);
if (heap.length > k) {
heap[0] = heap.pop()!;
siftDown(0);
}
}
return heap.map(([_, num]) => num);
}
console.log(topKFrequent([1, 1, 1, 2, 2, 3], 2)); // [2, 1] hoặc [1, 2]# Python - Top K Frequent Elements
import heapq
from collections import Counter
def top_k_frequent(nums: list[int], k: int) -> list[int]:
freq = Counter(nums)
# nlargest trả về k items có counts cao nhất
return heapq.nlargest(k, freq.keys(), key=freq.get)
# Cách khác: min heap size k
def top_k_frequent_v2(nums: list[int], k: int) -> list[int]:
freq = Counter(nums)
heap = []
for num, count in freq.items():
heapq.heappush(heap, (count, num))
if len(heap) > k:
heapq.heappop(heap)
return [num for count, num in heap]
print(top_k_frequent([1, 1, 1, 2, 2, 3], 2)) # [1, 2]Time: O(n log k). Space: O(n) cho frequency map.
Bài 3: Merge K Sorted Lists
Cho mảng
klinked lists, mỗi list đã sắp xếp tăng dần. Merge tất cả thành một sorted list.
Approach: Dùng min heap để luôn chọn phần tử nhỏ nhất từ đầu tất cả các lists.
// TypeScript - Merge K Sorted Lists
class ListNode {
val: number;
next: ListNode | null;
constructor(val = 0, next: ListNode | null = null) {
this.val = val;
this.next = next;
}
}
function mergeKLists(lists: (ListNode | null)[]): ListNode | null {
// Min heap lưu [value, listIndex]
const heap: [number, number][] = [];
const siftUp = (i: number) => {
while (i > 0) {
const p = Math.floor((i - 1) / 2);
if (heap[i][0] >= heap[p][0]) break;
[heap[i], heap[p]] = [heap[p], heap[i]];
i = p;
}
};
const siftDown = (i: number) => {
while (true) {
let s = i;
const l = 2 * i + 1, r = 2 * i + 2;
if (l < heap.length && heap[l][0] < heap[s][0]) s = l;
if (r < heap.length && heap[r][0] < heap[s][0]) s = r;
if (s === i) break;
[heap[i], heap[s]] = [heap[s], heap[i]];
i = s;
}
};
const push = (item: [number, number]) => {
heap.push(item);
siftUp(heap.length - 1);
};
const pop = (): [number, number] => {
const top = heap[0];
if (heap.length === 1) { heap.pop(); return top; }
heap[0] = heap.pop()!;
siftDown(0);
return top;
};
// Khởi tạo heap với head của mỗi list
const heads = [...lists];
for (let i = 0; i < heads.length; i++) {
if (heads[i]) push([heads[i]!.val, i]);
}
const dummy = new ListNode();
let current = dummy;
while (heap.length > 0) {
const [val, idx] = pop();
current.next = new ListNode(val);
current = current.next;
heads[idx] = heads[idx]!.next;
if (heads[idx]) push([heads[idx]!.val, idx]);
}
return dummy.next;
}# Python - Merge K Sorted Lists
import heapq
from typing import Optional
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def merge_k_lists(lists: list[Optional[ListNode]]) -> Optional[ListNode]:
heap = []
# Dùng index làm tiebreaker (ListNode không comparable)
for i, node in enumerate(lists):
if node:
heapq.heappush(heap, (node.val, i, node))
dummy = ListNode()
current = dummy
while heap:
val, idx, node = heapq.heappop(heap)
current.next = ListNode(val)
current = current.next
if node.next:
heapq.heappush(heap, (node.next.val, idx, node.next))
return dummy.nextTime: O(N log k) với N = tổng nodes trên tất cả lists. Space: O(k).
Bài 4: Tìm Median Từ Data Stream
Thiết kế cấu trúc dữ liệu hỗ trợ thêm số và tìm median hiệu quả.
Approach: Dùng hai heaps — max heap cho nửa dưới và min heap cho nửa trên. Median luôn ở đỉnh một hoặc cả hai heaps.
Các bất biến:
- Max heap chứa nửa nhỏ hơn, min heap chứa nửa lớn hơn
- Size max heap bằng min heap (tổng chẵn) hoặc lớn hơn 1 (tổng lẻ)
- Mọi phần tử trong max heap ≤ mọi phần tử trong min heap
// TypeScript - Median Finder với Two Heaps
class MedianFinder {
private lo: MaxHeap; // max heap — nửa dưới
private hi: MinHeap; // min heap — nửa trên
constructor() {
this.lo = new MaxHeap();
this.hi = new MinHeap();
}
addNum(num: number): void {
// Luôn thêm vào max heap trước
this.lo.insert(num);
// Đảm bảo max nửa dưới ≤ min nửa trên
if (this.hi.size > 0 && this.lo.peek()! > this.hi.peek()!) {
this.hi.insert(this.lo.extractMax()!);
}
// Cân bằng size: lo có thể lớn hơn tối đa 1
if (this.lo.size > this.hi.size + 1) {
this.hi.insert(this.lo.extractMax()!);
} else if (this.hi.size > this.lo.size) {
this.lo.insert(this.hi.extractMin()!);
}
}
findMedian(): number {
if (this.lo.size > this.hi.size) {
return this.lo.peek()!;
}
return (this.lo.peek()! + this.hi.peek()!) / 2;
}
}
// Sử dụng
const mf = new MedianFinder();
mf.addNum(1);
mf.addNum(2);
console.log(mf.findMedian()); // 1.5
mf.addNum(3);
console.log(mf.findMedian()); // 2# Python - Median Finder với Two Heaps
import heapq
class MedianFinder:
def __init__(self):
self.lo: list[int] = [] # max heap (đảo dấu)
self.hi: list[int] = [] # min heap
def add_num(self, num: int) -> None:
heapq.heappush(self.lo, -num)
# Đảm bảo max nửa dưới ≤ min nửa trên
if self.hi and -self.lo[0] > self.hi[0]:
heapq.heappush(self.hi, -heapq.heappop(self.lo))
# Cân bằng size
if len(self.lo) > len(self.hi) + 1:
heapq.heappush(self.hi, -heapq.heappop(self.lo))
elif len(self.hi) > len(self.lo):
heapq.heappush(self.lo, -heapq.heappop(self.hi))
def find_median(self) -> float:
if len(self.lo) > len(self.hi):
return -self.lo[0]
return (-self.lo[0] + self.hi[0]) / 2
# Sử dụng
mf = MedianFinder()
mf.add_num(1)
mf.add_num(2)
print(mf.find_median()) # 1.5
mf.add_num(3)
print(mf.find_median()) # 2.0Time: O(log n) mỗi lần add, O(1) cho find median. Space: O(n).
Bài 5: K Điểm Gần Gốc Tọa Độ Nhất
Cho mảng điểm
points[i] = [xi, yi], trả vềkđiểm gần gốc tọa độ(0, 0)nhất.
Approach: Dùng max heap size K. Nếu một điểm gần hơn điểm xa nhất trong heap, thay thế nó.
// TypeScript - K Closest Points to Origin
function kClosest(points: number[][], k: number): number[][] {
// Max heap theo khoảng cách (đảo dấu để có max behavior dùng min heap logic)
const heap: [number, number[]][] = []; // [negDist, point]
const siftUp = (i: number) => {
while (i > 0) {
const p = Math.floor((i - 1) / 2);
if (heap[i][0] <= heap[p][0]) break;
[heap[i], heap[p]] = [heap[p], heap[i]];
i = p;
}
};
const siftDown = (i: number) => {
while (true) {
let largest = i;
const l = 2 * i + 1, r = 2 * i + 2;
if (l < heap.length && heap[l][0] > heap[largest][0]) largest = l;
if (r < heap.length && heap[r][0] > heap[largest][0]) largest = r;
if (largest === i) break;
[heap[i], heap[largest]] = [heap[largest], heap[i]];
i = largest;
}
};
for (const point of points) {
const dist = point[0] ** 2 + point[1] ** 2;
heap.push([dist, point]);
siftUp(heap.length - 1);
if (heap.length > k) {
heap[0] = heap.pop()!;
siftDown(0);
}
}
return heap.map(([_, point]) => point);
}
console.log(kClosest([[1, 3], [-2, 2]], 1)); // [[-2, 2]]# Python - K Closest Points to Origin
import heapq
def k_closest(points: list[list[int]], k: int) -> list[list[int]]:
# Max heap size k (đảo dấu distance)
heap = []
for x, y in points:
dist = -(x * x + y * y) # đảo dấu cho max heap
heapq.heappush(heap, (dist, [x, y]))
if len(heap) > k:
heapq.heappop(heap)
return [point for _, point in heap]
print(k_closest([[1, 3], [-2, 2]], 1)) # [[-2, 2]]Time: O(n log k). Space: O(k).
Bài 6: Task Scheduler
Cho các tasks biểu diễn bằng ký tự và cooldown period
n, tìm số intervals tối thiểu CPU cần để hoàn thành tất cả tasks.
Approach: Dùng max heap để luôn thực thi task có frequency cao nhất còn lại. Sau khi thực thi, đặt task vào cooldown queue.
// TypeScript - Task Scheduler
function leastInterval(tasks: string[], n: number): number {
const freq = new Map<string, number>();
for (const task of tasks) {
freq.set(task, (freq.get(task) || 0) + 1);
}
// Max heap of frequencies
const heap: number[] = [];
const pushHeap = (val: number) => {
heap.push(val);
let i = heap.length - 1;
while (i > 0) {
const p = Math.floor((i - 1) / 2);
if (heap[i] <= heap[p]) break;
[heap[i], heap[p]] = [heap[p], heap[i]];
i = p;
}
};
const popHeap = (): number => {
const top = heap[0];
if (heap.length === 1) { heap.pop(); return top; }
heap[0] = heap.pop()!;
let i = 0;
while (true) {
let largest = i;
const l = 2 * i + 1, r = 2 * i + 2;
if (l < heap.length && heap[l] > heap[largest]) largest = l;
if (r < heap.length && heap[r] > heap[largest]) largest = r;
if (largest === i) break;
[heap[i], heap[largest]] = [heap[largest], heap[i]];
i = largest;
}
return top;
};
for (const count of freq.values()) {
pushHeap(count);
}
const cooldown: [number, number][] = []; // [count, availableTime]
let time = 0;
while (heap.length > 0 || cooldown.length > 0) {
time++;
if (heap.length > 0) {
const count = popHeap() - 1;
if (count > 0) {
cooldown.push([count, time + n]);
}
}
if (cooldown.length > 0 && cooldown[0][1] === time) {
pushHeap(cooldown.shift()![0]);
}
}
return time;
}
console.log(leastInterval(["A","A","A","B","B","B"], 2)); // 8# Python - Task Scheduler
import heapq
from collections import Counter, deque
def least_interval(tasks: list[str], n: int) -> int:
freq = Counter(tasks)
# Max heap (đảo dấu cho max behavior)
heap = [-count for count in freq.values()]
heapq.heapify(heap)
cooldown = deque() # (count, available_time)
time = 0
while heap or cooldown:
time += 1
if heap:
count = heapq.heappop(heap) + 1 # +1 vì đã đảo dấu
if count < 0:
cooldown.append((count, time + n))
if cooldown and cooldown[0][1] == time:
heapq.heappush(heap, cooldown.popleft()[0])
return time
print(least_interval(["A","A","A","B","B","B"], 2)) # 8Time: O(n × m) với m = cooldown period. Space: O(26) = O(1) cho task frequencies.
Bài 7: Sắp Xếp Lại Chuỗi
Cho chuỗi
s, sắp xếp lại các ký tự sao cho không có hai ký tự liền kề giống nhau. Trả về kết quả hợp lệ bất kỳ, hoặc""nếu không thể.
Approach: Dùng max heap để luôn đặt ký tự có frequency cao nhất tiếp theo, xen kẽ với ký tự trước đó.
// TypeScript - Reorganize String
function reorganizeString(s: string): string {
const freq = new Map<string, number>();
for (const c of s) {
freq.set(c, (freq.get(c) || 0) + 1);
}
// Max heap of [count, char]
const heap: [number, string][] = [];
const push = (item: [number, string]) => {
heap.push(item);
let i = heap.length - 1;
while (i > 0) {
const p = Math.floor((i - 1) / 2);
if (heap[i][0] <= heap[p][0]) break;
[heap[i], heap[p]] = [heap[p], heap[i]];
i = p;
}
};
const pop = (): [number, string] => {
const top = heap[0];
if (heap.length === 1) { heap.pop(); return top; }
heap[0] = heap.pop()!;
let i = 0;
while (true) {
let largest = i;
const l = 2 * i + 1, r = 2 * i + 2;
if (l < heap.length && heap[l][0] > heap[largest][0]) largest = l;
if (r < heap.length && heap[r][0] > heap[largest][0]) largest = r;
if (largest === i) break;
[heap[i], heap[largest]] = [heap[largest], heap[i]];
i = largest;
}
return top;
};
for (const [char, count] of freq) {
push([count, char]);
}
let result = "";
let prev: [number, string] | null = null;
while (heap.length > 0) {
const [count, char] = pop();
result += char;
if (prev && prev[0] > 0) {
push(prev);
}
prev = [count - 1, char];
}
return result.length === s.length ? result : "";
}
console.log(reorganizeString("aab")); // "aba"
console.log(reorganizeString("aaab")); // ""# Python - Reorganize String
import heapq
from collections import Counter
def reorganize_string(s: str) -> str:
freq = Counter(s)
# Max heap (đảo dấu counts)
heap = [(-count, char) for char, count in freq.items()]
heapq.heapify(heap)
result = []
prev = None # (neg_count, char)
while heap:
neg_count, char = heapq.heappop(heap)
result.append(char)
if prev and prev[0] < 0:
heapq.heappush(heap, prev)
prev = (neg_count + 1, char) # +1 vì đã đảo dấu
return "".join(result) if len(result) == len(s) else ""
print(reorganize_string("aab")) # "aba"
print(reorganize_string("aaab")) # ""Time: O(n log k) với k = số ký tự unique. Space: O(k).
Bài 8: Meeting Rooms II
Cho mảng các khoảng thời gian họp
[start, end], tìm số phòng họp tối thiểu cần thiết.
Approach: Sắp xếp theo start time. Dùng min heap để theo dõi end times của các cuộc họp đang diễn ra. Nếu cuộc họp kết thúc sớm nhất xong trước cuộc họp tiếp theo bắt đầu, tái sử dụng phòng đó.
// TypeScript - Meeting Rooms II
function minMeetingRooms(intervals: number[][]): number {
intervals.sort((a, b) => a[0] - b[0]);
// Min heap of end times
const heap: number[] = [];
const push = (val: number) => {
heap.push(val);
let i = heap.length - 1;
while (i > 0) {
const p = Math.floor((i - 1) / 2);
if (heap[i] >= heap[p]) break;
[heap[i], heap[p]] = [heap[p], heap[i]];
i = p;
}
};
const pop = (): number => {
const top = heap[0];
if (heap.length === 1) { heap.pop(); return top; }
heap[0] = heap.pop()!;
let i = 0;
while (true) {
let smallest = i;
const l = 2 * i + 1, r = 2 * i + 2;
if (l < heap.length && heap[l] < heap[smallest]) smallest = l;
if (r < heap.length && heap[r] < heap[smallest]) smallest = r;
if (smallest === i) break;
[heap[i], heap[smallest]] = [heap[smallest], heap[i]];
i = smallest;
}
return top;
};
for (const [start, end] of intervals) {
// Nếu phòng kết thúc sớm nhất đã rảnh, tái sử dụng
if (heap.length > 0 && heap[0] <= start) {
pop();
}
push(end);
}
return heap.length;
}
console.log(minMeetingRooms([[0,30],[5,10],[15,20]])); // 2
console.log(minMeetingRooms([[7,10],[2,4]])); // 1# Python - Meeting Rooms II
import heapq
def min_meeting_rooms(intervals: list[list[int]]) -> int:
intervals.sort(key=lambda x: x[0])
heap = [] # min heap of end times
for start, end in intervals:
if heap and heap[0] <= start:
heapq.heappop(heap)
heapq.heappush(heap, end)
return len(heap)
print(min_meeting_rooms([[0,30],[5,10],[15,20]])) # 2
print(min_meeting_rooms([[7,10],[2,4]])) # 1Time: O(n log n). Space: O(n).
10. Hướng Dẫn Nhận Diện Pattern
| Dấu hiệu trong bài toán | Pattern | Cấu trúc dữ liệu |
|---|---|---|
| "K lớn nhất" / "K nhỏ nhất" | Top-K | Min heap size K |
| "K xuất hiện nhiều nhất" | Top-K + frequency | Đếm + min heap size K |
| "Median" / "phần tử giữa" | Two heaps | Max heap (dưới) + min heap (trên) |
| "Merge K sorted" | K-way merge | Min heap size K |
| "Schedule" / "cooldown" / "interval" | Greedy + heap | Max heap of frequencies |
| "Gần nhất" / "nearest" | Top-K theo khoảng cách | Max heap size K (hoặc min heap) |
| "Liên tục thêm + query min/max" | Streaming | Min hoặc max heap |
| "Sắp xếp lại" / "không liền kề giống nhau" | Greedy interleave | Max heap of frequencies |
Sơ Đồ Quyết Định
Mẹo Phỏng Vấn
- "K lớn nhất" → Min heap size K (nghe ngược đời nhưng đúng — root chính là phần tử lớn thứ K)
- "K nhỏ nhất" → Max heap size K
- "Median từ stream" → Hai heaps: max heap (nửa dưới) + min heap (nửa trên)
- "Merge K sorted X" → Min heap giữ một phần tử từ mỗi source
- "Sắp xếp lại với constraints" → Max heap of frequencies + greedy placement
- "Frequency cao nhất + cooldown" → Max heap + queue cho cooldown tracking
11. Tham Khảo Complexity
Các Thao Tác Heap
| Thao tác | Min Heap | Max Heap |
|---|---|---|
| Peek | O(1) | O(1) |
| Insert | O(log n) | O(log n) |
| Extract | O(log n) | O(log n) |
| Build | O(n) | O(n) |
| Search | O(n) | O(n) |
Complexity Các Bài Toán
| Bài toán | Time | Space |
|---|---|---|
| Phần tử lớn thứ K | O(n log k) | O(k) |
| Top K Frequent | O(n log k) | O(n) |
| Merge K Sorted Lists | O(N log k) | O(k) |
| Tìm Median (stream) | O(log n) mỗi lần add | O(n) |
| K Closest Points | O(n log k) | O(k) |
| Task Scheduler | O(n) | O(1) |
| Reorganize String | O(n log k) | O(k) |
| Meeting Rooms II | O(n log n) | O(n) |
12. Bài Tập Thực Hành
Easy (Xây Dựng Nền Tảng)
| # | Bài toán | Pattern | Approach |
|---|---|---|---|
| 1 | Last Stone Weight | Max heap | Đập hai viên đá nặng nhất liên tục |
| 2 | Kth Largest Element in a Stream | Min heap size K | Duy trì K phần tử, root là đáp án |
| 3 | Relative Ranks | Max heap | Extract theo thứ tự xếp hạng |
| 4 | Sort Array by Increasing Frequency | Heap + frequency | Đếm rồi heap sort theo frequency |
Medium (Xây Dựng Tự Tin)
| # | Bài toán | Pattern | Approach |
|---|---|---|---|
| 5 | Kth Largest Element in an Array | Top-K | Min heap size K |
| 6 | Top K Frequent Elements | Top-K + counting | Đếm + min heap size K |
| 7 | K Closest Points to Origin | Top-K theo khoảng cách | Max heap size K |
| 8 | Task Scheduler | Greedy + heap | Max heap + cooldown queue |
| 9 | Reorganize String | Greedy interleave | Max heap of frequencies |
| 10 | Sort Characters By Frequency | Heap + frequency | Max heap of (count, char) |
| 11 | Meeting Rooms II | Sweep line + heap | Min heap of end times |
| 12 | Ugly Number II | Min heap + dedup | Sinh candidates qua heap |
Hard (Master Level)
| # | Bài toán | Pattern | Approach |
|---|---|---|---|
| 13 | Find Median from Data Stream | Two heaps | Max heap (dưới) + min heap (trên) |
| 14 | Merge K Sorted Lists | K-way merge | Min heap của K heads |
| 15 | Sliding Window Median | Two heaps + window | Lazy deletion với hai heaps |
| 16 | IPO | Two heaps (greedy) | Sort theo capital, max heap cho profit |
| 17 | Smallest Range Covering Elements from K Lists | K-way merge + window | Min heap tracking tất cả K lists |
Tóm Tắt
Heap tưởng đơn giản — một quy tắc duy nhất (parent ≤ children) cho bạn O(1) truy cập phần tử nhỏ nhất với O(log n) cập nhật.
Các thao tác cốt lõi:
- Insert: Thêm vào cuối, sift up — O(log n)
- Extract: Xóa root, sift down — O(log n)
- Build heap: Giải thuật Floyd — O(n)
- Peek: Đọc root — O(1)
Các pattern chính:
- Top-K → Min heap size K (cho K lớn nhất) hoặc max heap size K (cho K nhỏ nhất)
- Two heaps → Tìm median, bài toán data stream
- K-way merge → Min heap giữ một phần tử mỗi source
- Greedy + heap → Task scheduling, sắp xếp lại chuỗi
- Interval problems → Min heap of end times
Lợi thế Python: Module heapq cho bạn min heap production-ready. Muốn max heap thì đảo dấu giá trị. Developer JavaScript/TypeScript: bạn phải tự cài đặt hoặc dùng library.
Tiếp theo trong DSA series? Bài tiếp theo cover Dynamic Programming & Greedy Algorithms — các paradigm giải thuật giải quyết bài toán tối ưu bằng cách chia thành subproblems chồng lấn (DP) hoặc đưa ra lựa chọn locally optimal (greedy). Bạn sẽ học memoization, tabulation, và các pattern kinh điển như knapsack, longest subsequence, và coin change.
Bài trước: Graphs & Graph Algorithms
Tổng quan series: Data Structures & Algorithms Roadmap
📬 Đăng ký nhận bản tin
Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.
Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.
💬 Comments
Đăng nhập để bình luận
Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.