Linked List — delivery manifest ও crate loading¶
দুই warehouse থেকে batch ID অনুযায়ী সাজানো parcel list এসেছে। বাতিল batch-এর parcel বাদ দিতে হবে। তারপর পূর্ণ crate-এ একসঙ্গে kটি parcel উল্টো ক্রমে load হবে; শেষের অসম্পূর্ণ crate আগের ক্রমেই থাকবে। Array বানিয়ে sort/reverse না করে একই node-এর link বদলে final manifest তৈরি করো।
কোর্স-সংযোগ: Microsoft Module 1-এর linked list, traversal ও operation cost-এর সম্পর্কিত অনুশীলন। এটি সাইটের নিজস্ব guided problem।
১. আগে যা পড়বে¶
| ক্রম | নোট | বোঝার লক্ষ্য |
|---|---|---|
| 1 | Linked list ধারণা, pointer-এর চিত্র | node-এর identity ও value কেন এক নয় |
| 2 | Cycle detection | fast/slow pointer কখন মিলবে |
| 3 | Merge Two Sorted Lists | dummy head দিয়ে প্রথম node-এর বিশেষ case সরানো |
| 4 | Remove Elements | বাদ দেওয়ার সময় previous কেন একই থাকে |
| 5 | Reverse Linked List, Reverse k-Group | next হারানোর আগেই বাঁচিয়ে রাখা |
২. সমস্যার চুক্তি¶
build_manifest(left, right, cancelled, group_size) একটি Node head ফেরাবে। প্রতিটি node-এ integer value ও next থাকবে। value হলো batch ID; একই batch-এর একাধিক parcel থাকতে পারে। cancelled-এ ID থাকলে সেই ID-এর সব parcel বাদ যাবে।
- দুই list nondecreasing order-এ থাকবে, cycle থাকবে না, এবং তারা কোনো node share করবে না। মোট
0 ≤ n ≤ 100000। group_size = k ≥ 1integer। অবশিষ্ট node-এর মধ্যে প্রতি পূর্ণk-group reverse হবে; ছোট শেষ group অপরিবর্তিত।- সমান ID-এর merge-এ left list-এর node আগে আসে। কোনো নতুন parcel node তৈরি করবে না; temporary dummy node ব্যবহার করা যাবে।
- Input consume হবে: original head ধরে আগের list আবার ব্যবহার করবে না। Node-এর link বদলানোই কাজটির অংশ।
- cycle, shared tail, unsorted input বা invalid k পেলে link বদলানোর আগেই
ValueError। খালি list বৈধ।
উদাহরণ¶
left = from_values([1, 4, 7])
right = from_values([2, 3, 6, 8])
result = build_manifest(left, right, cancelled={3, 7}, group_size=2)
assert to_values(result) == [2, 1, 6, 4, 8]
দুই sorted manifest merge
1→2→চিহ্নিত ঘর: 3→4→6→চিহ্নিত ঘর: 7→8Batch 3 ও 7 বাতিল।
বাতিলের node bypass
1→2→4→6→8দুটি পূর্ণ crate: [1,2] ও [4,6]; শেষ [8] অসম্পূর্ণ।
প্রতি পূর্ণ crate উল্টো
চিহ্নিত ঘর: 2→চিহ্নিত ঘর: 1→চিহ্নিত ঘর: 6→চিহ্নিত ঘর: 4→8শেষ 8-এর অবস্থান অপরিবর্তিত; value কপি নয়, link বদলেছে।
৩. চারটি guided milestone¶
ধাপ A — নিরাপদে list পড়া¶
প্রথমে assert_acyclic(head) লিখো। slow এক ধাপ, fast দুই ধাপ। identity দিয়ে মিলবে: slow is fast; একই value থাকলেই cycle নয়। এরপর sorted order যাচাই করে প্রতিটি list-এর শেষ node মনে রাখো।
কেন শেষ node যথেষ্ট: দুটি acyclic singly linked list কোনো node share করলে সেই node-এর পরের পুরো পথই shared; তাই তাদের tail একই হবে। দুই nonempty list-এর tail একই হলে reject করো।
Checkpoint: self-loop reject হবে; 1 → shared5 এবং 2 → shared5-ও reject হবে। একই value-সহ আলাদা node বৈধ। এই ধাপের আগে কোনো next বদলাবে না।
ধাপ B — existing node merge¶
merge_sorted(left, right)-এ dummy ও tail রাখো। ছোট head-কে tail.next করো, সেই input head এগিয়ে নাও, তারপর tail এগোবে।
Invariant: dummy-র পরের অংশ sorted এবং tail তার শেষ node। Hint: তুলনায় <= দিলে equal-ID parcel-এর স্থির ক্রম থাকে। শেষের অবশিষ্ট chain একবার link করলেই হবে।
Checkpoint: দুই input-এর সব node identity output-এ আছে; sample-এর merge হলো [1,2,3,4,6,7,8]।
ধাপ C — cancelled parcel bypass¶
remove_cancelled(head, cancelled)-এ dummy-কে previous করো। previous.next.value বাতিল হলে previous.next = previous.next.next। অন্যথায় previous এগোবে।
Invariant: previous পর্যন্ত রাখা প্রতিটি node বৈধ। Hint: node বাদ দিলে previous এগিয়ো না; পরেরটিও বাতিল হতে পারে। cancelled আগে set করলে membership সাধারণত O(1)।
Checkpoint: প্রথম node বাতিল, পরপর দুই node বাতিল, এবং সব node বাতিল—তিনটিই পরীক্ষা করো। সব বাদ গেলে head হবে None।
ধাপ D — পূর্ণ group-ই reverse¶
reverse_complete_groups(head, k)-এ before হলো group-এর আগের node। আগে k ধাপ হেঁটে দেখো group পূর্ণ কি না। পূর্ণ না হলে কাজ শেষ; কোনো pointer বদলাবে না।
পূর্ণ হলে তিনটি anchor রাখো: first, last, after = last.next। reverse করার শুরুতে previous = after রাখো; এতে পুরনো first শেষে গিয়েই পরের group-এর সঙ্গে যুক্ত হয়। reverse শেষে before.next = last, তারপর নতুন before হবে পুরনো first।
Invariant: before-এর আগের group-গুলো final; after-এর পরের chain এখনো untouched। next বদলানোর আগে following = current.next রাখতে হবে।
Checkpoint: [1,2,3,4,5], k=2 → [2,1,4,3,5]; k=1-এ আগের ক্রম। k বড় হলে পুরো ছোট list অপরিবর্তিত।
৪. Unit test ও solution পড়ার নিয়ম¶
Python reference implementation · Unit tests
১০টি test-এ sample, duplicates, empty/all-cancelled, অসম্পূর্ণ group, invalid input এবং node identity বজায় থাকা যাচাই হয়। আরও ১৫০ random case-এ একটি সহজ array model-এর সঙ্গে final order মেলানো হয়। শুধু value মিললে pointer solution সঠিক প্রমাণিত হয় না; cycle বা নতুন parcel node তৈরি হওয়াও ধরা চাই।
Reference পড়ার সময় প্রথমে build_manifest-এর pipeline দেখো। তারপর group reversal-এর following, previous, current তিন pointer কাগজে trace করো। এই তিনটির অর্থ মুখে বলতে না পারলে code মুখস্থ করে এগিয়ো না।
৫. Coverage ও খরচ¶
| Pattern | কোথায় কাজে লাগল |
|---|---|
| Fast/slow pointer | invalid cycle ধরা |
| Shared-tail reasoning | input lists disjoint কি না |
| Dummy node + sorted merge | এক manifest তৈরি |
| Previous/current deletion | cancelled parcel বাদ |
| In-place reversal | পূর্ণ crate-এর order বদল |
| আলাদা অনুশীলন | random pointer copy, palindrome check, digit addition, LRU cache এখানে নেই |
মোট সময় expected O(n + c), যেখানে c cancelled ID-এর সংখ্যা। cancellation set O(c) memory নেয়; pointer transformation নিজে O(1) অতিরিক্ত memory। to_values শুধু output দেখা/test-এর জন্য O(n) array বানায়; মূল algorithm সেটি ব্যবহার করে না।