011 — Merge Intervals¶
Source¶
- Original source: Classic capstone interview problem (sort then sweep)
- Platform: LeetCode — https://leetcode.com/problems/merge-intervals/
- Topic: sorting + arrays
- Difficulty: Medium
- Pattern: Sort by start, তারপর এক-পাসে overlap collapse (sweep)
- Basic idea inherited from: এটা একটা cross-topic capstone — এখানে মেশে 03 sorting (interval-গুলোকে start দিয়ে sort করে একটা সাজানো timeline বানানো) আর 02 arrays (সেই সাজানো list-এর উপর বাঁ-থেকে-ডান একবার scan করে পাশাপাশি জিনিস compare করা)। দুই tool জোড়া লাগলেই এই problem।
1. Problem in simple words¶
তোমাকে কতগুলো interval দেওয়া আছে, প্রতিটা [start, end] রূপে — যেমন কোনো মিটিংয়ের শুরু আর শেষ সময়। এদের মধ্যে কিছু একে অন্যের সাথে ছুঁয়ে যায় বা overlap করে। তোমার কাজ: যেসব interval overlap করে, তাদের একসাথে জুড়ে একটা বড় interval বানাও, আর শেষে এমন একগুচ্ছ আলাদা (non-overlapping) interval ফেরত দাও যেগুলো একই জায়গা cover করে।
intervals : [[1, 3], [2, 6], [8, 10], [15, 18]]
[1,3] আর [2,6] overlap করে -> জুড়ে [1,6]
ফলাফল : [[1, 6], [8, 10], [15, 18]]
2. Which basic idea does this inherit from?¶
এই problem দুটো আগের chapter-এর tool জোড়া দেয়:
- 03 sorting থেকে: interval-গুলোকে আগে start time দিয়ে sort করা। এক বার সাজানো হয়ে গেলে, যে interval-গুলো overlap করতে পারে তারা list-এ পাশাপাশি বসে — তখন আর সব pair মিলিয়ে দেখার দরকার নেই।
- 02 arrays থেকে: সেই সাজানো array-র উপর single pass scan, যেখানে প্রতি step-এ শুধু "আগের জমানো interval" আর "এখনকার interval"-কে compare করি।
একা sorting দেয় "overlap-রা পাশাপাশি" এই গ্যারান্টি; একা array-scan দেয় পাশাপাশি জিনিস জোড়ার ক্ষমতা। দুটো মিললে O(n log n)।
3. What is the hidden math or logic?¶
লুকানো logic একটা overlap invariant: যদি list start দিয়ে sorted হয়, তাহলে নতুন interval [s, e] আগের জমানো interval [ps, pe]-র সাথে overlap করে ঠিক তখনই যখন s <= pe। কারণ sort করায় s >= ps নিশ্চিত, তাই শুধু s যদি pe-র ভেতরে (বা সমান) পড়ে তবেই দুটো ছুঁয়ে আছে। overlap হলে নতুন end হয় max(pe, e) (একটা আরেকটাকে গিলে ফেলতে পারে, তাই max)।
4. Which data structure is needed?¶
- ইনপুট হিসেবে interval-দের একটা list/array (02 arrays)।
- ফলাফল জমাতে আরেকটা result list (
merged), যেটায় শেষ element-টাই বারবার "চলমান জমানো interval"-এর কাজ করে।
কোনো ভারী structure লাগে না — sort + একটা output list-ই যথেষ্ট।
5. Why this data structure fits¶
আমাদের শুধু দরকার "সর্বশেষ জমানো interval"-টাকে দ্রুত দেখা ও বাড়ানো। একটা result list-এর শেষ element (merged[-1]) ঠিক সেই কাজটা O(1)-এ করে — হয় তার end বাড়াই, নয় নতুন interval push করি। sort (03) overlap-দের পাশাপাশি এনে দেওয়ায় আমাদের কখনো পিছনে ফিরে তাকাতে হয় না, তাই এই append-only list-ই (02 arrays-এর সরল রূপ) নিখুঁত। এ কারণেই extra space শুধু output, আর কাজটা single pass।
6. Real-life analogy¶
ভাবো তুমি সারাদিনের সব মিটিং একটা ক্যালেন্ডারে বসাচ্ছ, শুরুর সময় অনুযায়ী সাজিয়ে। নতুন একটা মিটিং নিলে দেখো — এটা কি আগের চলতি মিটিং শেষ হওয়ার আগেই শুরু হচ্ছে? হলে দুটো আসলে একটাই লম্বা ব্যস্ত-সময়, তাই শেষ সময়টা যেটা বেশি দূর সেটা রাখো। নাহলে আগের মিটিং শেষ, নতুন আলাদা একটা শুরু। দিনের শেষে তোমার হাতে থাকে কয়েকটা পরিষ্কার "ব্যস্ত block"।
7. Visual explanation¶
[[1,3],[2,6],[8,10],[15,18]] — start দিয়ে sort (এখানে আগেই sorted), তারপর sweep:
sorted : [1,3] [2,6] [8,10] [15,18]
নাও [1,3] -> merged খালি, push merged = [[1,3]]
নাও [2,6] -> 2 <= 3 ? হ্যাঁ overlap end = max(3,6)=6 merged = [[1,6]]
নাও [8,10] -> 8 <= 6 ? না push merged = [[1,6],[8,10]]
নাও [15,18] -> 15 <= 10 ? না push merged = [[1,6],[8,10],[15,18]]
ফলাফল = [[1,6],[8,10],[15,18]]
8. Example input and output¶
Input : [[1,3],[2,6],[8,10],[15,18]] -> Output: [[1,6],[8,10],[15,18]]
Input : [[1,4],[4,5]] -> Output: [[1,5]] # ছোঁয়াও overlap (4 <= 4)
Edge case 1 (একটা interval): [[5,7]] -> [[5,7]]
Edge case 2 (একটা আরেকটাকে গেলে): [[1,10],[2,3]] -> [[1,10]]
Edge case 3 (unsorted input): [[2,6],[1,3]] -> [[1,6]] # আগে sort করতেই হবে
9. Brute force thinking¶
প্রথম সরল চিন্তা: sort না করে বারবার পুরো list-টা ঘুরে দেখি কোনো দুটো interval overlap করছে কি না; পেলে তাদের জুড়ে দিই, আবার শুরু থেকে দেখি। কোনো overlap আর না পাওয়া পর্যন্ত repeat।
repeat:
found = False
প্রতিটা pair (i, j):
if overlap(i, j):
ওদের জুড়ে একটা বানাও, দুটো মুছে যোগ করো
found = True; break
if not found: break
10. Why brute force is slow¶
প্রতিবার সব pair মেলানো O(n^2), আর প্রতিটা merge-এর পর আবার শুরু থেকে — সব মিলিয়ে O(n^3) পর্যন্ত যেতে পারে। কারণ আমরা কোনো order ব্যবহার করছি না, তাই একই interval বারবার compare হয়। sort (03) একবার করে নিলে overlap-রা পাশাপাশি বসে, আর single pass-ই (02) যথেষ্ট হয়ে যায়।
11. Key observation¶
মূল observation: start দিয়ে sort করলে, একটা interval কেবল তার ঠিক-আগের জমানো interval-এর সাথেই overlap করতে পারে — দূরের কারো সাথে নয়। কারণ start বাড়ছে, তাই পরের কোনো interval আগের জমানোটার শুরুর আগে ঢুকতে পারে না। এই এক চিন্তাই O(n^2) compare-কে এক-পাসে নামিয়ে আনে।
12. Optimized intuition¶
interval-গুলো start দিয়ে sort করো। একটা খালি merged list রাখো। এক এক করে interval নাও: যদি merged খালি হয় বা এখনকার start আগের জমানোটার end-এর চেয়ে বড় হয় (gap আছে), নতুন interval হিসেবে push করো। নাহলে overlap — আগের জমানোটার end-কে max(আগের end, এখনকার end) করে বাড়াও। পুরো list শেষ হলে merged-ই উত্তর।
13. Thinking tweak¶
মোচড়: "সব pair overlap করে কি না দেখব" ভাবার বদলে আগে sort করে নাও — তখন প্রশ্নটা নেমে আসে শুধু "এখনকার start কি আগের end ছাড়িয়ে গেছে?"-তে। বিশাল pairwise তুলনা একটা সাজানো list-এর উপর single sweep-এ গলে যায়।
14. Step-by-step dry run¶
Input [[1,4],[2,3],[5,7]]:
- start দিয়ে sort:
[[1,4],[2,3],[5,7]](আগেই সাজানো);merged = [] - নাও
[1,4]:mergedখালি → push;merged = [[1,4]] - নাও
[2,3]:2 <= 4→ overlap; end =max(4, 3) = 4;merged = [[1,4]] - নাও
[5,7]:5 <= 4? না → push;merged = [[1,4],[5,7]] - শেষ → return
[[1,4],[5,7]]
15. Python solution¶
def merge_intervals(intervals):
if not intervals:
return []
intervals.sort(key=lambda iv: iv[0]) # start দিয়ে sort (03 sorting)
merged = [intervals[0][:]] # প্রথমটা দিয়ে শুরু (copy)
for s, e in intervals[1:]: # বাকিগুলো একবার scan (02 arrays)
if s <= merged[-1][1]: # এখনকার start আগের end-এর ভেতরে?
merged[-1][1] = max(merged[-1][1], e) # overlap -> end বাড়াও
else:
merged.append([s, e]) # gap -> নতুন interval
return merged
# ---- tests ----
assert merge_intervals([[1,3],[2,6],[8,10],[15,18]]) == [[1,6],[8,10],[15,18]]
assert merge_intervals([[1,4],[4,5]]) == [[1,5]] # ছোঁয়াও overlap
assert merge_intervals([[5,7]]) == [[5,7]] # একটা interval
assert merge_intervals([[1,10],[2,3]]) == [[1,10]] # একটা আরেকটাকে গেলে
assert merge_intervals([[2,6],[1,3]]) == [[1,6]] # unsorted input
assert merge_intervals([]) == [] # খালি
assert merge_intervals([[1,4],[0,4]]) == [[0,4]] # একই end, ছোট start
print("সব test pass!")
16. Line-by-line code explanation¶
if not intervals: return []— খালি ইনপুটে আগেভাগে খালি list ফেরত, পরেরintervals[0]যেন crash না করে।intervals.sort(key=lambda iv: iv[0])— start দিয়ে সাজাও; এতেই overlap-রা পাশাপাশি বসে (03 sorting)।merged = [intervals[0][:]]— প্রথম interval-এর একটা copy দিয়ে শুরু ([:]যাতে মূল ইনপুট না বদলায়)।for s, e in intervals[1:]— দ্বিতীয় থেকে একবার scan (02 arrays-এর single pass)।if s <= merged[-1][1]— এখনকার start আগের জমানোটার end-এর ভেতরে হলে overlap।merged[-1][1] = max(...)— overlap হলে শেষ জমানোটার end-কে দূরতমটা দিয়ে বাড়াই (একটা আরেকটাকে গিলতে পারে)।else: merged.append([s, e])— gap থাকলে আলাদা নতুন interval।return merged— সব মিলিয়ে non-overlapping interval-দের list।
17. Output walkthrough¶
[[1,3],[2,6],[8,10],[15,18]]-এ sort করার পর [1,3] দিয়ে শুরু; [2,6]-এ 2 <= 3 তাই end হয় 6 → [1,6]; [8,10]-এ 8 <= 6 মিথ্যা, তাই আলাদা push; [15,18]-ও তাই → [[1,6],[8,10],[15,18]] — assert pass। ছোঁয়া-overlap (4 <= 4), গিলে ফেলা, unsorted, খালি — সব case সঠিক। শেষে print: সব test pass!।
18. Time complexity¶
O(n log n) — মূল খরচ sort-এ; তার পরের sweep মাত্র O(n)।
19. Space complexity¶
O(n) — output merged list সবচেয়ে খারাপ ক্ষেত্রে n-টা interval রাখে (যদি কেউ overlap না করে)। in-place sort ধরলে বাড়তি কাজের memory O(1) (sort-এর নিজস্ব খরচ বাদে)।
20. Common mistakes¶
- sort করতে ভুলে যাওয়া — তাহলে "শুধু আগের জমানোটার সাথে compare" যুক্তিটাই ভেঙে যায়।
- overlap শর্তে
<ব্যবহার করা (s < pe) — তখন ছোঁয়া interval ([1,4]আর[4,5]) জোড়া পড়ে না;<=লাগে যদি ছোঁয়াকেও overlap ধরো। - end বাড়াতে
maxনা নিয়ে সরাসরিeবসানো —[1,10]আর[2,3]-এ end ভুল করে 3 হয়ে যাবে; দূরতমটা রাখতেmaxজরুরি। - মূল ইনপুট list সরাসরি বদলে ফেলা — copy (
[:]) রাখলে side-effect এড়ানো যায়।
21. Which basic problem this inherits from¶
ভিত্তি: comparator দিয়ে sorting (03 sorting, ../../03-searching-and-sorting/) + সাজানো array-র উপর single-pass scan আর পাশাপাশি compare (02 arrays, ../../02-arrays-and-strings/)। এই দুটো cold জানা থাকলে "sort তারপর sweep" নিজে থেকেই আসে।
22. Similar harder problems¶
- Insert Interval (একটা নতুন interval ঢুকিয়ে আবার merge) — https://leetcode.com/problems/insert-interval/
- Non-overlapping Intervals (সবচেয়ে কম interval সরিয়ে overlap মুক্ত করা) — https://leetcode.com/problems/non-overlapping-intervals/
- Meeting Rooms II (heap + intervals, একসাথে সর্বোচ্চ কতগুলো) — https://leetcode.com/problems/meeting-rooms-ii/
- Interval List Intersections (দুই sorted list-এর overlap বের করা) — https://leetcode.com/problems/interval-list-intersections/
23. Pattern learned¶
Sort + sweep on intervals: interval-কে start দিয়ে sort করে এক-পাসে "এখনকার start কি আগের end ছাড়িয়েছে?" দেখে overlap collapse করা। interval-জাতীয় যেকোনো merge/overlap problem-এর canonical সূচনা।
24. Final summary¶
ভবিষ্যতের আমাকে: "interval / range / overlap merge" শুনলেই আগে start দিয়ে sort, তারপর single sweep মনে করবে। চাল — merged[-1] ধরে রাখো, s <= end হলে end = max(...), নাহলে push। <= বনাম < (ছোঁয়া গোনা) আর end-এ max — এই দুটোতেই বেশি ভুল হয়। heap লাগে শুধু "একসাথে কতগুলো active" জানতে চাইলে (Meeting Rooms II)।
মনে রাখার নিয়ম (legal): সব নিজের ভাষায় লেখা; problem statement copy করা হয়নি; official link শুধু attribution-এর জন্য; "asked by Google/Amazon" দাবি নেই — এটা একটা common interview pattern।