Skip to content

017 — Sort List

Source

  • Original source: Classic linked list exercise
  • Platform: LeetCode — https://leetcode.com/problems/sort-list/
  • Topic: linked list
  • Difficulty: Medium
  • Pattern: merge sort on chains
  • Basic idea inherited from: middle দিয়ে split (problem 4) + merge two (problem 2), recursively

1. Problem in simple words

একটা linked list দেওয়া, যার value গুলো এলোমেলো। তোমাকে এটাকে ছোট-থেকে-বড় (ascending) ক্রমে sort করতে হবে আর sorted list-এর head return করতে হবে। লক্ষ্য: O(n log n) সময়ে — মানে সহজ O(n²) sort চলবে না।

আগে : 4 -> 2 -> 1 -> 3
পরে : 1 -> 2 -> 3 -> 4

2. Which basic idea does this inherit from?

এটা দুটো পুরোনো skeleton recursively জোড়া — খাঁটি merge sort, কিন্তু array নয়, linked list-এ:

  • Middle দিয়ে split (problem 4, 004-middle-of-list.md) — list-টা মাঝখান থেকে দুই ভাগ করা।
  • Merge two sorted lists (problem 2, 002-merge-two-sorted-lists.md) — দুই sorted অর্ধ জোড়া দেওয়া।

দুটোকে recursion-এ মুড়ে দিলেই পুরো sort।

3. What is the hidden math or logic?

লুকানো logic হলো divide and conquer-এর সেই recurrence: T(n) = 2·T(n/2) + O(n)। প্রতি level-এ পুরো n node merge হয় (O(n)), আর প্রতিবার অর্ধেক করে ভাঙায় level-এর সংখ্যা log n। তাই মোট O(n log n)। merge sort তাই array-র মতো linked list-এও সমান দক্ষ — বরং linked list-এ merge step-এ extra space লাগে না।

4. Which data structure is needed?

কোনো নতুন container নয় — list নিজেই যথেষ্ট। লাগে slow/fast pointer (split-এর জন্য), dummy head (merge-এর জন্য), আর recursion-এর জন্য call stack। তাই extra space মূলত recursion depth = O(log n)।

5. Why this data structure fits

Merge sort-এর জন্য linked list আদর্শ: array merge-এ দুই অর্ধ জোড়াতে O(n) extra array লাগে, কিন্তু linked list-এ merge শুধু arrow ঘোরানো — O(1) extra (recursion stack বাদে)। আর random access না থাকায় quicksort-এর pivot ভাগ ঝামেলার; তাই linked list-এ merge sort-ই স্বাভাবিক পছন্দ।

6. Real-life analogy

একগাদা পরীক্ষার খাতা sort করছ। একা পুরোটা সামলানো কঠিন, তাই তাড়াটা অর্ধেক অর্ধেক করে ভাগ করে দুজনকে দিলে; তারা আবার ভাগ করল — যতক্ষণ না হাতে এক-একটা খাতা থাকে (এমনিতেই sorted)। তারপর দুজন দুজন মিলে দুটো sorted তাড়া এক করে উপরে উঠতে থাকল — শেষে পুরোটা sorted।

7. Visual explanation

4 -> 2 -> 1 -> 3 দিয়ে split-merge গাছ:

              [4, 2, 1, 3]
            split (middle)
          /                \
     [4, 2]                [1, 3]
    /      \              /      \
  [4]      [2]          [1]      [3]
    \      /              \      /
   merge → [2, 4]        merge → [1, 3]
            \              /
             merge → [1, 2, 3, 4]

8. Example input and output

Input : 4 -> 2 -> 1 -> 3          -> Output: 1 -> 2 -> 3 -> 4
Input : -1 -> 5 -> 3 -> 4 -> 0    -> Output: -1 -> 0 -> 3 -> 4 -> 5

Edge 1 (খালি list):  None         -> Output: None
Edge 2 (একটা node):  1            -> Output: 1
Edge 3 (duplicate):  3 -> 3 -> 1  -> Output: 1 -> 3 -> 3

9. Brute force thinking

সরল চিন্তা: সব value একটা array-তে তুলে নাও, array-টা sort করো (sorted()), তারপর sorted value দিয়ে নতুন list বানাও বা পুরোনো node-এ value বসাও।

4->2->1->3 -> [4,2,1,3] -> sort -> [1,2,3,4] -> 1->2->3->4

10. Why brute force is slow

আসলে এটা O(n log n) time দেয় (Python-এর sort), কিন্তু O(n) extra space নেয় (array)। আর এটা linked list-এর কাঠামোকে কাজে লাগায় না — node-এর arrow ঘুরিয়ে in-place merge করলে extra array লাগত না। Interview-তে চাওয়া হয় list-এর উপরেই merge sort, যাতে extra space কমে (শুধু recursion stack)।

11. Key observation

মূল observation: একটা একক node সবসময় sorted। তাই list-কে বারবার অর্ধেক করে ভাঙতে থাকো যতক্ষণ না প্রতিটা টুকরো এক-node হয় (তখন এমনিতেই sorted), তারপর জোড়ায় জোড়ায় merge করে উপরে ওঠো — এটাই merge sort-এর প্রাণ।

12. Optimized intuition

Recursion-এ ভাবো: base case — list খালি বা এক-node হলে already sorted, ফেরাও। নাহলে slow/fast দিয়ে middle খুঁজে দুই ভাগে কাটো, দুই ভাগকে recursively sort করো, তারপর problem 2-এর merge দিয়ে দুই sorted অর্ধ জোড়ো। উপরে উঠতে উঠতে পুরো list sorted।

