Stack and Queue — Core Operations¶
আমরা stack-কে ধরি এক প্রান্তে ব্যবহার করা একটা Python list হিসেবে, আর queue/deque-কে collections.deque হিসেবে। প্রতিটা operation-এ: এটা কী করে, খরচটা যা সেটা কেন, একটা before/after ছবি, code, আর pitfall।
1. Stack push — stack.append(x)¶
কী: x-কে stack-এর উপরে বসাও।
কেন O(1) amortized: এটা হুবহু শেষে একটা dynamic-array append — spare room-এ লেখো; শুধু কালেভদ্রে doubling copy হয় (দেখো ../02-arrays-and-strings/operations.md)।
Pitfalls: "top"-এর জন্য একটাই convention বেছে নাও (এখানে: list-এর ডান প্রান্ত) আর কখনো মেশাবে না। insert(0, x) দিয়ে push করা logically কাজ করে কিন্তু O(n) — একটা নীরব performance bug।
2. Stack pop — stack.pop()¶
কী: top element-টা সরাও আর return করো।
কেন O(1): array-র শেষ element সরালে কিছুই shift হয় না।
Pitfalls: খালি list-এ pop() করলে IndexError ওঠে। if stack: দিয়ে guard করো, অথবা algorithm এমনভাবে design করো যাতে ওই point-এ emptiness অসম্ভব হয় (আর comment-এ লেখো কেন)।
3. Stack peek — stack[-1]¶
কী: না সরিয়ে top-টা দেখো।
কেন O(1): জানা position-এ index access।
Pitfalls: খালি stack-এ peek করলেও IndexError ওঠে। Monotonic-stack loop করে while stack and stack[-1] < x: — ওই stack and guard-টা load-bearing।
4. Enqueue — deque-তে q.append(x)¶
কী: লাইনের পেছনে যোগ দাও।
কেন O(1): deque পেছনের block-এর খালি slot-এ লেখে; কোনো shifting নেই, পুরো structure-এর কোনো reallocation নেই।
Pitfalls: পেছনে কোনোটাই না — বিপদটা সামনে (পরের operation)।
5. Dequeue — q.popleft()¶
কী: লাইনের সামনের জনকে serve করো আর সরাও।
কেন O(1): deque তার front marker-টা front block-এর ভেতরে এগিয়ে নেয়। তুলনা করো: list.pop(0)-কে বাকি প্রতিটা element বাঁয়ে shift করতে হয় — O(n)।
Pitfalls: BFS loop-এ list.pop(0) ব্যবহার করাই হলো THE classic Python ভুল — program-টা correct কিন্তু চুপচাপ O(n^2), বড় input-এ time out করে। খালি deque-তে popleft করলে IndexError ওঠে; BFS loop সেটা এড়ায় while q: দিয়ে।
6. Peek front / back — q[0], q[-1]¶
কী: না সরিয়ে যেকোনো প্রান্ত পরীক্ষা করো।
কেন O(1): deque দুই প্রান্তের block-এরই direct reference ধরে রাখে।
Pitfalls: deque-র মাঝখানে index করা (q[k]) O(n) — deque শুধু প্রান্তের জন্য optimized। মাঝখানে fast access দরকার হলে আসলে তোমার দরকার ছিল একটা list।
7. Deque back-pop — q.pop()¶
কী: পেছন থেকে সরাও — যে operation একটা queue-কে deque বানায়।
কেন O(1): popleft-এর symmetric।
Pitfalls: monotonic-deque code-এ তুমি pop() (useless পেছনের element eject করতে) আর popleft() (expired সামনের element eject করতে) — দুটোই ডাকবে। এগুলো গুলিয়ে ফেললে এমন ভুল answer আসে যেগুলো ছোট test-এ plausible দেখায় — সন্দেহ হলেই visual-explanation.md section 7-এর centerpiece table-টা আবার trace করো।
8. Size আর emptiness — len(q), not q¶
কী: কয়টা item আছে; খালি কিনা।
কেন O(1): দুটো structure-ই একটা size counter maintain করে।
Pitfalls: while len(q) > 0: কাজ করে কিন্তু while q:-ই idiom। Loop-এর শুরুতে n = len(q) cache করা level-by-level BFS-এর জন্য একদম ঠিক (প্রতি outer iteration-এ একটা full layer process করো) — কিন্তু পুরো queue শেষ করতে চাইলে ভুল।
9. Bounded deque — deque(maxlen=k)¶
কী: এমন একটা deque যেটা বড়জোর k-টা item ধরে; ভরা অবস্থায় add করলে উল্টো প্রান্ত থেকে চুপচাপ ফেলে দেয়।
কেন matter করে: "last k events" buffer-এর জন্য perfect (recent log, moving average)।
recent = deque(maxlen=3)
for x in [1, 2, 3, 4]:
recent.append(x)
# recent is deque([2, 3, 4]) — the 1 fell off the front automatically
Pitfalls: চুপচাপ ফেলে দেওয়াটা এখানে feature, আর যেখানে তুমি আশা করোনি সেখানে bug। Sliding-window-maximum-এর জন্য maxlen ব্যবহার কোরো না — monotonic deque-কে নিজের eviction নিজে control করতেই হবে (front eviction নির্ভর করে index-এর উপর, count-এর উপর না)।
10. MinStack — twin stack দিয়ে O(1) minimum¶
কী: এমন একটা stack যেটা "current minimum কত?"-এর উত্তরও O(1)-এ দেয়।
কেন O(1): মূল stack-এর পাশে একটা দ্বিতীয় stack রাখো, যার top সবসময় মূল stack-এ এই মুহূর্তে থাকা সবকিছুর minimum। প্রতিটা push-এর সাথে twin-এ push করো min(x, current_min); দুটো একসাথে pop করো। Minimum-এর history আপনা থেকেই সংরক্ষিত থাকে।
push 5, 2, 7: main: [5, 2, 7] mins: [5, 2, 2]
^ top of mins = current min = 2
pop (7): main: [5, 2] mins: [5, 2] min still 2
pop (2): main: [5] mins: [5] min back to 5 — for free
# full class in implementation.py
self.stack.append(x)
self.mins.append(min(x, self.mins[-1] if self.mins else x))
Pitfalls: শুধু একটা current_min variable রাখলে minimum-টা pop হওয়া মাত্র fail করে — history ছাড়া আগের minimum-টা ফিরে পাওয়া যায় না। Twin stack-ই হলো সেই history।
Big-O summary table¶
| Operation | Stack (list) |
Queue (deque) |
Deque | Wrong tool (list as queue) |
|---|---|---|---|---|
| push / append back | O(1) am. | O(1) | O(1) | O(1) |
| pop top / back | O(1) | — | O(1) | O(1) |
| dequeue / pop front | — | O(1) | O(1) | O(n) ← the trap |
| peek (either end) | O(1) | O(1) | O(1) | O(1) |
| index middle | O(1) | O(n) | O(n) | O(1) |
| search by value | O(n) | O(n) | O(n) | O(n) |
| size / empty check | O(1) | O(1) | O(1) | O(1) |
| MinStack get_min | O(1) | — | — | — |
Table-টা column ধরে পড়ো: প্রতিটা structure ঠিক সেখানেই সস্তা যেখানে তার discipline থাকে, আর সেখানেই দামি যেখানে সে access নিষেধ করে। Structure বেছে নেওয়া = কোন column-এর cost তুমি দেবে সেটা বেছে নেওয়া।
এরপর: named technique গুলো — full Sliding Window Maximum walkthrough সহ — patterns.md-তে।