Tekdüze Kuyrukla Kayan Pencere Maksimumu
Pencere içindeki maksimum sorgularını öğe başına O(1) sürede yanıtlamak için dizinlerden oluşan azalan bir çift uçlu kuyruk tutun ve kayan pencere maksimumu problemini O(n) sürede çözün.
Tekdüze Kuyrukla Kayan Pencere Maksimumu, CoddyKit'te ücretsiz bir Coding Interview Prep dersidir. Bu, 4 dersinin 3. dersidir. Aşağıdan dersin tamamını ücretsiz okuyabilir, sonra tarayıcıda yerleşik kod editörü ve 7/24 yapay zeka koçu ile uygulamalı olarak pratik yapabilirsin. Bu, Coding Interview Prep öğrenme yolunun bir parçasıdır ve ilerlemeniz web ve CoddyKit uygulaması arasında senkronize olur. Coding Interview Prep kursu toplamda 4 dersten oluşur.
Kayar Pencere Maksimumu Problemi
Kayar Pencere Maksimumu problemi (LeetCode 239), bir dizi ve k pencere boyutu verir. Pencere soldan sağa her seferinde bir konum kayarken her penceredeki maksimum öğeyi çıktı olarak verin. Kaba kuvvet yaklaşımı, k öğeli her pencerenin maksimumunu O(k) içinde hesaplar — toplamda O(nk) verir; bu da büyük k değerleri için fazla yavaştır.
Monotonik çift uçlu kuyruk çözümü, indislerden oluşan azalan bir çift uçlu kuyruğu koruyarak toplamda O(n) karmaşıklığa ulaşır. Ön taraf her zaman geçerli pencerenin maksimumunun indisini tutar; böylece O(1) maksimum sorguları sağlanırken her iki uçta da işlem yapılabilir.
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))Monotonik Çift Uçlu Kuyruk: Temel Fikir
İndisleri (değerleri değil) saklayan azalan monotonik bir çift uçlu kuyruk koruyun. Değişmez koşul: nums[deque[0]] >= nums[deque[1]] >= ... >= nums[deque[-1]]. i indisini eklemeden önce:
- Süresi dolan indisleri önden kaldırın:
deque[0] <= i - kise indis pencereden çıkmıştır. - Daha küçük öğeleri arkadan kaldırın:
nums[deque[-1]] <= nums[i]olduğu sürece bu indisler gelecekteki hiçbir pencerenin maksimumu olamaz (soldadırlar ve daha küçüktürler); bu yüzden onları atın.
Bu işlemlerden sonra i'yi arkaya ekleyin. Ön taraf her zaman geçerli pencerenin maksimumunu verir.
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]Çift Uçlu Kuyruğu Adım Adım İzleme
[1, 3, -1, -3, 5, 3, 6, 7] dizisini k=3 ile adım adım izleyelim:
- i=0 (1): dq=[0]
- i=1 (3): pop 0 (1<3), dq=[1]
- i=2 (-1): -1<3 olduğundan koru, dq=[1,2]. Pencere [1,3,-1], maksimum=nums[1]=3
- i=3 (-3): -3<-1, dq=[1,2,3]. Ön tarafı kontrol edin: 1 > 3-3=0, OK. Pencere maksimumu=3
- i=4 (5): pop 3,2,1 (tümü daha küçük), dq=[4]. Ön taraf 4 > 4-3=1, OK. Maksimum=5
- i=5 (3): 3<5, dq=[4,5]. Ön taraf 4 > 5-3=2, OK. Maksimum=5
- i=6 (6): pop 5,4 (ikisi de daha küçük), 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)Her Öğenin En Fazla Bir Kez Kuyruğa Eklenip Çıkarılmasının Nedeni
O(n) garantisi, monotonik yığındakiyle aynı itfa edilmiş argümana dayanır: her indis çift uçlu kuyruğa tam olarak bir kez eklenir ve süresi dolduğunda önden veya yerini daha büyük bir öğe aldığında arkadan olmak üzere en fazla bir kez kaldırılır. Tüm döngü boyunca toplam çift uçlu kuyruk işlemi en fazla 2n'dir.
İç döngüler genel karmaşıklığı artırmaz — bu döngülerde yapılan her pop işleminin karşılığı daha önceki ekleme işlemidir. Bu, monotonik yığınla aynı mantıktır; ancak her iki uçtan da kaldırmaya izin veren bir çift uçlu kuyruğa genişletilmiştir.
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)Kayar Pencere Minimumu
Kayar pencere minimumu bunun simetrik karşılığıdır: artan monotonik bir çift uçlu kuyruk koruyun (yeni öğe arkadaki öğeden küçük olduğunda arkadan pop yapın). Ön taraf her zaman geçerli pencerenin minimumunu tutar. Diğer tüm adımlar maksimum sürümüyle aynıdır — yalnızca karşılaştırma yönünü tersine çevirin.
Kayar pencere minimumunu isteyen problemler çoğu zaman daha büyük algoritmaların alt problemleri olarak karşımıza çıkar. Örneğin, k ara durak içeren bir yol boyunca malları taşımanın minimum maliyetini bulmak, DP dizileri üzerinde kayar pencere minimumu gerektirebilir.
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]Sıçrama Oyunu VI: Monotonik Çift Uçlu Kuyrukla DP
Sıçrama Oyunu VI (LeetCode 1696), DP ile monotonik çift uçlu kuyruğun birleştiği klasik bir örnektir. Bir dizi ve en fazla k uzunluğunda sıçrama verildiğinde, 0 indisinden başlayarak her adımda 1 ila k adım ileri sıçrar ve hedef hücrenin puanını eklersiniz. Toplam puanı maksimize edin. DP bağıntısı dp[i] = nums[i] + max(dp[i-k], ..., dp[i-1]) şeklindedir. DP dizisi üzerindeki kayar pencere maksimumu toplamda O(n) süre sağlar.
Bu örüntü — her hücrenin önceki hücrelerin sabit boyutlu bir penceresinin maksimumuna bağlı olduğu DP bağıntısı — sık görülür ve her zaman monotonik çift uçlu kuyruk gerektirir.
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)) # 0Kayar Pencere Maksimumu: Segment Ağacı Seçeneği
Pencere boyutunun değiştiği (sabit k olmadığı) problemlerde monotonik çift uçlu kuyruk doğrudan uygulanamaz. Bunun yerine, O(n log n) ön işleme sonrasında sorgu başına O(1) sürede statik aralık maksimumu sorguları için seyrek tablo veya sorgu başına O(log n) sürede dinamik güncellemeler için segment ağacı kullanın. Ancak sabit k değerli kayar pencerelerde O(n) karmaşıklığıyla çift uçlu kuyruk eşsizdir.
Mülakatlarda pencere boyutu sabitse O(n log n)'lik segment ağacı yerine O(n)'lik monotonik çift uçlu kuyruğu her zaman tercih edin. Ödünleşimi belirtin: çift uçlu kuyruk keyfi pencere boyutlarını veya güncellemeleri işleyemezken segment ağaçları bunları işleyebilir.
# 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]Bir Öğe Silindikten Sonra En Uzun 1'ler Alt Dizisi
LeetCode 1493: ikili bir dizi verildiğinde, tam olarak bir öğe (0 veya 1 olabilir) silindikten sonra 1'lerden oluşan en uzun alt dizinin uzunluğunu bulun. Bu, kayar pencere problemidir. En fazla bir 0 içeren bir pencere koruyun. Pencerede birden fazla 0 olduğunda soldan küçültün.
Bu, değişken boyutlu kayar pencere örüntüsünü kullanır; çift uçlu kuyruk kullanmaz. Ancak bunu maksimum pencere tekniğiyle birleştirebilirsiniz: tüm geçerli pencereleri bulduktan sonra maksimum uzunluk yanıttır. “Bir öğe sil” ifadesi, 1'lerden oluşan penceremizde tam olarak bir 0'a izin verdiğimiz anlamına gelir.
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 1Çift Uçlu Kuyruk ve Kuyruk ve Yığın Karşılaştırması
Her kapsayıcıyı ne zaman kullanacağınızı anlamak mülakatlar için önemlidir:
- Yığın (liste): LIFO, tek uçtan erişim. DFS, ifade ayrıştırma ve monotonik yığın problemleri için kullanın.
- Kuyruk (çift uçlu kuyrukta sola ekleme/popleft): FIFO, bir uçtan ekleme, diğer uçtan çıkarma. BFS ve görev zamanlama için kullanın.
- Çift Uçlu Kuyruk: her iki uca da O(1) içinde erişilebilir. Süresi dolan öğeleri önden kaldırmak ve monotonik değişmez koşulu korumak için arkadan kaldırmak üzere kayar pencere problemlerinde kullanın. Kayar pencere maksimumu, çift uçlu kuyruk problemlerinin klasik örneğidir.
Python'ın collections.deque yapısı bu üçünün tamamı için kullanılabilir. Yığın davranışı için append/pop, kuyruk veya çift uçlu kuyruk davranışı için append/popleft ya da sola ekleme/pop kullanın.
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]]}')Toplamı En Az K Olan En Kısa Alt Dizi: Çift Uçlu Kuyruk + Önek Toplamları
Toplamı En Az K Olan En Kısa Alt Dizi (LeetCode 862), önek toplamlarını monotonik bir çift uçlu kuyrukla birleştiren ileri düzey bir problemdir. Önek toplamlarını oluşturun, ardından her sağ uç noktası için prefix[right] - prefix[left] >= k koşulunu sağlayan en soldaki önek toplamını bulmak üzere bir çift uçlu kuyruk kullanın. Çift uçlu kuyruk, artan önek toplamlarını korur (artan düzeni korumak için arkadan pop yapar) ve geçerli yanıtları toplamak için önden pop yapar.
Bu, negatif sayılar içerdiği için (basit iki işaretçi yaklaşımını geçersiz kılar) ve çift uçlu kuyruğun hem monotonik yapı hem de süresi dolan öğeleri çıkarma mekanizması olarak kullanılmasını gerektirdiği için en zor kayar pencere problemlerinden biridir.
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)) # 3Çift Uçlu Kuyruk Problemleri İçin Mülakat Stratejisi
Şu işaretlerle bir monotonik çift uçlu kuyruk problemini tanıyın: (1) sabit boyutlu bir kayar pencerenin maksimumuna veya minimumuna ihtiyacınız vardır, (2) dp[i] = f(nums[i], max(dp[i-k..i-1])) biçiminde bir DP bağıntısına ihtiyacınız vardır veya (3) monotonik bir koşulu sağlayan en yakın geçerli indise ihtiyacınız vardır.
Mülakatlarda çift uçlu kuyruk çözümünü temiz bir şekilde kodlayın: deque içe aktarın, iki değişmez koşulu (önden süresi dolma, arkadan monotonluk) koruyun ve sonuçları k-1 indisinden başlayarak döndürün. Her zaman O(n) zaman karmaşıklığından ve çift uçlu kuyruk için O(k) alandan (aynı anda en fazla k indis saklanır) söz edin; gelişmeyi göstermek için bunları O(nk) karmaşıklığındaki kaba kuvvet çözümüyle karşılaştırın.
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))Hızlı Kontrol
Bu derste ele alınan Veri Yapıları & Algoritmalar — Kodlama Mülakatı Hazırlığı kavramlarını ne ölçüde anladığınızı sınayın.
Ders Özeti
Bu derste şunları öğrendiniz: azalan monotonik bir çift uçlu kuyruk, yeni gelenlerden daha küçük olan öğeleri arkadan atarken pencere maksimumunu ön tarafında tutar, süresi dolan indisler pencere sınırının dışına çıktıklarında önden kaldırılır ve her indis en fazla bir kez kuyruğa eklenip çıkarıldığından toplam karmaşıklık O(n), çift uçlu kuyruk alanı ise O(k) olur. Sırada, hem monotonik yığın hem de iki işaretçi yaklaşımını kullanarak yağmur suyu tutma problemini çözeceğiz.
Sıkça Sorulan Sorular
“Tekdüze Kuyrukla Kayan Pencere Maksimumu” dersi ücretsiz mi?
Evet — “Tekdüze Kuyrukla Kayan Pencere Maksimumu” dersin tüm metni burada web'de ücretsiz olarak okunabilir. Etkileşimli olarak pratik yapmak (yerleşik kod editörü ve 7/24 yapay zeka koçu) ve Coding Interview Prep kursunun geri kalanını açmak için CoddyKit PRO'ya yükselt. Coding Interview Prep kursu toplamda 4 dersten oluşur.
“Tekdüze Kuyrukla Kayan Pencere Maksimumu” dersinde ne öğreneceğim?
Pencere içindeki maksimum sorgularını öğe başına O(1) sürede yanıtlamak için dizinlerden oluşan azalan bir çift uçlu kuyruk tutun ve kayan pencere maksimumu problemini O(n) sürede çözün. Coding Interview Prep ile uygulamalı kodu tarayıcıda doğrudan çalıştırarak pratik yaparsın ve 7/24 yapay zeka koçu dersi çalışırken sorularını yanıtlar.
Coding Interview Prep öğrenmeye başlamak için deneyim gerekli mi?
Önceden deneyim gerekmez. CoddyKit'te Coding Interview Prep, başlangıçtan ileri seviyeye kadar yapılandırıldığı için buradan başlayabilir veya başından başlayıp kendi hızında ilerleme yapabilirsin. Bu, 4 dersinin 3. dersidir.
“Tekdüze Kuyrukla Kayan Pencere Maksimumu” dersi ne kadar sürer?
Çoğu CoddyKit dersi yaklaşık 5–10 dakika sürer. Her biri kısa ve etkileşimli olduğu için sabit ilerleme yaparsın ve web ile uygulama arasında tam olarak bıraktığın yerden devam edebilirsin.
Bu Coding Interview Prep dersinde kod yazıp çalıştırabilir miyim?
Evet. Her Coding Interview Prep dersi yerleşik bir kod editörü içerir, bu sayede tarayıcıda gerçek kod yazıp çalıştırabilir ve anlık yapay zeka geri bildirimi alırsın — yerel kurulum gerekli değildir.
Bu kursun tüm dersleri
- Tekdüze Yığın: Artan ve Azalan
- Histogramdaki En Büyük Dikdörtgen
- Tekdüze Kuyrukla Kayan Pencere Maksimumu
- Yağmur Suyu Biriktirme: Yığın ve İki İşaretçi