Linked Lists — Singly, Doubly, Circular
প্রথম সত্যিকারের ডেটা স্ট্রাকচার
1. Arrays vs Linked Lists
| Array | Linked List | |
|---|---|---|
| Memory | পাশাপাশি (contiguous) | ছড়ানো nodes, pointer দিয়ে যুক্ত |
| i-th access | O(1) | O(n) |
| Insert at start | O(n) | O(1) |
| Insert at end | O(1) amortized | O(n), tail pointer থাকলে O(1) |
| Cache-friendly | হ্যাঁ | না |
2. Singly Linked List — Full CRUD
#include <stdio.h>
#include <stdlib.h>
typedef struct Node {
int data;
struct Node *next;
} Node;
Node *new_node(int v) {
Node *n = malloc(sizeof *n);
n->data = v; n->next = NULL;
return n;
}
void push_front(Node **head, int v) {
Node *n = new_node(v);
n->next = *head;
*head = n;
}
void push_back(Node **head, int v) {
Node *n = new_node(v);
if (!*head) { *head = n; return; }
Node *cur = *head;
while (cur->next) cur = cur->next;
cur->next = n;
}
int remove_value(Node **head, int v) {
Node **p = head;
while (*p) {
if ((*p)->data == v) {
Node *t = *p; *p = t->next; free(t);
return 1;
}
p = &(*p)->next;
}
return 0;
}
void print(Node *h) {
for (; h; h = h->next) printf("%d ", h->data);
putchar('\n');
}
void free_list(Node *h) {
while (h) { Node *t = h; h = h->next; free(t); }
}
int main(void) {
Node *head = NULL;
push_back(&head, 10);
push_back(&head, 20);
push_back(&head, 30);
push_front(&head, 5);
print(head); // 5 10 20 30
remove_value(&head, 20);
print(head); // 5 10 30
free_list(head);
return 0;
}
Node **head (double pointer) ব্যবহার করলে push_front-এ caller-এর head update সরাসরি করা যায়। প্রতিটি linked-list operation-এর সময় এই pattern ভালোভাবে বোঝা জরুরি।3. The Classic — Reverse a Linked List
#include <stdio.h>
#include <stdlib.h>
typedef struct Node { int data; struct Node *next; } Node;
Node *make(int v, Node *nxt) { Node *n = malloc(sizeof *n); n->data=v; n->next=nxt; return n; }
Node *reverse(Node *head) {
Node *prev = NULL, *cur = head;
while (cur) {
Node *nxt = cur->next; // save next
cur->next = prev; // flip pointer
prev = cur; // advance prev
cur = nxt; // advance cur
}
return prev;
}
void print(Node *h) { for (; h; h = h->next) printf("%d ", h->data); putchar('\n'); }
int main(void) {
Node *h = make(1, make(2, make(3, make(4, make(5, NULL)))));
print(h); // 1 2 3 4 5
h = reverse(h);
print(h); // 5 4 3 2 1
while (h) { Node *t = h; h = h->next; free(t); }
return 0;
}
prev, cur, nxt। প্রতিটি iteration-এ cur-এর next আগের দিকে ঘুরিয়ে দিয়ে এগোতে হয়। Interview-র সবচেয়ে জনপ্রিয় প্রশ্ন।4. Floyd's Cycle Detection (Tortoise & Hare)
#include <stdio.h>
#include <stdlib.h>
typedef struct Node { int data; struct Node *next; } Node;
int has_cycle(Node *head) {
Node *slow = head, *fast = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) return 1;
}
return 0;
}
int main(void) {
Node *a = malloc(sizeof *a); a->data = 1;
Node *b = malloc(sizeof *b); b->data = 2;
Node *c = malloc(sizeof *c); c->data = 3;
a->next = b; b->next = c; c->next = NULL;
printf("no cycle: %s\n", has_cycle(a) ? "yes" : "no");
c->next = b; // create a cycle: 1 → 2 → 3 → 2 …
printf("with cycle: %s\n", has_cycle(a) ? "yes" : "no");
c->next = NULL; // break cycle before freeing
free(a); free(b); free(c);
return 0;
}
Slow pointer 1 ধাপ, fast 2 ধাপ করে এগোয় — cycle থাকলে fast slow-কে ধরবেই। Proof: গাণিতিকভাবে cycle-এর length-এর গুণিতক পার্থক্য দেখে সেটা দেখানো যায়।
5. Doubly Linked List
প্রতিটি node-এ prev ও next দুটি pointer — দুই দিকেই চলাচল সম্ভব। এক্সট্রা pointer-এর খরচে ইনসার্ট/ডিলিট আরও সহজ।
typedef struct DNode {
int data;
struct DNode *prev, *next;
} DNode;
6. Circular List
শেষ node-এর next আবার head-এ ফিরে যায়। রাউন্ড-রবিন scheduler, সাইক্লিক বাফার, Josephus problem-এর জন্য আদর্শ।
7. Practice Problems
- Implement singly linked list with push_front, push_back, print, free.push_front, push_back, print, free দিয়ে singly linked list লিখুন।
✨ Show Answer
Section 2-এর
sll.c-ই সম্পূর্ণ উত্তর। - Count nodes iteratively.Iteratively node সংখ্যা গুনুন।
✨ Show Answer
count.c#include <stdio.h> #include <stdlib.h> typedef struct Node { int v; struct Node *next; } Node; Node *mk(int v, Node *n) { Node *x = malloc(sizeof *x); x->v=v; x->next=n; return x; } int count(Node *h) { int c=0; for (; h; h=h->next) c++; return c; } int main(void) { Node *h = mk(1, mk(2, mk(3, mk(4, NULL)))); printf("count = %d\n", count(h)); while (h) { Node *t=h; h=h->next; free(t); } return 0; } - Count nodes recursively.Recursively node গুনুন।
✨ Show Answer
int count_r(Node *h) { return h ? 1 + count_r(h->next) : 0; }লম্বা list-এ stack overflow-র ঝুঁকি আছে; তাই iterative version বাস্তবে preferred।
- Search for a value, return the node or NULL.Value খুঁজে node বা NULL return করুন।
✨ Show Answer
Node *find(Node *h, int key) { for (; h; h = h->next) if (h->v == key) return h; return NULL; } - Delete first node with a given value (already shown above).প্রদত্ত value-র প্রথম node মুছুন।
✨ Show Answer
Section 2-এর
remove_value-ই reference।Node **ppattern-টি বিশেষভাবে লক্ষ করুন — head-এ ডিলিট হলেও edge case-এ আলাদা code লাগে না। - Reverse iteratively.Iteratively reverse করুন।
✨ Show Answer
Section 3-এর
reverse.c-ই সম্পূর্ণ উত্তর। - Reverse recursively.Recursively reverse করুন।
✨ Show Answer
Node *reverse_r(Node *h) { if (!h || !h->next) return h; Node *new_head = reverse_r(h->next); h->next->next = h; h->next = NULL; return new_head; } - Find the middle node (slow/fast pointer).Slow/fast pointer দিয়ে middle node বের করুন।
✨ Show Answer
middle.c#include <stdio.h> #include <stdlib.h> typedef struct N { int v; struct N *next; } N; N *mk(int v, N *n) { N *x = malloc(sizeof *x); x->v=v; x->next=n; return x; } N *middle(N *h) { N *s = h, *f = h; while (f && f->next) { s = s->next; f = f->next->next; } return s; } int main(void) { N *h = mk(1, mk(2, mk(3, mk(4, mk(5, NULL))))); printf("middle = %d\n", middle(h)->v); while (h) { N *t=h; h=h->next; free(t); } return 0; } - Detect a cycle (Floyd's).Floyd's algorithm দিয়ে cycle detect করুন।
✨ Show Answer
Section 4-এর
cycle.c-ই উত্তর। - If a cycle exists, find its entry point.Cycle থাকলে তার entry point বের করুন।
✨ Show Answer
N *cycle_start(N *h) { N *s = h, *f = h; while (f && f->next) { s = s->next; f = f->next->next; if (s == f) { // meeting point s = h; while (s != f) { s = s->next; f = f->next; } return s; } } return NULL; }Mathematical trick: মিটিং-পয়েন্ট থেকে একটি slow pointer head-এ নিয়ে গেলে দুটি আবার entry-তেই মিলবে।
- Merge two sorted lists.দুটি sorted list merge করুন।
✨ Show Answer
merge.c#include <stdio.h> #include <stdlib.h> typedef struct N { int v; struct N *next; } N; N *mk(int v, N *n) { N *x = malloc(sizeof *x); x->v=v; x->next=n; return x; } N *merge(N *a, N *b) { N dummy = {0, NULL}, *tail = &dummy; while (a && b) { if (a->v <= b->v) { tail->next = a; a = a->next; } else { tail->next = b; b = b->next; } tail = tail->next; } tail->next = a ? a : b; return dummy.next; } int main(void) { N *a = mk(1, mk(4, mk(7, NULL))); N *b = mk(2, mk(3, mk(9, NULL))); N *m = merge(a, b); for (N *p = m; p; p = p->next) printf("%d ", p->v); putchar('\n'); while (m) { N *t=m; m=m->next; free(t); } return 0; } - Remove duplicates from a sorted list.Sorted list থেকে duplicate মুছুন।
✨ Show Answer
void dedup(N *h) { for (; h && h->next; ) { if (h->v == h->next->v) { N *t = h->next; h->next = t->next; free(t); } else h = h->next; } } - Check if a list is a palindrome.List palindrome কি না যাচাই করুন।
✨ Show Answer
Strategy: middle বের করুন → দ্বিতীয় অংশ reverse করুন → দুই অংশ side-by-side মেলান। O(n) time, O(1) extra space।
- Swap every two adjacent nodes.প্রতি দুটি adjacent node swap করুন।
✨ Show Answer
N *swap_pairs(N *h) { N dummy = {0, h}, *p = &dummy; while (p->next && p->next->next) { N *a = p->next, *b = a->next; a->next = b->next; b->next = a; p->next = b; p = a; } return dummy.next; } - Rotate a list by k positions.List-কে k অবস্থান rotate করুন।
✨ Show Answer
List-এর length
nবের করুন, k %= n। List-কে circular বানান, তারপর (n−k)-th node-এর পর cut করুন। - Intersection point of two lists.দুটি list-এর intersection point বের করুন।
✨ Show Answer
প্রতিটি list-এর length বের করে difference এগিয়ে রাখুন; এরপর parallel চলুন — প্রথম মিল হওয়া node-ই intersection।
- Sort a list using merge sort.Merge sort দিয়ে list sort করুন।
✨ Show Answer
Middle বের করে দুই অংশে ভাগ করুন, recursively sort করুন, তারপর
mergeদিয়ে মিলিয়ে দিন। O(n log n) time, extra array লাগে না। - Build a doubly linked list + insert/delete at both ends.Doubly linked list বানান ও উভয় প্রান্তে insert/delete করুন।
✨ Show Answer
typedef struct D { int v; struct D *prev, *next; } D; void push_front(D **head, int v) { D *n = malloc(sizeof *n); n->v = v; n->prev = NULL; n->next = *head; if (*head) (*head)->prev = n; *head = n; } - Build a circular list and solve Josephus problem.Circular list-এ Josephus problem সমাধান করুন।
✨ Show Answer
n-টি node circular-ভাবে যুক্ত, প্রতি k-th node মুছে যাবে। শেষে যে node বেঁচে থাকে সেই survivor। Linked list দিয়ে সোজা; closed-form সমাধানও আছে।
- Copy a list with random pointers.Random pointer-সহ list কপি করুন।
✨ Show Answer
Classic interview problem। Trick: প্রতিটি node-এর ঠিক পরে তার clone insert করুন, random assign করুন (
n->next->random = n->random->next), শেষে দুই list আলাদা করুন। O(n) time, O(1) extra space। - Remove duplicates from an unsorted list (O(n²) and O(n)).Unsorted list থেকে duplicate মুছুন — O(n²) ও O(n) দুটোতেই।
✨ Show Answer
O(n²): প্রতিটি node-এর জন্য পরের সব node-এ খুঁজুন। O(n): hash set-এ আগের মান রাখুন, match হলে current node মুছুন।
- Why does a linked list lose to an array for cache-sensitive workloads?Cache-সংবেদনশীল কাজে linked list array-র চেয়ে ধীর কেন?
✨ Show Answer
CPU cache একবারে 64 byte (cache line) নিয়ে আসে। Array-র পাশাপাশি elements একই লাইনে থাকে — একটি fetch-এই পরের কয়েকটি পাওয়া যায়। Linked list-এর node-গুলো ছড়ানো; প্রতিটি node access-এ প্রায় নতুন cache miss। তাই একই asymptotic complexity হলেও array প্রায়শই ২-১০ গুণ দ্রুত।
- Prove that iterative reverse is correct using a loop invariant.Loop invariant দিয়ে iterative reverse-এর শুদ্ধতা প্রমাণ করুন।
✨ Show Answer
Invariant: iteration-এর শুরুতে —
prevহলো ইতিমধ্যে deal-করা অংশের reverse-এর head;curবাকি (unprocessed) sub-list-এর head। শুরুতে: prev = NULL, cur = head — ০টি node reverse হলো, সব বাকি। Step:cur-কেprev-এ যুক্ত করি, dot and advance — invariant বজায় থাকে। শেষে: cur = NULL — সব node reverse হয়েprev-এ এসে গেছে। - Convert a BST to a sorted doubly linked list.BST-কে sorted doubly linked list-এ রূপান্তর করুন।
✨ Show Answer
Inorder traversal-এ প্রতিটি node-এর জন্য শেষ visited node-কে
prevহিসেবে সেট করুন এবং তারnext-ও সংশোধন করুন। Recursion + global "last" variable — O(n) time। - Flatten a multilevel linked list.Multilevel linked list flatten করুন।
✨ Show Answer
প্রতিটি node-এ একটি child list পাওয়া গেলে recursively flatten করুন, তারপর current next-এর আগে সেটিকে যুক্ত করুন। DFS-র মতো walk।
- Implement a simple LRU with singly linked list (then discuss why doubly is better).Singly linked list দিয়ে সাধারণ LRU লিখুন; তারপর আলোচনা করুন doubly কেন ভালো।
✨ Show Answer
Singly-তে node যেকোনো জায়গা থেকে মুছতে হলে আগের node খোঁজা লাগে — O(n)। Doubly linked list-এ
prevথাকে বলে O(1)-এ মুছে সামনে আনা যায়। তাই real-world LRU (ধরুন Redis, memcached) সবসময় doubly linked list + hash map।
Glossary (শব্দকোষ)
| Term | Meaning | বাংলায় |
|---|---|---|
| Linked List | A linear data structure of nodes connected by pointers. | Pointer দিয়ে যুক্ত node-এর linear data structure। |
| Node | One element holding data and pointer(s) to the next/prev. | Data ও next/prev pointer ধারণকারী element। |
| Singly Linked List | Each node has only a next pointer. | প্রতিটি node-এ শুধু next pointer। |
| Doubly Linked List | Each node has both next and prev. | Node-এ next ও prev দুটোই আছে। |
| Circular Linked List | Last node points back to the first. | শেষ node প্রথমকেই point করে। |
| Head | Pointer to the first node. | প্রথম node-এর pointer। |
| Tail | Pointer to the last node. | শেষ node-এর pointer। |
| Traversal | Walking the list node by node. | List-এ এক এক করে চলা। |
| Insertion | Adding a new node at head, tail, or middle. | নতুন node যোগ করা। |
| Deletion | Removing a node and re-linking neighbors. | Node মুছে neighbor-দের পুনঃসংযোগ। |
| Sentinel / Dummy Node | A placeholder node simplifying edge cases. | Edge case সহজ করতে dummy node। |
| Cycle Detection | Finding loops via Floyd's tortoise-and-hare algorithm. | Floyd-এর কচ্ছপ-খরগোশ অ্যালগরিদমে loop খোঁজা। |
| Reverse | Flipping the direction of all next pointers. | সব next pointer উল্টে দেওয়া। |
Summary — Module 25
Node + pointer — singly, doubly, circular, আপনার প্রয়োজন অনুযায়ী বেছে নিন। Reverse, middle, cycle detection — এই তিনটি ভালোভাবে আয়ত্ত করলেই interview-র অর্ধেক জয়। সবসময় allocation-এর সাথে free-এর pair রাখুন।