Maksimum Jendela Geser dengan Deque Monotonik
Pertahankan deque indeks menurun untuk menjawab kueri maksimum-dalam-jendela dalam O(1) per elemen, sehingga masalah sliding-window-maximum terselesaikan dalam O(n)
Maksimum Jendela Geser dengan Deque Monotonik adalah pelajaran Coding Interview Prep gratis di CoddyKit. Ini adalah pelajaran 3 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 Coding Interview Prep, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus Coding Interview Prep mencakup 4 pelajaran total.
Masalah Maksimum Jendela Geser
Masalah Maksimum Jendela Geser (LeetCode 239) menerima sebuah larik dan ukuran jendela k. Saat jendela bergeser dari kiri ke kanan satu posisi setiap kali, keluarkan elemen maksimum di setiap jendela. Pendekatan coba semua kemungkinan menghitung maksimum setiap jendela berisi k elemen dalam O(k)—sehingga totalnya O(nk), yang terlalu lambat untuk k besar.
Solusi antrean dua ujung monoton (antrean dengan dua ujung) mencapai O(n) secara keseluruhan dengan mempertahankan antrean dua ujung indeks yang menurun. Ujung depan selalu menyimpan indeks maksimum jendela saat ini, menyediakan query maksimum O(1) sambil memungkinkan operasi pada ujung depan dan belakang.
from collections import deque
# Brute force O(nk) for comparison
def sliding_max_brute(nums, k):
return [max(nums[i:i+k]) for i in range(len(nums) - k + 1)]
nums = [1, 3, -1, -3, 5, 3, 6, 7]
k = 3
print('Input:', nums, 'k=', k)
print('Expected: [3, 3, 5, 5, 6, 7]')
print('Brute: ', sliding_max_brute(nums, k))Gagasan Utama Antrean Dua Ujung Monoton
Pertahankan antrean dua ujung monoton menurun yang menyimpan indeks (bukan nilai). Invariannya: nums[deque[0]] >= nums[deque[1]] >= ... >= nums[deque[-1]]. Sebelum menambahkan indeks i:
- Buang indeks kedaluwarsa dari ujung depan: jika
deque[0] <= i - k, indeks tersebut telah keluar dari jendela. - Buang indeks yang lebih kecil dari ujung belakang: selama
nums[deque[-1]] <= nums[i], indeks-indeks tersebut tidak mungkin lagi menjadi maksimum jendela mana pun di masa mendatang (posisinya lebih ke kiri dan nilainya lebih kecil), jadi buang indeks tersebut.
Setelah operasi ini, tambahkan i ke ujung belakang. Ujung depan selalu memberikan maksimum jendela saat ini.
from collections import deque
def sliding_window_max(nums, k):
dq = deque() # stores indices; values are decreasing
result = []
for i, n in enumerate(nums):
# 1. Remove indices outside the current window
while dq and dq[0] <= i - k:
dq.popleft()
# 2. Remove indices with smaller values from the back
while dq and nums[dq[-1]] <= n:
dq.pop()
dq.append(i)
# 3. Record max when first full window is complete
if i >= k - 1:
result.append(nums[dq[0]]) # front = max of current window
return result
nums = [1, 3, -1, -3, 5, 3, 6, 7]
print(sliding_window_max(nums, 3)) # [3, 3, 5, 5, 6, 7]Menelusuri Antrean Dua Ujung Langkah demi Langkah
Mari kita telusuri [1, 3, -1, -3, 5, 3, 6, 7] dengan k=3:
- i=0 (1): dq=[0]
- i=1 (3): pop 0 (1<3), dq=[1]
- i=2 (-1): -1<3 sehingga tetap, dq=[1,2]. Jendela [1,3,-1], maksimum=nums[1]=3
- i=3 (-3): -3<-1, dq=[1,2,3]. Periksa ujung depan: 1 > 3-3=0, OK. Maksimum jendela=3
- i=4 (5): pop 3,2,1 (semuanya lebih kecil), dq=[4]. Ujung depan 4 > 4-3=1, OK. Maksimum=5
- i=5 (3): 3<5, dq=[4,5]. Ujung depan 4 > 5-3=2, OK. Maksimum=5
- i=6 (6): pop 5,4 (keduanya lebih kecil), dq=[6]. Maksimum=6
- i=7 (7): pop 6, dq=[7]. Maksimum=7
from collections import deque
def sliding_window_max_trace(nums, k):
dq = deque()
result = []
for i, n in enumerate(nums):
while dq and dq[0] <= i - k:
print(f' Remove expired index {dq[0]} from front')
dq.popleft()
while dq and nums[dq[-1]] <= n:
print(f' Remove smaller index {dq[-1]} (val={nums[dq[-1]]}) from back')
dq.pop()
dq.append(i)
print(f'i={i} n={n}: dq={list(dq)} vals={[nums[j] for j in dq]}')
if i >= k - 1:
win_max = nums[dq[0]]
result.append(win_max)
print(f' Window {nums[max(0,i-k+1):i+1]} -> max={win_max}')
return result
nums = [1, 3, -1, -3, 5, 3, 6, 7]
result = sliding_window_max_trace(nums, 3)
print('Result:', result)Mengapa Setiap Elemen Ditambahkan dan Dikeluarkan Paling Banyak Sekali
Jaminan O(n) berasal dari argumen yang sama seperti pada tumpukan monoton, yaitu argumen teramortisasi: setiap indeks ditambahkan ke antrean dua ujung tepat sekali dan dikeluarkan (baik dari depan saat kedaluwarsa maupun dari belakang saat digantikan) paling banyak sekali. Total operasi antrean dua ujung di seluruh perulangan paling banyak 2n.
Perulangan internal tidak meningkatkan kompleksitas keseluruhan—setiap pengeluaran yang dilakukan dalam perulangan tersebut 'dibayar' oleh penambahan sebelumnya. Ini adalah penalaran yang sama seperti pada tumpukan monoton, tetapi diterapkan pada antrean dua ujung yang memungkinkan penghapusan dari kedua ujung.
from collections import deque
def sliding_window_max_instrumented(nums, k):
dq = deque()
result = []
front_pops = back_pops = pushes = 0
for i, n in enumerate(nums):
while dq and dq[0] <= i - k:
dq.popleft(); front_pops += 1
while dq and nums[dq[-1]] <= n:
dq.pop(); back_pops += 1
dq.append(i); pushes += 1
if i >= k - 1:
result.append(nums[dq[0]])
print(f'n={len(nums)}: pushes={pushes}, front_pops={front_pops}, back_pops={back_pops}')
print(f'Total deque ops = {pushes + front_pops + back_pops} <= 3n = {3*len(nums)}')
return result
import random; random.seed(0)
nums = [random.randint(-100, 100) for _ in range(20)]
sliding_window_max_instrumented(nums, 5)Minimum Jendela Geser
minimum jendela geser adalah padanan simetrisnya: pertahankan antrean dua ujung monoton menaik (keluarkan dari belakang saat elemen baru lebih kecil daripada elemen di belakang). Ujung depan selalu menyimpan minimum jendela saat ini. Setiap langkah lainnya identik dengan versi maksimum—cukup balik arah perbandingan.
Soal yang meminta minimum jendela geser sering muncul sebagai subsoal dalam algoritma yang lebih besar. Misalnya, biaya minimum untuk memindahkan barang sepanjang jalur dengan k pemberhentian antara dapat memerlukan minimum jendela geser pada larik DP.
from collections import deque
def sliding_window_min(nums, k):
dq = deque() # increasing monotonic deque
result = []
for i, n in enumerate(nums):
while dq and dq[0] <= i - k:
dq.popleft() # expired
while dq and nums[dq[-1]] >= n:
dq.pop() # pop larger values from back
dq.append(i)
if i >= k - 1:
result.append(nums[dq[0]]) # front = min
return result
nums = [1, 3, -1, -3, 5, 3, 6, 7]
print('Max k=3:', sliding_window_min.__name__, '->', end=' ')
print(sliding_window_min(nums, 3)) # [-1, -3, -3, -3, 3, 3]
from collections import deque
def sliding_window_max(nums, k):
dq = deque(); result = []
for i, n in enumerate(nums):
while dq and dq[0] <= i-k: dq.popleft()
while dq and nums[dq[-1]] <= n: dq.pop()
dq.append(i)
if i >= k-1: result.append(nums[dq[0]])
return result
print('Max k=3:', sliding_window_max(nums, 3)) # [3,3,5,5,6,7]Permainan Lompatan VI: DP dengan Antrean Dua Ujung Monoton
Permainan Lompatan VI (LeetCode 1696) adalah contoh klasik perpaduan DP dan antrean dua ujung monoton. Diberikan sebuah larik dan ukuran lompatan maksimum k, mulai dari indeks 0, pada setiap langkah Anda melompat 1 hingga k langkah ke depan sambil menambahkan skor sel tujuan. Maksimalkan total skor. Rekurensi DP-nya adalah dp[i] = nums[i] + max(dp[i-k], ..., dp[i-1]). Maksimum jendela geser pada larik DP menghasilkan total O(n).
Pola ini—rekurensi DP yang setiap selnya bergantung pada maksimum jendela berukuran tetap dari sel-sel sebelumnya—sering muncul dan selalu memerlukan antrean dua ujung monoton.
from collections import deque
def max_result(nums, k):
n = len(nums)
dp = [0] * n
dp[0] = nums[0]
dq = deque([0]) # indices of max dp values in current window
for i in range(1, n):
# Remove expired indices
while dq and dq[0] < i - k:
dq.popleft()
# dp[i] = nums[i] + max dp in window [i-k, i-1]
dp[i] = nums[i] + dp[dq[0]]
# Maintain decreasing deque on dp values
while dq and dp[dq[-1]] <= dp[i]:
dq.pop()
dq.append(i)
return dp[n - 1]
print(max_result([1,-1,-2,4,-7,3], 2)) # 7: path 1->4->3
print(max_result([10,-5,-2,4,0,3], 3)) # 17: path 10->4->3
print(max_result([1,-5,-20,4,-1,3,-6,-3], 2)) # 0Maksimum Jendela Geser: Alternatif Pohon Segmen
Untuk soal dengan ukuran jendela yang berubah-ubah (bukan k tetap), antrean dua ujung tidak dapat diterapkan secara langsung. Sebagai gantinya, gunakan tabel renggang untuk query maksimum rentang statis dalam O(1) per query setelah prapemrosesan O(n log n), atau pohon segmen untuk pembaruan dinamis dengan O(log n) per query. Namun, untuk jendela geser dengan k tetap, antrean dua ujung tak tertandingi dengan O(n).
Dalam wawancara, selalu pilih antrean dua ujung monoton O(n) daripada pohon segmen O(n log n) jika ukuran jendela konstan. Sebutkan komprominya: antrean dua ujung tidak dapat menangani ukuran jendela sembarang atau pembaruan, sedangkan pohon segmen dapat.
# Sparse table for static RMQ (range maximum query)
import math
def build_sparse_table(arr):
n = len(arr)
LOG = int(math.log2(n)) + 1 if n else 1
table = [[0]*n for _ in range(LOG)]
table[0] = arr[:]
j = 1
while (1 << j) <= n:
for i in range(n - (1 << j) + 1):
table[j][i] = max(table[j-1][i], table[j-1][i + (1 << (j-1))])
j += 1
return table
def query(table, l, r):
k = int(math.log2(r - l + 1))
return max(table[k][l], table[k][r - (1 << k) + 1])
arr = [1, 3, -1, -3, 5, 3, 6, 7]
table = build_sparse_table(arr)
k = 3
result = [query(table, i, i + k - 1) for i in range(len(arr) - k + 1)]
print('Sparse table result:', result) # [3, 3, 5, 5, 6, 7]Sublarik Terpanjang Berisi Angka 1 Setelah Menghapus Satu Elemen
LeetCode 1493: diberikan larik biner, temukan panjang sublarik 1 terpanjang setelah menghapus tepat satu elemen (yang dapat berupa 0 atau 1). Ini adalah soal jendela geser. Pertahankan jendela dengan paling banyak satu 0. Jika jendela memiliki lebih dari satu 0, kecilkan dari kiri.
Ini menggunakan pola jendela geser berukuran variabel—bukan antrean dua ujung. Namun, jika dipadukan dengan teknik jendela maksimum: setelah menemukan semua jendela yang valid, panjang maksimum adalah jawabannya. 'Menghapus satu elemen' berarti kita mengizinkan tepat satu 0 dalam jendela yang berisi angka 1.
def longest_subarray(nums):
left = 0
zeros = 0
max_len = 0
for right in range(len(nums)):
if nums[right] == 0:
zeros += 1
while zeros > 1:
if nums[left] == 0:
zeros -= 1
left += 1
# Window [left, right] has at most 1 zero
# After deleting one element, length = right - left (not +1, since we delete one)
max_len = max(max_len, right - left)
return max_len
print(longest_subarray([1,1,0,1])) # 3: delete the 0
print(longest_subarray([0,1,1,1,0,1,1,0,1])) # 5
print(longest_subarray([1,1,1])) # 2: must delete one 1Perbandingan Antrean Dua Ujung, Antrean, dan Tumpukan
Memahami kapan menggunakan setiap wadah adalah kunci dalam wawancara:
- Tumpukan (daftar): LIFO, akses satu ujung. Gunakan untuk DFS, penguraian ekspresi, dan soal tumpukan monoton.
- Antrean (antrean dua ujung dengan appendleft/popleft): FIFO, penambahan dari satu ujung, pengeluaran dari ujung lain. Gunakan untuk BFS dan penjadwalan tugas.
- Antrean dua ujung: kedua ujung dapat diakses dalam O(1). Gunakan untuk jendela geser dengan kedaluwarsa (hapus dari depan) dan invarian monoton (hapus dari belakang). Maksimum jendela geser adalah soal antrean dua ujung yang utama.
collections.deque milik Python adalah alat untuk ketiganya. Gunakan append/pop untuk perilaku tumpukan dan append/popleft atau appendleft/pop untuk perilaku antrean atau antrean dua ujung.
from collections import deque
# deque as stack
stack = deque()
stack.append(1); stack.append(2); stack.append(3)
print('Stack pop:', stack.pop()) # 3 (LIFO)
# deque as queue
queue = deque()
queue.append(1); queue.append(2); queue.append(3)
print('Queue pop:', queue.popleft()) # 1 (FIFO)
# deque as sliding window with front expiry + back monotonic
dq = deque()
nums = [3, 1, 4, 1, 5, 9, 2, 6]
k = 3
for i, n in enumerate(nums):
while dq and dq[0] <= i - k: dq.popleft() # expire front
while dq and nums[dq[-1]] <= n: dq.pop() # maintain back
dq.append(i)
if i >= k - 1:
print(f'Window {nums[max(0,i-k+1):i+1]}: max={nums[dq[0]]}')Sublarik Terpendek dengan Jumlah Setidaknya K: Antrean Dua Ujung + Jumlah Awalan
Sublarik Terpendek dengan Jumlah Setidaknya K (LeetCode 862) adalah soal tingkat lanjut yang memadukan jumlah awalan dengan antrean dua ujung monoton. Bangun jumlah awalan, lalu gunakan antrean dua ujung untuk menemukan, bagi setiap titik ujung kanan, jumlah awalan paling kiri yang memenuhi prefix[right] - prefix[left] >= k. Antrean tersebut mempertahankan jumlah awalan menaik (keluarkan dari belakang agar tetap menaik), dan mengeluarkan dari depan untuk mengumpulkan jawaban yang valid.
Ini adalah salah satu soal jendela geser tersulit karena melibatkan bilangan negatif (sehingga pendekatan dua penunjuk sederhana tidak berlaku) dan mengharuskan antrean dua ujung berfungsi sekaligus sebagai struktur monoton dan mekanisme kedaluwarsa.
from collections import deque
def shortest_subarray(nums, k):
n = len(nums)
prefix = [0] * (n + 1)
for i in range(n):
prefix[i + 1] = prefix[i] + nums[i]
dq = deque() # monotonic increasing deque of indices into prefix
result = float('inf')
for right in range(n + 1):
# Pop from front: valid subarrays ending at `right`
while dq and prefix[right] - prefix[dq[0]] >= k:
result = min(result, right - dq.popleft())
# Pop from back: maintain increasing deque
while dq and prefix[dq[-1]] >= prefix[right]:
dq.pop()
dq.append(right)
return result if result != float('inf') else -1
print(shortest_subarray([1], 1)) # 1
print(shortest_subarray([1, 2], 4)) # -1
print(shortest_subarray([2, -1, 2], 3)) # 3
print(shortest_subarray([84,-37,32,40,95], 167)) # 3Strategi Wawancara untuk Soal Antrean Dua Ujung
Kenali soal antrean dua ujung monoton melalui tanda-tanda berikut: (1) Anda memerlukan maksimum atau minimum dari jendela geser berukuran tetap, (2) Anda memerlukan rekurensi DP dp[i] = f(nums[i], max(dp[i-k..i-1])), atau (3) Anda memerlukan indeks valid terdekat yang memenuhi kondisi monoton.
Dalam wawancara, tulis solusi antrean dua ujung dengan rapi: impor deque, pertahankan dua invarian (kedaluwarsa di depan, sifat monoton di belakang), dan kembalikan hasil mulai dari indeks k-1. Selalu sebutkan kompleksitas waktu O(n) dan ruang O(k) untuk antrean dua ujung (paling banyak k indeks disimpan sekaligus), lalu bandingkan dengan solusi coba semua kemungkinan O(nk) untuk menunjukkan peningkatannya.
from collections import deque
# Clean, interview-ready template
def sliding_window_max_template(nums, k):
if not nums or k == 0:
return []
dq = deque() # monotonic decreasing, stores indices
result = []
for i in range(len(nums)):
# Invariant 1: remove expired indices (outside window)
while dq and dq[0] < i - k + 1:
dq.popleft()
# Invariant 2: remove indices with smaller values (useless)
while dq and nums[dq[-1]] < nums[i]:
dq.pop()
dq.append(i)
# Record result once first full window is established
if i >= k - 1:
result.append(nums[dq[0]])
return result
# Complexity: O(n) time, O(k) space
print(sliding_window_max_template([1,3,-1,-3,5,3,6,7], 3))
print(sliding_window_max_template([1], 1))
print(sliding_window_max_template([], 3))Uji Cepat
Uji pemahaman Anda tentang konsep Struktur Data & Algoritma — Persiapan Wawancara Pemrograman dari pelajaran ini.
Ringkasan Pelajaran
Dalam pelajaran ini Anda mempelajari: antrean dua ujung monoton menurun mempertahankan maksimum jendela di bagian depan sambil membuang elemen dari belakang yang lebih kecil daripada elemen baru, indeks kedaluwarsa dikeluarkan dari depan saat berada di luar batas jendela, dan setiap indeks ditambahkan dan dikeluarkan paling banyak sekali sehingga menghasilkan O(n) secara keseluruhan dengan ruang antrean dua ujung O(k). Berikutnya kita menyelesaikan masalah menampung air hujan menggunakan tumpukan monoton dan pendekatan dua penunjuk.
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Maksimum Jendela Geser dengan Deque Monotonik” gratis?
Ya — teks lengkap “Maksimum Jendela Geser dengan Deque Monotonik” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus Coding Interview Prep, upgrade ke CoddyKit PRO. Kursus Coding Interview Prep mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Maksimum Jendela Geser dengan Deque Monotonik”?
Pertahankan deque indeks menurun untuk menjawab kueri maksimum-dalam-jendela dalam O(1) per elemen, sehingga masalah sliding-window-maximum terselesaikan dalam O(n) Kamu berlatih Coding 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 Coding Interview Prep?
Tidak diperlukan pengalaman sebelumnya. Coding 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 3 dari 4.
Berapa lama pelajaran “Maksimum Jendela Geser dengan Deque Monotonik” 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 Coding Interview Prep ini?
Ya. Setiap pelajaran Coding 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
- Tumpukan Monotonik: Menaik vs Menurun
- Persegi Panjang Terbesar dalam Histogram
- Maksimum Jendela Geser dengan Deque Monotonik
- Menampung Air Hujan: Tumpukan dan Dua Penunjuk