020 — Smallest Range Covering Elements from K Lists¶
Source¶
- Original source: Classic k-way merge with a coverage window
- Platform: LeetCode — https://leetcode.com/problems/smallest-range-covering-elements-from-k-lists/
- Topic: heap / priority queue
- Difficulty: Hard
- Pattern: K-way merge (min-heap of frontiers) + tracking the current max → window
- Basic idea inherited from:
02-arrays-and-stringssliding window +08k-way merge
1. Problem in simple words¶
k-টা list দেওয়া আছে, প্রতিটা বাড়তে থাকা (sorted) ক্রমে সাজানো। তোমাকে এমন একটা ছোট range [a, b] খুঁজতে হবে যেটার ভেতরে প্রতিটা list থেকে অন্তত একটা করে সংখ্যা পড়ে। range যত ছোট (b - a যত কম) তত ভালো; tie হলে যে range-এর a ছোট সেটা নাও। সেই [a, b] return করো।
2. Which basic idea does this inherit from?¶
ভিত দুটো জিনিসের জোড়: 08-এর k-way merge (প্রতিটা list-এর সামনের element নিয়ে একটা min-heap, বারবার সবচেয়ে ছোটটা টানা — যেমন 012/017-এ), আর 02-arrays-and-strings-এর sliding window চিন্তা (একটা window যেটার ভেতরে সব list "covered" থাকে)। এখানে window-টা সরাসরি index-এর না, বরং প্রতিটা list থেকে একটা করে value নিয়ে গড়া।
3. What is the hidden math or logic?¶
লুকানো logic: যদি প্রতিটা list থেকে ঠিক একটা করে value বেছে নাও, তাহলে সেই k-টা value-এর [min, max]-ই একটা valid range। তুমি চাও max - min ছোট করতে। min-টা যে list থেকে এসেছে, সেই list-এ একটু বড় value-এ এগোলে min বাড়ে (range সঙ্কুচিত হওয়ার সুযোগ), কিন্তু max কখনো কমে না। তাই বারবার বর্তমান min-কে তার পরের element দিয়ে replace করে min বাড়াও, প্রতিবার [min, max] মেপে রাখো — কোনো list শেষ হলে থামো।
4. Which data structure is needed?¶
একটা min-heap (k-way merge frontier)। প্রতিটা entry (value, list_index, element_index) — কোন list-এর কোন position থেকে এসেছে তা মনে রাখার জন্য। সাথে একটা সাধারণ variable current_max — heap-এ এই মুহূর্তে থাকা সব value-এর মধ্যে সবচেয়ে বড়টা।
5. Why this data structure fits¶
প্রতি step-এ তোমার দরকার শুধু বর্তমান minimum (যাকে এগিয়ে দিলে window সঙ্কুচিত হতে পারে)। min-heap সেটা top-এ O(1)-এ দেয়, pop/push O(log k)। max আলাদা variable-এ রাখা সস্তা, কারণ নতুন value push করার সময়ই সেটা update করা যায়। পুরো merged sequence কখনো বানাতে হয় না — heap-এ একসাথে বড়জোর k-টা element থাকে।
6. Real-life analogy¶
ভাবো k-টা দৌড়বিদ, প্রত্যেকে নিজের track-এ সামনে এগোচ্ছে (sorted)। তুমি চাও এক মুহূর্ত যেখানে সবাই একে অপরের যত কাছাকাছি সম্ভব। সবচেয়ে পিছিয়ে থাকা দৌড়বিদকে এক পা সামনে এগিয়ে দাও (তার পরের checkpoint-এ) — এতে দলটা কুঁকড়ে আসে, আর সবচেয়ে এগিয়ে থাকা জন তো পিছায় না। প্রতিবার দলের "ছড়ানো" (max − min) মেপে রাখো; কোনো একজন track শেষ করলে খেলা শেষ। "সবচেয়ে পিছিয়ে থাকাকে চট করে দাও" — সেই যন্ত্রই min-heap।
7. Visual explanation¶
[[4,10,15,24,26],[0,9,12,20],[5,18,22,30]] — frontier min-heap (value, list):
শুরু: frontier = (4,L0) (0,L1) (5,L2) max = 5
min=0, max=5 -> range [0,5], width 5 (best এখন [0,5])
0 (L1) pop -> L1-এর পরের 9 push -> frontier (4,L0)(9,L1)(5,L2) max=9
min=4, max=9 -> [4,9] width 5
4 (L0) pop -> 10 push -> (10,L0)(9,L1)(5,L2) max=10
min=5, max=10 -> [5,10] width 5
...
শেষমেশ frontier (24,L0)(20,L1)(22,L2) max=24
min=20, max=24 -> [20,24] width 4 <- সবচেয়ে ছোট
20 (L1) pop -> L1 শেষ -> থামো
উত্তর: [20, 24]
8. Example input and output¶
Input : [[4,10,15,24,26],[0,9,12,20],[5,18,22,30]] -> Output: [20, 24]
Input : [[1,2,3],[1,2,3],[1,2,3]] -> Output: [1, 1] (সব মিলে যায়)
Input : [[10],[11]] -> Output: [10, 11]
Input : [[1,10,11],[20,30]] -> Output: [11, 20]
9. Brute force thinking¶
প্রথম সরল চিন্তা: প্রতিটা list থেকে একটা করে value-এর সব combination বানাও, প্রতিটার [min, max] মাপো, সবচেয়ে ছোট range রাখো।
10. Why brute force is slow¶
প্রতিটা list-এর length যদি গড়ে m হয়, combination-সংখ্যা O(mᵏ) — exponential, k একটু বড় হলেই অসম্ভব। অথচ আমাদের সব combination দেখার দরকার নেই: কেবল min-কে এগিয়ে যাওয়াটাই range সঙ্কুচিত করার একমাত্র productive move, তাই smart traversal-এ O(n log k)-তে নামা যায়।
11. Key observation¶
মূল observation: একটা valid window-এ window সঙ্কুচিত করার একমাত্র উপায় হলো বর্তমান minimum-কে বড় করা — অর্থাৎ যে list থেকে min এসেছে সেই list-এ পরের element-এ যাওয়া। max কমানোর কোনো উপায় নেই (সেটা শুধু বাড়ে), তাই min-কে যতটা সম্ভব max-এর কাছে টেনে আনাই খেলা। আর min সবসময় heap-এর top-এ।
12. Optimized intuition¶
প্রতিটা list-এর প্রথম element নিয়ে min-heap বানাও, current_max = তাদের সর্বোচ্চ। Loop: top (current min) pop করো; [min, current_max] যদি আগের best-এর চেয়ে ছোট হয়, best update করো। সেই list-এ পরের element থাকলে push করো (আর current_max দরকারমতো update করো); না থাকলে থামো — ওই list আর কোনো value দিতে পারবে না, তাই কোনো future window সম্পূর্ণ হবে না। মোট O(n log k)।
13. Thinking tweak¶
মোচড়: "smallest range" না ভেবে ভাবো "k-টা sorted stream একসাথে merge করছি, আর প্রতি মুহূর্তে heap-এ থাকা k-টা frontier value-ই আমার current window।" min এগোলে window নড়ে। "প্রতিটা group থেকে অন্তত একটা cover করতে হবে, sorted lists" — শুনলেই k-way-merge-min-heap + একটা running max মাথায় আনবে।
14. Step-by-step dry run¶
Input [[1,10,11],[20,30]]:
- frontier min-heap: (1, L0, idx0), (20, L1, idx0); current_max = 20
- best = কিছু না (∞ width)
- pop (1, L0): window [1, 20], width 19 → best = [1, 20]; L0-এ পরের 10 push; current_max = max(20, 10) = 20
- pop (10, L0): window [10, 20], width 10 < 19 → best = [10, 20]; L0-এ পরের 11 push; current_max = 20
- pop (11, L0): window [11, 20], width 9 < 10 → best = [11, 20]; L0-এ পরের element নেই → থামো
- return [11, 20]
15. Python solution¶
import heapq
def smallest_range(nums):
# প্রতিটা list-এর প্রথম element নিয়ে frontier min-heap: (value, list_idx, elem_idx)
heap = [(rows[0], i, 0) for i, rows in enumerate(nums)]
heapq.heapify(heap)
current_max = max(rows[0] for rows in nums) # frontier-এর সর্বোচ্চ
best_lo, best_hi = heap[0][0], current_max # একটা valid window দিয়ে শুরু
while True:
val, li, ei = heapq.heappop(heap) # বর্তমান minimum
if current_max - val < best_hi - best_lo: # আরও ছোট window?
best_lo, best_hi = val, current_max
if ei + 1 == len(nums[li]): # এই list শেষ -> আর cover সম্ভব না
break
nxt = nums[li][ei + 1] # একই list-এ পরের element
current_max = max(current_max, nxt) # max শুধু বাড়তে পারে
heapq.heappush(heap, (nxt, li, ei + 1))
return [best_lo, best_hi]
# ---- tests ----
assert smallest_range([[4,10,15,24,26],[0,9,12,20],[5,18,22,30]]) == [20, 24]
assert smallest_range([[1,2,3],[1,2,3],[1,2,3]]) == [1, 1] # সব মিলে যায়
assert smallest_range([[10],[11]]) == [10, 11]
assert smallest_range([[1,10,11],[20,30]]) == [11, 20]
assert smallest_range([[5]]) == [5, 5] # একটাই list, একটাই value
print("সব test pass!")
16. Line-by-line code explanation¶
heap = [(rows[0], i, 0) for i, rows in enumerate(nums)]— প্রতিটা list-এর প্রথম element, সাথে(list index, element index)যাতে জানি কোথা থেকে এলো।heapq.heapify(heap)— O(k)-এ min-heap; top সবসময় frontier-এর সবচেয়ে ছোট value।current_max = max(...)— frontier-এ এই মুহূর্তে থাকা সব value-এর সর্বোচ্চ; window-এর উপরের প্রান্ত।best_lo, best_hi = heap[0][0], current_max— শুরুতেই একটা valid window (min ও max) রেখে দিই, পরে ছোট পেলে update।val, li, ei = heapq.heappop(heap)— বর্তমান minimum আর সে কোন list-এর কোন position।if current_max - val < best_hi - best_lo:—[val, current_max]আগের best-এর চেয়ে সরু হলে best update।if ei + 1 == len(nums[li]): break— যে list থেকে min এলো সেটা শেষ → আর এগোনো গেলে ওই list cover হবে না, তাই থামো।current_max = max(current_max, nxt)— নতুন value push-এর আগে max দরকারমতো বাড়াও (কখনো কমে না)।heapq.heappush(heap, (nxt, li, ei + 1))— একই list-এর পরের element frontier-এ আনো।
17. Output walkthrough¶
smallest_range([[1,10,11],[20,30]]) section 14-এর dry run মেনে [11, 20] দেয় — assert pass। প্রথম বড় example-এ frontier ধীরে ধীরে এগিয়ে [20, 24]-এ সবচেয়ে সরু window পায়। [[1,2,3]] x3-এ শুরুতেই সব list-এ 1 আছে → [1, 1]। [[10],[11]] → শুরুর frontier-ই উত্তর [10, 11]। single list [[5]] → একটাই value [5, 5]। range একটা নির্দিষ্ট জোড়া, তাই সরাসরি list-সমতা assert। সব pass-এর পরে print হয়: সব test pass!।
18. Time complexity¶
O(n log k) — মোট n-টা element (সব list মিলে), প্রতিটা বড়জোর একবার push/pop হয় (প্রতিটা O(log k), কারণ heap-এ সর্বদা ≤ k entry)। heapify O(k)। তাই dominant term O(n log k)।
19. Space complexity¶
O(k) — heap-এ একসাথে ঠিক k-টা entry (প্রতিটা list থেকে একটা)। আউটপুট ছাড়া বাড়তি memory heap-সমান, input-এর মোট আকারের উপর নির্ভর করে না।
20. Common mistakes¶
current_maxtrack না করা, প্রতিবার heap-এর সব ঘেঁটে max বের করা — এতে step-প্রতি O(k) যোগ হয়, অযথা ধীর।- কোন list থেকে value এলো (
list_idx) tuple-এ না রাখা — তখন পরের element কোথা থেকে push করবে জানবে না। - list শেষ হলে
breakনা দেওয়া — খালি index access করে IndexError, বা ভুলভাবে অসম্পূর্ণ window গোনা। - tie-breaking ভুল করা — "strictly smaller width হলে তবেই update" রাখলে প্রথম (সবচেয়ে ছোট
a-ওয়ালা) best আপনাআপনি ধরে থাকে।
21. Which basic problem this inherits from¶
ভিত্তি 08-এর patterns.md-র Pattern 4 (K-way merge — প্রতিটা sorted source-এর frontier একটা min-heap-এ, বারবার ছোটটা টানো) — যেমন 012 Kth Smallest in Sorted Matrix আর 017 Merge k Sorted Lists। তার সাথে 02-arrays-and-strings-এর sliding-window "একটা valid window সঙ্কুচিত করো" idea মিশেছে।
22. Similar harder problems¶
- Merge k Sorted Lists — https://leetcode.com/problems/merge-k-sorted-lists/ (এই tracker-এর #17; বিশুদ্ধ k-way merge, window ছাড়া)
- Kth Smallest Element in a Sorted Matrix — https://leetcode.com/problems/kth-smallest-element-in-a-sorted-matrix/ (#12; k-way merge দিয়ে kth element)
- Sliding Window Median — https://leetcode.com/problems/sliding-window-median/ (#21; window + heap, ভিন্ন twist)
23. Pattern learned¶
K-way merge + coverage window: প্রতিটা sorted source-এর frontier একটা min-heap-এ রাখো, আর একটা running max ধরে রাখো; min-কে বারবার এগিয়ে দিয়ে [min, max] window সঙ্কুচিত করো, কোনো source শেষ হলে থামো। "প্রতিটা group থেকে অন্তত একটা cover করো, যত সরু window সম্ভব" — এই গন্ধ পেলেই এই pattern।
24. Final summary¶
ভবিষ্যতের আমাকে: smallest range = k-way merge-এর frontier min-heap + একটা current_max। প্রতি step-এ min pop করো, [min, current_max] best-এর সাথে তুলনা করো, সেই list-এর পরের element push করো (max update সহ); কোনো list ফুরোলে থামো। মনে রেখো — min এগোলেই window সঙ্কুচিত হয়, max কখনো কমে না। heap top সবসময় সেই "সবচেয়ে পিছিয়ে থাকা দৌড়বিদ"।
মনে রাখার নিয়ম (legal): সব নিজের ভাষায় লেখা; problem statement copy করা হয়নি; official link শুধু attribution-এর জন্য; "asked by Google/Amazon" দাবি নেই — এটা একটা common interview pattern।