13. Thinking tweak

মোচড়: "পুরো list কীভাবে sort করব" না ভেবে ভাবো — "অর্ধেক করে কেটে দুজনকে দিয়ে দিই; ওরা sorted করে আনলে আমি শুধু দুটো sorted জিনিস merge করব।" বড় কাজটা নিজেকে সমর্পণ করে দাও recursion-এর হাতে; তোমার দায়িত্ব শুধু split আর merge।

14. Step-by-step dry run

Input 4 -> 2 -> 1 -> 3:

  1. split (slow=head, fast=head.next): slow node 2-এ থামে → left = 4 -> 2, right = 1 -> 3
  2. sort(4 -> 2): আবার split → [4], [2]; merge → 2 -> 4
  3. sort(1 -> 3): split → [1], [3]; merge → 1 -> 3
  4. উপরে merge: merge(2 -> 4, 1 -> 3) → দুই মাথা তুলনা করে 1 -> 2 -> 3 -> 4
  5. ফল: 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(l1, l2):
    dummy = ListNode()      # dummy head — head-এর special case এড়াতে
    tail = dummy
    while l1 and l2:
        if l1.val <= l2.val:
            tail.next = l1
            l1 = l1.next
        else:
            tail.next = l2
            l2 = l2.next
        tail = tail.next
    tail.next = l1 if l1 else l2   # যেটা বাকি, পুরো জুড়ে দাও
    return dummy.next

def sort_list(head):
    if not head or not head.next:
        return head         # খালি বা এক-node → already sorted

    # split: middle খোঁজো (fast = head.next দিয়ে দুই-node case-ও এগোয়)
    slow = head
    fast = head.next
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
    mid = slow.next         # দ্বিতীয় অর্ধের শুরু
    slow.next = None        # দুই অর্ধ আলাদা করো

    left = sort_list(head)  # দুই অর্ধ recursively sort
    right = sort_list(mid)
    return merge_two(left, right)

# ---- 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(sort_list(build([4, 2, 1, 3]))) == [1, 2, 3, 4]
assert to_list(sort_list(build([-1, 5, 3, 4, 0]))) == [-1, 0, 3, 4, 5]
assert to_list(sort_list(build([]))) == []
assert to_list(sort_list(build([1]))) == [1]
assert to_list(sort_list(build([2, 1]))) == [1, 2]
assert to_list(sort_list(build([3, 3, 1, 1, 2]))) == [1, 1, 2, 3, 3]
print("সব test pass!")

16. Line-by-line code explanation

  • if not head or not head.next: — base case: খালি বা এক-node already sorted।
  • slow = head; fast = head.next — split-এ এখানে fast এক ঘর এগিয়ে শুরু; এতে দুই-node list-এ slow প্রথম node-এ থামে, যাতে দুই অর্ধই non-empty হয় (নাহলে infinite recursion)।
  • while fast and fast.next: — slow ঠিক first-half-এর শেষে থামবে।
  • mid = slow.next; slow.next = None — list দুই ভাগ।
  • left = sort_list(head); right = sort_list(mid) — recursively দুই অর্ধ sort।
  • merge_two(left, right) — problem 2-এর merge: দুই sorted অর্ধ এক করো।

17. Output walkthrough

build([4,2,1,3])4->2->1->3। split → 4->21->3; recursively sort → 2->41->3; merge → 1->2->3->4[1,2,3,4] — assert pass। নেগেটিভ, খালি, এক-node, দুই-node, duplicate — সব pass হয়ে শেষে print: সব test pass!

18. Time complexity

O(n log n) — প্রতি level-এ সব মিলিয়ে O(n) merge কাজ, আর অর্ধেক করে ভাঙায় log n level; গুণ করলে n log n।

19. Space complexity

O(log n) — merge নিজে O(1) (arrow ঘোরায়), কিন্তু recursion stack-এর গভীরতা log n। (পুরো iterative bottom-up লিখলে O(1)-এ নামানো যায়।)

20. Common mistakes

  • split-এ fast = head রাখা (head.next-এর বদলে) — দুই-node list-এ এক অর্ধ খালি থেকে যায় → infinite recursion / stack overflow।
  • slow.next = None দিয়ে দুই অর্ধ আলাদা করতে ভুলে যাওয়া — তখন merge-এ overlap, ভুল ফল বা loop।
  • base case (খালি/এক-node) না দেওয়া — recursion কখনো থামে না।
  • merge-এ < ব্যবহার করা <=-এর বদলে — duplicate-এ stable order হারাতে পারে।

21. Which basic problem this inherits from

ভিত্তি দুটো: middle দিয়ে split (004-middle-of-list.md) আর merge two sorted lists (002-merge-two-sorted-lists.md)। আটকে গেলে আগে এই দুটো আলাদা করে ঝালিয়ে নাও — sort তাদেরই recursion-এ মোড়ানো রূপ।

22. Similar harder problems

23. Pattern learned

Merge sort on a linked list: middle দিয়ে split + sorted merge, recursively — O(n log n) sort যেখানে merge step O(1) extra space-এ হয়।

24. Final summary

ভবিষ্যতের আমাকে: "list O(n log n)-এ sort? merge sort — middle দিয়ে কাটো, দুই অর্ধ recursively sort করো, তারপর merge।" এক-node সবসময় sorted — সেটাই recursion-এর তলদেশ।


মনে রাখার নিয়ম (legal): সব নিজের ভাষায় লেখা; problem statement copy করা হয়নি; official link শুধু attribution-এর জন্য; "asked by Google/Amazon" দাবি নেই — এটা একটা common interview pattern।