Median dari Aliran Data dan Penggabungan K Arah
Pertahankan dua heap (max-heap untuk separuh kecil dan min-heap untuk separuh besar) untuk pembaruan median O(log n), lalu gabungkan k list terurut menggunakan heap.
Median dari Aliran Data dan Penggabungan K Arah adalah pelajaran DSA Interview Prep gratis di CoddyKit. Ini adalah pelajaran 4 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar DSA Interview Prep, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus DSA Interview Prep mencakup 4 pelajaran total.
Masalah Median dari Aliran Data
Menemukan Median dari Aliran Data (LeetCode #295) meminta Anda mendukung dua operasi secara efisien: addNum(num) untuk menambahkan angka dan findMedian() untuk mengembalikan median saat ini. Median dari list dengan panjang genap adalah rata-rata dari dua nilai tengah. List terurut dengan pendekatan brute force memerlukan penyisipan O(n) dan median O(1). Solusi optimal menggunakan dua tumpukan untuk penyisipan O(log n) dan median O(1).
import heapq
# Strategy: maintain two halves of the data
# max_heap: lower half (stores negated values for max behavior)
# min_heap: upper half
# Invariant: len(max_heap) == len(min_heap) or len(max_heap) == len(min_heap) + 1
# Invariant: max(max_heap) <= min(min_heap)
# Median:
# odd count: max_heap[0] (top of lower half)
# even count: average of tops of both halves
print('Two-heap strategy for O(log n) insert, O(1) median')Implementasi MedianFinder dengan Dua Tumpukan
Pertahankan tumpukan maksimum untuk separuh bawah dan tumpukan minimum untuk separuh atas. Selalu pastikan tumpukan maksimum memiliki ukuran yang sama dengan tumpukan minimum atau satu elemen lebih banyak. Saat menambahkan angka, lakukan push ke tumpukan maksimum, lalu seimbangkan dengan memindahkan elemen teratas tumpukan maksimum ke tumpukan minimum jika elemen tersebut lebih besar daripada elemen minimum tumpukan minimum, kemudian seimbangkan kembali ukurannya jika diperlukan.
import heapq
class MedianFinder:
def __init__(self):
self.lo = [] # max-heap (negated) for lower half
self.hi = [] # min-heap for upper half
def addNum(self, num):
heapq.heappush(self.lo, -num) # push to lower half
# Ensure max of lower <= min of upper
if self.hi and -self.lo[0] > self.hi[0]:
heapq.heappush(self.hi, -heapq.heappop(self.lo))
# Balance sizes: lo can have at most 1 more than hi
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 findMedian(self):
if len(self.lo) > len(self.hi):
return -self.lo[0] # odd count: top of lower half
return (-self.lo[0] + self.hi[0]) / 2
mf = MedianFinder()
for n in [1, 2, 3, 4, 5]: mf.addNum(n)
print(mf.findMedian()) # 3.0Menelusuri Langkah-Langkah MedianFinder
Memahami alasan invarian dua tumpukan dipertahankan sangat penting untuk menjelaskan solusi dalam wawancara. Mari telusuri penambahan [5, 15, 1, 3] langkah demi langkah. Setelah setiap penyisipan, seimbangkan tumpukan sehingga tumpukan maksimum bawah menyimpan separuh nilai yang lebih kecil. Invarian ini memastikan max(lo) <= min(hi) selalu terpenuhi, sehingga median dapat diakses dengan mudah di bagian teratas salah satu atau kedua tumpukan.
import heapq
# Manual trace for [5, 15, 1, 3]:
# add 5: lo=[-5] hi=[] median=5
# add 15: lo=[-5] hi=[15] median=(5+15)/2=10
# add 1: lo=[-5,-1] hi=[15] median=5
# add 3: lo=[-5,-3,-1] hi=[15] -- lo too big
# -> lo=[-5,-3] hi=[1,15] -- wait, wrong direction
# Actually:
# add 1: push to lo -> lo=[-5,-1], then 1>lo? No, -lo[0]=5>15? No
# lo has 2, hi has 1: balance -> move lo top to hi
# lo=[-1], hi=[5,15]
# Median = (-lo[0] + hi[0])/2 = (1+5)/2 = 3
mf2 = MedianFinder()
for n, expected in [(5, 5.0), (15, 10.0), (1, 5.0), (3, 4.0)]:
mf2.addNum(n)
print(f'After adding {n}: median={mf2.findMedian()} (expected ~{expected})')Median Jendela Geser
Median Jendela Geser (LeetCode #480) merupakan varian yang lebih sulit: temukan median setiap jendela berukuran k saat jendela tersebut bergeser melintasi array. Pendekatan dua tumpukan diperluas dengan himpunan penghapusan tertunda untuk menangani elemen yang keluar dari jendela. Saat sebuah elemen meninggalkan jendela, tandai elemen tersebut dalam himpunan penghapusan; saat elemen itu mencapai bagian teratas salah satu tumpukan, buang elemen tersebut.
import heapq
def median_sliding_window(nums, k):
lo = [] # max-heap (negated)
hi = [] # min-heap
removed = {}
result = []
def balance():
# Move valid tops to correct side
while lo and removed.get(-lo[0], 0) > 0:
removed[-lo[0]] -= 1; heapq.heappop(lo)
while hi and removed.get(hi[0], 0) > 0:
removed[hi[0]] -= 1; heapq.heappop(hi)
for i, num in enumerate(nums):
heapq.heappush(lo, -num)
heapq.heappush(hi, -heapq.heappop(lo))
if len(hi) > len(lo): heapq.heappush(lo, -heapq.heappop(hi))
if i >= k:
out = nums[i - k]
removed[out] = removed.get(out, 0) + 1
balance()
if len(lo) > len(hi): heapq.heappush(hi, -heapq.heappop(lo))
if i >= k - 1:
if len(lo) > len(hi): result.append(float(-lo[0]))
else: result.append((-lo[0] + hi[0]) / 2.0)
return result
print(median_sliding_window([1,3,-1,-3,5,3,6,7], 3)) # [1,-1,-1,3,5,6]Penggabungan K-Arah: Masalahnya
Menggabungkan K Daftar Terurut (LeetCode #23) merupakan masalah fundamental dengan penerapan dalam pengurutan eksternal, penggabungan basis data, dan sistem terdistribusi. Diberikan k daftar tertaut terurut dengan total n simpul, gabungkan semuanya menjadi satu daftar terurut. Pendekatan naif, yaitu menggabungkan dua daftar sekaligus, memiliki kompleksitas O(kn), atau O(n log k) dengan pendekatan bagi-dan-taklukkan. Pendekatan tumpukan memproses setiap simpul tepat satu kali dengan pekerjaan O(log k) per simpul, sehingga totalnya O(n log k).
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
# Build a linked list from a Python list
def build_list(arr):
dummy = ListNode(0)
curr = dummy
for val in arr:
curr.next = ListNode(val)
curr = curr.next
return dummy.next
# Convert linked list to Python list for printing
def to_list(head):
result = []
while head:
result.append(head.val)
head = head.next
return result
print('K-way merge: O(n log k) using a min-heap of k heads')Penggabungan K-Arah dengan Tumpukan Minimum
Inisialisasi tumpukan dengan simpul pertama dari setiap daftar. Pada setiap langkah, lakukan pop terhadap elemen minimum, tambahkan elemen tersebut ke hasil, lalu lakukan push terhadap simpul berikutnya dari daftar tersebut jika ada. Tumpukan selalu memiliki paling banyak k elemen—satu kepala untuk setiap daftar aktif. Karena total n simpul diproses dengan operasi tumpukan O(log k) untuk masing-masing simpul, waktu totalnya adalah O(n log k) dan ruang yang diperlukan untuk tumpukan adalah O(k).
import heapq
def merge_k_lists(lists):
dummy = ListNode(0)
curr = dummy
heap = []
for i, node in enumerate(lists):
if node:
heapq.heappush(heap, (node.val, i, node))
while heap:
val, i, node = heapq.heappop(heap)
curr.next = node
curr = curr.next
if node.next:
heapq.heappush(heap, (node.next.val, i, node.next))
return dummy.next
lists = [
build_list([1, 4, 5]),
build_list([1, 3, 4]),
build_list([2, 6])
]
result = merge_k_lists(lists)
print(to_list(result)) # [1, 1, 2, 3, 4, 4, 5, 6]Rentang Terkecil yang Mencakup K Daftar
Rentang Terkecil (LeetCode #632) mencari rentang [lo, hi] terkecil sehingga setidaknya satu elemen dari setiap k daftar terurut berada di dalam rentang tersebut. Gunakan tumpukan minimum yang diinisialisasi dengan elemen pertama dari setiap daftar dan lacak nilai maksimum saat ini. Perkecil rentang dengan selalu memajukan daftar yang memiliki nilai minimum saat ini. Berhenti ketika salah satu daftar habis.
import heapq
def smallest_range(nums):
heap = []
current_max = float('-inf')
for i, lst in enumerate(nums):
heapq.heappush(heap, (lst[0], i, 0))
current_max = max(current_max, lst[0])
best = [float('-inf'), float('inf')]
while heap:
current_min, list_idx, elem_idx = heapq.heappop(heap)
if current_max - current_min < best[1] - best[0]:
best = [current_min, current_max]
if elem_idx + 1 >= len(nums[list_idx]):
break # one list exhausted
next_val = nums[list_idx][elem_idx + 1]
heapq.heappush(heap, (next_val, list_idx, elem_idx + 1))
current_max = max(current_max, next_val)
return best
print(smallest_range([[4,10,15,24,26],[0,9,12,20],[5,18,22,30]]))
# [20, 24]Elemen ke-K Terkecil dalam Matriks
Elemen Terkecil ke-K dalam Matriks Terurut (LeetCode #378): matriks n×n yang setiap baris dan kolomnya terurut. Temukan elemen terkecil ke-k. Perlakukan setiap baris sebagai list terurut dan gunakan penggabungan k-arah dengan tumpukan. Sebagai alternatif, gunakan pencarian biner pada rentang nilai. Pendekatan tumpukan memiliki kompleksitas O(k log n), sehingga efisien ketika k kecil; pencarian biner memiliki kompleksitas O(n log(max-min)), sehingga lebih baik untuk k yang besar.
import heapq
def kth_smallest_matrix(matrix, k):
n = len(matrix)
heap = [(matrix[0][0], 0, 0)]
count = 0
visited = {(0, 0)}
while heap:
val, r, c = heapq.heappop(heap)
count += 1
if count == k:
return val
# Push right neighbor
if c + 1 < n and (r, c+1) not in visited:
heapq.heappush(heap, (matrix[r][c+1], r, c+1))
visited.add((r, c+1))
# Push bottom neighbor
if r + 1 < n and (r+1, c) not in visited:
heapq.heappush(heap, (matrix[r+1][c], r+1, c))
visited.add((r+1, c))
return -1
matrix = [[1,5,9],[10,11,13],[12,13,15]]
print(kth_smallest_matrix(matrix, 8)) # 13Dua Tumpukan untuk Statistik Berjalan
Pola dua tumpukan dapat diperluas melampaui median. Anda dapat menggunakannya untuk mempertahankan kuantil berjalan, misalnya persentil ke-25: atur ukuran tumpukan bawah agar menampung p*n elemen dan tumpukan atas agar menampung (1-p)*n elemen. Setiap kali elemen ditambahkan, seimbangkan kembali seperti sebelumnya. Pola ini muncul dalam masalah statistik aliran data yang memerlukan penyisipan efisien dan kueri kuantil secara bersamaan.
import heapq
# Generalised two-heap for arbitrary quantile p
# lo contains floor(p * count) elements
# hi contains the remaining elements
class QuantileFinder:
def __init__(self, p):
self.p = p # quantile (e.g., 0.5 for median)
self.lo = [] # max-heap
self.hi = [] # min-heap
self.count = 0
def add(self, num):
self.count += 1
heapq.heappush(self.lo, -num)
heapq.heappush(self.hi, -heapq.heappop(self.lo))
# Target: lo should have floor(p * count) elements
target_lo = int(self.p * self.count)
while len(self.lo) < target_lo:
heapq.heappush(self.lo, -heapq.heappop(self.hi))
while len(self.lo) > target_lo:
heapq.heappush(self.hi, -heapq.heappop(self.lo))
def quantile(self):
return -self.lo[0] if self.lo else self.hi[0]
qf = QuantileFinder(0.5) # median
for n in [1, 2, 3, 4, 5, 6]: qf.add(n)
print(qf.quantile()) # 3 (median of 1-6)Menemukan K Titik Terdekat dari Titik Asal
Titik Terdekat Sejumlah K dari Titik Asal (LeetCode #973) menggunakan tumpukan maksimum berukuran k. Lakukan push terhadap jarak kuadrat setiap titik untuk menghindari akar kuadrat. Saat ukuran tumpukan melebihi k, lakukan pop terhadap titik yang paling jauh. K titik yang tersisa adalah k titik terdekat. Kompleksitasnya O(n log k). Alternatifnya adalah menggunakan seleksi cepat dengan rata-rata O(n), tetapi solusi dengan tumpukan lebih sederhana untuk diimplementasikan dan dijelaskan dengan benar selama wawancara.
import heapq
def k_closest(points, k):
heap = [] # max-heap via negation
for x, y in points:
dist_sq = x*x + y*y
heapq.heappush(heap, (-dist_sq, x, y))
if len(heap) > k:
heapq.heappop(heap) # remove farthest
return [[x, y] for _, x, y in heap]
points = [[1,3], [-2,2], [5,8], [0,1], [-1,-1]]
print(k_closest(points, 2))
# Two closest to origin: [0,1] (dist=1) and [-1,-1] (dist=2)
# Verify by distances:
for x, y in points:
print(f'({x},{y}): dist^2 = {x*x+y*y}')Analisis Waktu dan Ruang Dua Tumpukan
Pendekatan dua tumpukan untuk median mencapai penyisipan O(log n) per addNum dan findMedian O(1). Ruang yang digunakan adalah O(n) untuk menyimpan semua elemen. Penggabungan k-arah memerlukan waktu O(n log k) dan ruang O(k) untuk tumpukan. Kompleksitas ini mendekati optimal: Anda dapat membuktikan batas bawah berbasis perbandingan sebesar Omega(n log k) untuk penggabungan k-arah, yang menunjukkan bahwa solusi tumpukan optimal secara asimtotik. Selalu nyatakan kompleksitas ini dengan jelas dalam wawancara.
# Complexity summary for heap applications:
# Problem | Time per op | Space
# ----------------------|--------------|------
# MedianFinder.addNum | O(log n) | O(n)
# MedianFinder.find | O(1) | -
# Merge k sorted lists | O(n log k) | O(k)
# Kth smallest matrix | O(k log n) | O(n)
# K closest points | O(n log k) | O(k)
# Task scheduler | O(n log 26) | O(26)
# Kth largest stream | O(log k) | O(k)
# Sliding window median | O(n log k) | O(k)
print('Heap problems: identify k (heap size) vs n (input size)')Pemeriksaan Singkat
Uji pemahaman Anda tentang konsep Struktur Data & Algoritma — Persiapan Wawancara Pemrograman dari pelajaran ini.
Rangkuman Pelajaran
Dalam pelajaran ini Anda mempelajari: MedianFinder dengan dua tumpukan yang mencapai penyisipan O(log n) dan median O(1), penggabungan k-arah dengan tumpukan minimum dalam waktu O(n log k) dan ruang O(k), serta perluasannya, termasuk median jendela geser, rentang terkecil, dan titik terdekat sejumlah k. Selanjutnya, kita akan membahas representasi graf dan persiapan penelusuran.
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Median dari Aliran Data dan Penggabungan K Arah” gratis?
Ya — teks lengkap “Median dari Aliran Data dan Penggabungan K Arah” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus DSA Interview Prep, upgrade ke CoddyKit PRO. Kursus DSA Interview Prep mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Median dari Aliran Data dan Penggabungan K Arah”?
Pertahankan dua heap (max-heap untuk separuh kecil dan min-heap untuk separuh besar) untuk pembaruan median O(log n), lalu gabungkan k list terurut menggunakan heap. Kamu berlatih DSA Interview Prep dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.
Apakah aku perlu pengalaman untuk memulai DSA Interview Prep?
Tidak diperlukan pengalaman sebelumnya. DSA Interview Prep di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 4 dari 4.
Berapa lama pelajaran “Median dari Aliran Data dan Penggabungan K Arah” memakan waktu?
Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.
Bisakah aku menulis dan menjalankan kode dalam pelajaran DSA Interview Prep ini?
Ya. Setiap pelajaran DSA Interview Prep menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.
Semua pelajaran dalam kursus ini
- Sifat Heap dan Representasi Array
- Heapify, Push, dan Pop dari Awal
- heapq Python dan Trik Max-Heap
- Median dari Aliran Data dan Penggabungan K Arah