006 — Merge Two Sorted Lists¶
Source¶
- Original source: Classic linked-list / recursion exercise
- Platform: LeetCode — https://leetcode.com/problems/merge-two-sorted-lists/
- Topic: recursion (decrease-and-conquer on lists)
- Difficulty: Easy
- Pattern: decrease-and-conquer — ছোট head বেছে বাকিটা recursively merge
- Basic idea inherited from: linked lists section + leap of faith
1. Problem in simple words¶
দুটো sorted (ছোট থেকে বড় সাজানো) singly linked list দেওয়া আছে। তোমার কাজ: ওদের জোড়া লাগিয়ে একটা নতুন sorted list বানানো, যাতে দুই list-এর সব node ঠিক একবার করে থাকে আর সব মিলিয়ে sorted থাকে। নতুন list-এর head return করো।
2. Which basic idea does this inherit from?¶
ভিত্তি হলো linked lists section + leap of faith। দুটো sorted list-এর মধ্যে যেটার head ছোট, সেটাই পুরো merged list-এর head হবে — এটা নিশ্চিত। তারপর সেই head-এর next-এ বসবে "বাকি অংশ আর অন্য list-এর merge"। ওই বাকি merge-টা recursion-কে বিশ্বাস করে দিয়ে দাও (leap of faith); নিজে ভেতরে ঢুকে trace কোরো না।
3. What is the hidden math or logic?¶
লুকানো logic একটা invariant: যেকোনো মুহূর্তে দুই list-এর দুই head-ই নিজ নিজ list-এর সবচেয়ে ছোট অবশিষ্ট element। তাই ওই দুইয়ের মধ্যে ছোটটাই গোটা অবশিষ্ট merge-এর সবচেয়ে ছোট element — সেটাকে সামনে বসিয়ে বাকি problem এক node ছোট করে দাও। প্রতি step-এ ঠিক একটা node স্থায়ীভাবে জায়গা পায়।
4. Which data structure is needed?¶
Linked list নিজেই — নতুন কোনো structure লাগে না। আমরা শুধু বিদ্যমান node-গুলোর next pointer আবার বাঁধি (rewire), নতুন node বানাই না। recursion-এর জন্য call stack লাগে।
5. Why this data structure fits¶
Linked list-এ একটা node-কে অন্যটার সামনে জোড়া দেওয়া মানে শুধু একটা next pointer বদলানো — O(1)। array হলে মাঝখানে ঢোকাতে shift করতে হতো (O(n))। যেহেতু merge মানে বারবার "ছোট head-টা সামনে আনো", linked list-এর সস্তা pointer-rewiring এটাকে নিখুঁতভাবে মানায়, কোনো বাড়তি memory ছাড়াই।
6. Real-life analogy¶
দুই গোছা তাস ভাবো, প্রতি গোছা নিজে নিজে ছোট-থেকে-বড় সাজানো, মুখ নিচু করে রাখা। তুমি প্রতিবার দুই গোছার উপরের তাস দুটো দেখে যেটা ছোট সেটা তুলে নতুন stack-এ রাখো। এক গোছা শেষ হলে, বাকি গোছাটা যেমন আছে তেমন বসিয়ে দাও — কারণ সেটা তো এমনিতেই sorted।
7. Visual explanation¶
প্রতি call ছোট head বেছে বাকিটার merge-কে তার next-এ বসায় — single chain, গভীরতা = মোট node সংখ্যা:
merge(1a->2->4 , 1b->3->4)
1a <= 1b -> 1a.next = merge(2->4 , 1b->3->4)
1b < 2 -> 1b.next = merge(2->4 , 3->4)
2 < 3 -> 2.next = merge(4 , 3->4)
3 < 4 -> 3.next = merge(4 , 4)
4 <= 4 -> 4a.next = merge(None , 4)
list1 None -> return list2 (4) [base case]
ফলাফল গাঁথা: 1a -> 1b -> 2 -> 3 -> 4a -> 4
8. Example input and output¶
Input : 1->2->4 , 1->3->4 -> Output: 1->1->2->3->4->4
Input : 5 , 1->2->3 -> Output: 1->2->3->5
Edge case 1 (দুটোই খালি): None , None -> Output: None
Edge case 2 (একটা খালি): None , 0->1 -> Output: 0->1
9. Brute force thinking¶
প্রথম সরল চিন্তা: দুই list-এর সব value একটা array-তে ঢালো, array-টা sort করো, তারপর সেই array থেকে নতুন একটা linked list বানাও।
10. Why brute force is slow¶
কাজ করে, কিন্তু দুটো অপচয়: (a) array sort করতে O(m+n log(m+n)), অথচ input তো আগেই sorted — এই তথ্য নষ্ট হলো; (b) O(m+n) extra space (array + নতুন node)। অথচ দুই head তুলনা করেই linear সময়ে, পুরোনো node গুলো rewire করে, কম জায়গায় merge করা যায়।
11. Key observation¶
মূল observation: দুই sorted head-এর মধ্যে ছোটটাই merged list-এর পরের element — এর চেয়ে ছোট আর কিছু কোনো list-এ নেই। তাই পুরো list একসাথে না ভেবে, শুধু "এই মুহূর্তে কোন head ছোট" ঠিক করো, সেটা বসাও, বাকিটা recursion-কে দাও।
12. Optimized intuition¶
Base case: কোনো list None হলে অন্যটা যেমন আছে তেমন ফেরত দাও (sorted তো বটেই)। নাহলে দুই head তুলনা করো; ছোট head-টা রাখো, তার next-এ বসাও "ওই head বাদ দিয়ে বাকি অংশ আর অন্য list-এর merge"। সেই ছোট head-ই এই level-এর উত্তর। leap of faith: ভেতরের merge ঠিক করবে — তুমি শুধু এই এক জোড়া তুলনা সামলাও।
13. Thinking tweak¶
মোচড়: "দুটো পুরো list কীভাবে মেশাব?" ভাবার বদলে জিজ্ঞেস করো "merged list-এর সবচেয়ে প্রথম node-টা কে?" — অবশ্যই দুই head-এর ছোটটা। ওটা বসিয়ে দিলে বাকি কাজটা ঠিক একই ধরনের, এক node ছোট problem; recursion-কে বিশ্বাস করো।
14. Step-by-step dry run¶
merge(1->3, 2->4):
1 <= 2→ head = node1;1.next = merge(3, 2->4)।3 > 2→ head = node2;2.next = merge(3, 4)।3 <= 4→ head = node3;3.next = merge(None, 4)।- list1
None→ base case, return4। - unwind করে গাঁথা হয়:
1 -> 2 -> 3 -> 4।
15. Python solution¶
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def merge_two_lists(l1, l2):
if l1 is None: # base case: এক list শেষ
return l2
if l2 is None:
return l1
if l1.val <= l2.val: # ছোট head বেছে নাও
l1.next = merge_two_lists(l1.next, l2)
return l1
else:
l2.next = merge_two_lists(l1, l2.next)
return l2
# ---- helpers (test-এর জন্য) ----
def build(values):
dummy = ListNode()
tail = dummy
for v in values:
tail.next = ListNode(v)
tail = tail.next
return dummy.next
def to_list(head):
out = []
while head:
out.append(head.val)
head = head.next
return out
# ---- tests ----
assert to_list(merge_two_lists(build([1, 2, 4]), build([1, 3, 4]))) == [1, 1, 2, 3, 4, 4]
assert to_list(merge_two_lists(build([5]), build([1, 2, 3]))) == [1, 2, 3, 5]
assert to_list(merge_two_lists(build([]), build([]))) == [] # দুটোই খালি
assert to_list(merge_two_lists(build([]), build([0, 1]))) == [0, 1] # একটা খালি
assert to_list(merge_two_lists(build([1, 1]), build([1, 1]))) == [1, 1, 1, 1] # সব সমান
print("সব test pass!")
16. Line-by-line code explanation¶
if l1 is None: return l2/if l2 is None: return l1— base case: এক list ফুরালে বাকিটা পুরোটা জুড়ে দাও।if l1.val <= l2.val:— দুই head তুলনা;<=রাখায় সমান হলে stable থাকে।l1.next = merge_two_lists(l1.next, l2)—l1-এর head রেখে তার পরের অংশ ওl2-কে merge করে পেছনে জোড়া।return l1/return l2— যে head ছোট, সেটাই এই level-এর merged head।
17. Output walkthrough¶
merge_two_lists(build([1,2,4]), build([1,3,4])): প্রতি ধাপে ছোট head বসে — 1,1,2,3,4,4 গাঁথা হয়, to_list দেয় [1,1,2,3,4,4]। [5] ও [1,2,3]-এ 5 শেষে বসে। দুটো খালি → None → []। এক খালি → অন্যটা সরাসরি। সব-সমান case-ও ঠিক, তারপর print: সব test pass!।
18. Time complexity¶
O(m + n) — প্রতিটা node ঠিক একবার তুলনা ও rewire হয়; m, n দুই list-এর দৈর্ঘ্য।
19. Space complexity¶
O(m + n) recursion stack-এর জন্য (প্রতি node-এ এক level গভীর)। Iterative version-এ O(1) — নতুন node বানে না, শুধু pointer rewire।
20. Common mistakes¶
- base case (এক list
None) না রাখা —None.val-এ crash। <ব্যবহার করে সমান value-তে stability নষ্ট করা;<=নিরাপদ।- recursion-এর ফল
next-এ assign করতে ভুলে যাওয়া — তখন বাকি অংশ হারিয়ে যায়। - নতুন node বানিয়ে memory নষ্ট করা — পুরোনো node rewire করলেই হয়।
- Python-এ খুব লম্বা list-এ depth limit — তখন iterative দুই-pointer version বেছে নাও।
21. Which basic problem this inherits from¶
ভিত্তি: linked-list traversal (../../03-linked-list/) + leap of faith (../concept.md)। এটাই Merge Sort-এর combine step; ../patterns.md-এর Pattern 1 (decrease-and-conquer) দেখো। আটকে গেলে আগে Reverse Linked List (../../03-linked-list/problems/001-reverse-linked-list.md) ঝালিয়ে নাও।
22. Similar harder problems¶
- Merge k Sorted Lists — https://leetcode.com/problems/merge-k-sorted-lists/
- Sort List (merge sort on a list) — https://leetcode.com/problems/sort-list/
- Add Two Numbers — https://leetcode.com/problems/add-two-numbers/
23. Pattern learned¶
Decrease-and-conquer on lists: দুই head তুলনা করে ছোটটা সামনে বসাও, বাকিটার merge তার next-এ recursively গাঁথো — merge sort ও যেকোনো "দুই sorted ধারা মেশানো" কাজের মূল pattern।
24. Final summary¶
ভবিষ্যতের আমাকে: "Merge = ছোট head বেছে নাও, তার next-এ বাকিটার merge বসাও; এক list শেষ হলে অন্যটা জুড়ে দাও।" যখনই দুটো (বা k-টা) sorted ধারা একত্র করতে হবে — এই two-head compare + leap-of-faith recursion মনে করবে।
মনে রাখার নিয়ম (legal): সব নিজের ভাষায় লেখা; problem statement copy করা হয়নি; official link শুধু attribution-এর জন্য; "asked by Google/Amazon" দাবি নেই — এটা একটা common interview pattern।