Linked Lists — Singly, Doubly, Circular

প্রথম সত্যিকারের ডেটা স্ট্রাকচার

~50 min Advanced 25 practice problems Live code

1. Arrays vs Linked Lists

ArrayLinked List
Memoryপাশাপাশি (contiguous)ছড়ানো nodes, pointer দিয়ে যুক্ত
i-th accessO(1)O(n)
Insert at startO(n)O(1)
Insert at endO(1) amortizedO(n), tail pointer থাকলে O(1)
Cache-friendlyহ্যাঁনা
10next → 20next → 30next → 40next → NULL head → 10 → 20 → 30 → 40 → NULL Figure 25.1 — Singly linked list structure।

2. Singly Linked List — Full CRUD

sll.c
#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

reverse.c
#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;
}
তিন pointer নিয়ে "dance" — prev, cur, nxt। প্রতিটি iteration-এ cur-এর next আগের দিকে ঘুরিয়ে দিয়ে এগোতে হয়। Interview-র সবচেয়ে জনপ্রিয় প্রশ্ন।

4. Floyd's Cycle Detection (Tortoise & Hare)

cycle.c
#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

  1. 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-ই সম্পূর্ণ উত্তর।

  2. 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;
    }
  3. 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।

  4. 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;
    }
  5. Delete first node with a given value (already shown above).
    প্রদত্ত value-র প্রথম node মুছুন।
    ✨ Show Answer

    Section 2-এর remove_value-ই reference। Node **p pattern-টি বিশেষভাবে লক্ষ করুন — head-এ ডিলিট হলেও edge case-এ আলাদা code লাগে না।

  6. Reverse iteratively.
    Iteratively reverse করুন।
    ✨ Show Answer

    Section 3-এর reverse.c-ই সম্পূর্ণ উত্তর।

  7. 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;
    }
  8. 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;
    }
  9. Detect a cycle (Floyd's).
    Floyd's algorithm দিয়ে cycle detect করুন।
    ✨ Show Answer

    Section 4-এর cycle.c-ই উত্তর।

  10. 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-তেই মিলবে।

  11. 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;
    }
  12. 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;
        }
    }
  13. Check if a list is a palindrome.
    List palindrome কি না যাচাই করুন।
    ✨ Show Answer

    Strategy: middle বের করুন → দ্বিতীয় অংশ reverse করুন → দুই অংশ side-by-side মেলান। O(n) time, O(1) extra space।

  14. 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;
    }
  15. Rotate a list by k positions.
    List-কে k অবস্থান rotate করুন।
    ✨ Show Answer

    List-এর length n বের করুন, k %= n। List-কে circular বানান, তারপর (n−k)-th node-এর পর cut করুন।

  16. Intersection point of two lists.
    দুটি list-এর intersection point বের করুন।
    ✨ Show Answer

    প্রতিটি list-এর length বের করে difference এগিয়ে রাখুন; এরপর parallel চলুন — প্রথম মিল হওয়া node-ই intersection।

  17. Sort a list using merge sort.
    Merge sort দিয়ে list sort করুন।
    ✨ Show Answer

    Middle বের করে দুই অংশে ভাগ করুন, recursively sort করুন, তারপর merge দিয়ে মিলিয়ে দিন। O(n log n) time, extra array লাগে না।

  18. 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;
    }
  19. 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 সমাধানও আছে।

  20. 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।

  21. 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 মুছুন।

  22. 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 প্রায়শই ২-১০ গুণ দ্রুত।

  23. 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-এ এসে গেছে।

  24. 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।

  25. Flatten a multilevel linked list.
    Multilevel linked list flatten করুন।
    ✨ Show Answer

    প্রতিটি node-এ একটি child list পাওয়া গেলে recursively flatten করুন, তারপর current next-এর আগে সেটিকে যুক্ত করুন। DFS-র মতো walk।

  26. 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 (শব্দকোষ)

TermMeaningবাংলায়
Linked ListA linear data structure of nodes connected by pointers.Pointer দিয়ে যুক্ত node-এর linear data structure।
NodeOne element holding data and pointer(s) to the next/prev.Data ও next/prev pointer ধারণকারী element।
Singly Linked ListEach node has only a next pointer.প্রতিটি node-এ শুধু next pointer।
Doubly Linked ListEach node has both next and prev.Node-এ next ও prev দুটোই আছে।
Circular Linked ListLast node points back to the first.শেষ node প্রথমকেই point করে।
HeadPointer to the first node.প্রথম node-এর pointer।
TailPointer to the last node.শেষ node-এর pointer।
TraversalWalking the list node by node.List-এ এক এক করে চলা।
InsertionAdding a new node at head, tail, or middle.নতুন node যোগ করা।
DeletionRemoving a node and re-linking neighbors.Node মুছে neighbor-দের পুনঃসংযোগ।
Sentinel / Dummy NodeA placeholder node simplifying edge cases.Edge case সহজ করতে dummy node।
Cycle DetectionFinding loops via Floyd's tortoise-and-hare algorithm.Floyd-এর কচ্ছপ-খরগোশ অ্যালগরিদমে loop খোঁজা।
ReverseFlipping the direction of all next pointers.সব next pointer উল্টে দেওয়া।

Summary — Module 25

Node + pointer — singly, doubly, circular, আপনার প্রয়োজন অনুযায়ী বেছে নিন। Reverse, middle, cycle detection — এই তিনটি ভালোভাবে আয়ত্ত করলেই interview-র অর্ধেক জয়। সবসময় allocation-এর সাথে free-এর pair রাখুন।

Next Module → Stacks & Queues — LIFO ও FIFO।