004 — Middle of the Linked List¶
Source¶
- Original source: Classic linked list exercise
- Platform: LeetCode — https://leetcode.com/problems/middle-of-the-linked-list/
- Topic: linked list
- Difficulty: Easy
- Pattern: slow/fast pointers
- Basic idea inherited from: cycle-check-এর skeleton (problem 3-এর slow/fast)
1. Problem in simple words¶
একটা singly linked list দেওয়া। তোমাকে এর মাঝের node-টা বের করতে হবে আর সেখান থেকে বাকি list return করতে হবে। list-এর দৈর্ঘ্য জোড় হলে দুটো মাঝ থাকে — তখন দ্বিতীয়টা নাও।
বিজোড় (5টা): 1 -> 2 -> 3 -> 4 -> 5 মাঝ = 3
জোড় (6টা): 1 -> 2 -> 3 -> 4 -> 5 -> 6 মাঝ = 4 (দ্বিতীয়টা)
2. Which basic idea does this inherit from?¶
এটা সরাসরি problem 3 (003-linked-list-cycle.md)-এর slow/fast skeleton-এর উপর দাঁড়িয়ে। সেখানে আমরা শিখেছি দুটো pointer ভিন্ন গতিতে চালানো। এখানে cycle ধরা নয় — বরং সেই একই দুই-গতির খেলা দিয়ে এক পাসে মাঝ খুঁজে ফেলা। skeleton একই, শুধু লক্ষ্য আলাদা।
3. What is the hidden math or logic?¶
লুকানো অঙ্ক একটা সরল অনুপাত: fast যখন slow-এর দ্বিগুণ গতিতে এগোয়, তখন fast যখন শেষ ছুঁয়েছে, slow ঠিক অর্ধেক পথ গেছে। অর্ধেক পথ মানেই মাঝ। গণিতে — fast ২k ঘর গেলে slow k ঘর; fast শেষে (২k = n) মানে slow = n/2 = মাঝ।
4. Which data structure is needed?¶
কোনো extra data structure লাগে না — শুধু দুটো pointer (slow, fast)। O(1) extra space-এই কাজ হয়ে যায়।
5. Why this data structure fits¶
Linked list-এ আমরা index করে সরাসরি মাঝে লাফ দিতে পারি না (length আগে থেকে জানা নেই)। কিন্তু একই list-এ দুই গতিতে হাঁটা যায়। দুই pointer-এর গতির অনুপাত নিজেই length মেপে দেয় — আলাদা করে আগে length গুনতে হয় না, এক পাসেই মাঝ পাওয়া যায়।
6. Real-life analogy¶
দুই বন্ধু একসাথে একটা লম্বা করিডোরের শুরু থেকে হাঁটা শুরু করল। একজন স্বাভাবিক গতিতে, আরেকজন ঠিক দ্বিগুণ জোরে। দ্রুতজন যখন করিডোরের শেষ দেয়ালে পৌঁছবে, ধীরজন তখন ঠিক মাঝখানে দাঁড়িয়ে থাকবে — মাপজোক ছাড়াই।
7. Visual explanation¶
slow ১ ঘর, fast ২ ঘর। 1->2->3->4->5 (বিজোড়):
শুরু: slow=1, fast=1
step 1: slow=2, fast=3
step 2: slow=3, fast=5
fast.next = None → থামো
slow = 3 ← মাঝ ✓
1->2->3->4->5->6 (জোড়):
step 1: slow=2, fast=3
step 2: slow=3, fast=5
step 3: slow=4, fast=None (5.next.next = None)
fast = None → থামো
slow = 4 ← দ্বিতীয় মাঝ ✓
8. Example input and output¶
Input : 1 -> 2 -> 3 -> 4 -> 5 -> Output: 3 -> 4 -> 5 (head = node 3)
Input : 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> Output: 4 -> 5 -> 6 (দ্বিতীয় মাঝ)
Edge 1 (একটা node): 1 -> Output: 1
Edge 2 (দুটো node): 1 -> 2 -> Output: 2 (দ্বিতীয়টা)
9. Brute force thinking¶
সরল চিন্তা: আগে পুরো list একবার ঘুরে length n গুনে নাও। তারপর আবার head থেকে শুরু করে n // 2 ঘর এগিয়ে গিয়ে সেই node return করো।
10. Why brute force is slow¶
এটা ঠিক উত্তর দেয়, কিন্তু list-এর উপর দুবার হাঁটে (একবার গুনতে, একবার পৌঁছাতে) — মোট ~2n কাজ। slow/fast দিয়ে একবারেই হয়ে যায়, কারণ গতির অনুপাত নিজেই গুনে রাখে। বড় list বা streaming data-তে এক-পাস সমাধানই কাম্য।
11. Key observation¶
মূল observation: মাঝ খুঁজতে length জানার দরকার নেই। দুটো pointer-এর গতি ১:২ রাখলেই দ্রুতজন শেষে পৌঁছানোর মুহূর্তে ধীরজন আপনাআপনি মাঝে থাকে। গতির অনুপাতই length-এর কাজ করে দেয়।
12. Optimized intuition¶
slow আর fast দুটোকেই head থেকে ছাড়ো। প্রতি step-এ slow ১ ঘর, fast ২ ঘর। while fast and fast.next: শর্ত রাখো — মানে fast নিরাপদে দুই ঘর লাফাতে পারবে কিনা। loop শেষ হলে slow-ই মাঝ। জোড় দৈর্ঘ্যে এই শর্ত স্বয়ংক্রিয়ভাবে দ্বিতীয় মাঝ দেয়।
13. Thinking tweak¶
মোচড়: "length গুনে অর্ধেকে যাব" না ভেবে ভাবো — "একজনকে দ্বিগুণ জোরে ছেড়ে দিই; ও শেষে গেলে অন্যজন এমনিতেই মাঝে।" গোনা বাদ, গতি দিয়ে কাজ — এক পাসেই শেষ।
14. Step-by-step dry run¶
Input 1 -> 2 -> 3 -> 4 (জোড়, ৪টা):
- শুরু:
slow=1,fast=1 fastওfast.nextআছে →slow=2,fast=3fastআছে,fast.nextআছে →slow=3,fast=None(3.next.next = None)fast=None→ loop শেষ- return
slow=3→ অর্থাৎ3 -> 4(দুই মাঝ 2,3-এর দ্বিতীয়টা)
15. Python solution¶
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def middle_node(head):
slow = fast = head
while fast and fast.next: # fast দুই ঘর লাফাবে, তাই দুটোই থাকা চাই
slow = slow.next # ১ ঘর
fast = fast.next.next # ২ ঘর
return slow # fast শেষে → slow ঠিক মাঝে
# ---- 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(middle_node(build([1, 2, 3, 4, 5]))) == [3, 4, 5] # বিজোড় → আসল মাঝ
assert to_list(middle_node(build([1, 2, 3, 4, 5, 6]))) == [4, 5, 6] # জোড় → দ্বিতীয় মাঝ
assert to_list(middle_node(build([1]))) == [1] # একটা node
assert to_list(middle_node(build([1, 2]))) == [2] # জোড় → দ্বিতীয়টা
print("সব test pass!")
16. Line-by-line code explanation¶
slow = fast = head— দুজনেই head থেকে শুরু।while fast and fast.next:—fastদুই ঘর লাফায়, তাইfastওfast.nextদুটোই থাকতে হবে; নাহলে শেষ ছুঁয়েছে।slow = slow.next— ধীরজন ১ ঘর।fast = fast.next.next— দ্রুতজন ২ ঘর।return slow— loop শেষেfastশেষপ্রান্তে, তাইslowমাঝে। জোড় দৈর্ঘ্যে শর্তের গঠনই দ্বিতীয় মাঝ দেয়।
17. Output walkthrough¶
build([1,2,3,4,5]) বানায় 1->2->3->4->5; middle_node ফেরায় node 3, তাই to_list দেয় [3,4,5]। ৬-নোডের list-এ ফেরায় node 4 → [4,5,6]। একক node → [1]; দুই-নোডে দ্বিতীয়টা → [2]। সব assert pass → সব test pass!।
18. Time complexity¶
O(n) — fast n/2 ধাপে শেষে পৌঁছায়, তাই মোট রৈখিক; list-এর উপর একবারই হাঁটা।
19. Space complexity¶
O(1) — শুধু দুটো pointer; list-এর সাইজের সাথে extra memory বাড়ে না।
20. Common mistakes¶
whileশর্তে শুধুfastচেক করা,fast.nextনা — তখনfast.next.next-এ crash।- জোড় দৈর্ঘ্যে প্রথম মাঝ চেয়ে শর্ত গুলিয়ে ফেলা; দ্বিতীয় মাঝ চাইলে
while fast and fast.next:রাখতেই হবে। - length আগে গুনে দুবার হাঁটা — কাজ করে, কিন্তু এক-পাস সুবিধা হারায়।
21. Which basic problem this inherits from¶
ভিত্তি: problem 3-এর slow/fast skeleton (003-linked-list-cycle.md) আর সাধারণ traversal (../operations.md)। দুই-speed চিন্তার ছবি ../visual-explanation.md-এর "tortoise–hare" frame-এ।
22. Similar harder problems¶
- Linked List Cycle (একই skeleton, ভিন্ন লক্ষ্য) — https://leetcode.com/problems/linked-list-cycle/ (এই tracker-এর #3)
- Remove Nth Node From End of List (runner gap) — https://leetcode.com/problems/remove-nth-node-from-end-of-list/ (#9)
- Palindrome Linked List (মাঝ বের করে অর্ধেক উল্টানো) — https://leetcode.com/problems/palindrome-linked-list/ (#14)
23. Pattern learned¶
Slow/fast pointers (middle finding): এক pointer ১ ঘর, আরেকটা ২ ঘর — দ্রুতজন শেষে গেলে ধীরজন মাঝে। length না গুনে এক পাসে মাঝ পাওয়ার classic কৌশল।
24. Final summary¶
ভবিষ্যতের আমাকে: "মাঝ লাগবে? length গুনো না — slow ১ ঘর, fast ২ ঘর ছাড়ো; fast শেষে গেলে slow মাঝে।" জোড়ে দ্বিতীয় মাঝ চাইলে while fast and fast.next:। এই skeleton split-list, palindrome, reorder — সবেতেই ফিরে আসবে।
25. JavaScript solution (with test cases)¶
const assert = (cond, msg = "") => { if (!cond) throw new Error("FAIL " + msg); };
class ListNode {
constructor(val = 0, next = null) { this.val = val; this.next = next; }
}
const arrayToList = (arr) => {
const dummy = new ListNode();
let tail = dummy;
for (const v of arr) { tail.next = new ListNode(v); tail = tail.next; }
return dummy.next;
};
const listToArray = (head) => {
const out = [];
while (head) { out.push(head.val); head = head.next; }
return out;
};
function middleNode(head) {
let slow = head, fast = head;
while (fast && fast.next) { // fast দুই ঘর লাফাবে, তাই দুটোই থাকা চাই
slow = slow.next; // ১ ঘর
fast = fast.next.next; // ২ ঘর
}
return slow; // fast শেষে -> slow ঠিক মাঝে
}
// ---- test cases ----
const eq = (a, b) => JSON.stringify(a) === JSON.stringify(b);
assert(eq(listToArray(middleNode(arrayToList([1, 2, 3, 4, 5]))), [3, 4, 5]), "বিজোড় -> আসল মাঝ");
assert(eq(listToArray(middleNode(arrayToList([1, 2, 3, 4, 5, 6]))), [4, 5, 6]), "জোড় -> দ্বিতীয় মাঝ");
assert(eq(listToArray(middleNode(arrayToList([1]))), [1]), "একটা node");
assert(eq(listToArray(middleNode(arrayToList([1, 2]))), [2]), "জোড় -> দ্বিতীয়টা");
console.log("সব JS test pass!");
JS টীকা:
while (fast && fast.next)শর্তটাই জোড়-দৈর্ঘ্যে দ্বিতীয় মাঝ দেয় — শর্ত বদলে শুধুwhile (fast.next && fast.next.next)করলে প্রথম মাঝ আসত। length আগে গুনে দুবার হাঁটার দরকার নেই; গতির অনুপাত ১:২ নিজেই length মেপে দেয়, তাই এক পাসেই মাঝ।arrayToList/listToArrayhelper দিয়ে snippetnode-এ নিজে নিজে রান করে।
26. TypeScript solution (with test cases)¶
interface ListNode { val: number; next: ListNode | null }
const arrayToList = (arr: number[]): ListNode | null => {
let head: ListNode | null = null;
for (let i = arr.length - 1; i >= 0; i--) head = { val: arr[i], next: head };
return head;
};
const listToArray = (head: ListNode | null): number[] => {
const out: number[] = [];
while (head) { out.push(head.val); head = head.next; }
return out;
};
function middleNode(head: ListNode | null): ListNode | null {
let slow: ListNode | null = head;
let fast: ListNode | null = head;
while (fast && fast.next) {
slow = slow!.next; // fast থাকলে slow-ও থাকে
fast = fast.next.next;
}
return slow;
}
function expect<T>(a: T, e: T, m = ""): void { if (a !== e) throw new Error(`FAIL ${m}`); }
const S = JSON.stringify;
expect(S(listToArray(middleNode(arrayToList([1, 2, 3, 4, 5])))), S([3, 4, 5]), "odd");
expect(S(listToArray(middleNode(arrayToList([1, 2, 3, 4, 5, 6])))), S([4, 5, 6]), "even");
expect(S(listToArray(middleNode(arrayToList([1])))), S([1]), "single");
expect(S(listToArray(middleNode(arrayToList([1, 2])))), S([2]), "two");
console.log("সব TS test pass!");
TS টীকা:
ListNode | nullunion-এwhile (fast && fast.next)দুই ধাপ narrow করে — তাই block-এর ভেতরেfast.next.nextনিরাপদ।slow!.next-এ!দিই কারণ যুক্তিগতভাবে slow fast-এর পিছনে, কিন্তু কম্পাইলার তা স্বাধীনভাবে প্রমাণ করতে পারে না। return typeListNode | nullসততার সাথে বলে দেয় খালি list-এnullফিরতে পারে।
27. কোথায় কাজে লাগে (real-world use)¶
- List splitting: merge sort বা reorder-list-এ list-কে দুই অর্ধে ভাগ করতে মাঝ লাগে — এই slow/fast দিয়ে এক পাসে।
- Streaming median-ish: একবারে length না জেনেও মাঝ-উপাদান বের করা (যেখানে rewind কঠিন)।
- Pagination/midpoint jump: doubly বা singly chain-এ "মাঝখানে যাও" দরকার হলে।
- Palindrome / symmetry check: মাঝ বের করে দ্বিতীয় অর্ধেক উল্টে মেলানোর প্রথম ধাপ।
- Load balancing on a chain: একটা linked pipeline-কে দুই সমান অংশে ভাগ করতে।
এক কথায়, "length না গুনে এক পাসে মাঝ চাই" — singly chain বা stream-এ — এই slow/fast runner-ই আদর্শ; আর এটা split/palindrome-জাতীয় বহু combo problem-এর প্রথম ইট।
মনে রাখার নিয়ম (legal): সব নিজের ভাষায় লেখা; problem statement copy করা হয়নি; official link শুধু attribution-এর জন্য; "asked by Google/Amazon" দাবি নেই — এটা একটা common interview pattern।