Vivek
Back to Articles

Linked List – DSA Notes

9/3/2025

Core singly/doubly list routines: reverse, middle, merge, k-groups, palindrome, cycles and more.

📌 Summary Table

ProblemPatternTimeSpace
Reverse Linked List (Iterative)Pointer rewiringO(n)O(1)
Reverse Linked List (Tail Recursion)Tail recursionO(n)O(n) stack
Middle of Linked ListTortoise–HareO(n)O(1)
Remove Elements (Recursion)Recurrence on nextO(n)O(n) stack
Merge Two Sorted Lists (In-place)Splice nodesO(m+n)O(1)
Remove N-th From EndTwo pointers + dummyO(n)O(1)
Delete Given NodeCopy next into currentO(1)O(1)
Odd–Even Linked ListTwo lanes stitchingO(n)O(1)
Add Two NumbersSchool additionO(max(m,n))O(1) excl. output
Intersection of Two ListsA+B, B+A trickO(m+n)O(1)
Detect Cycle (Floyd)Slow/Fast pointersO(n)O(1)
Reverse Nodes in k-GroupHead-insertion k timesO(n)O(1)
Palindrome Linked ListReverse 2nd halfO(n)O(1)
Detect Cycle II (Start)Floyd + resetO(n)O(1)
Flatten a Linked List (bottom)Merge verticallyO(N)O(1)
Rotate ListMake ring, breakO(n)O(1)
Copy List w/ Random (O(1) extra)Interweave clonesO(n)O(1) extra
Sort List (Merge Sort)Split & mergeO(n log n)O(log n) stack

Reverse Linked List (Iterative)

Problem. Reverse a singly linked list in-place.

Idea.
Walk through nodes, rewiring next to point backward with prev/cur/nxt.

Complexity. O(n) time, O(1) space.

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null, cur = head;
        while (cur != null) {
            ListNode nxt = cur.next; // store next
            cur.next = prev;         // reverse link
            prev = cur;              // advance prev
            cur = nxt;               // advance cur
        }
        return prev; // new head
    }
}

Key Takeaway. Three-pointer prev/cur/next rewiring pattern.


Reverse Linked List (Tail Recursion)

Problem. Reverse a list using recursion.

Idea. Pass prev along; recursion mimics the iterative rewiring.

Complexity. O(n) time, O(n) stack.

class Solution {
    public ListNode reverseList(ListNode head) { return rev(null, head); }

    private ListNode rev(ListNode prev, ListNode cur) {
        if (cur == null) return prev;     // base: prev is new head
        ListNode nxt = cur.next;          // store next
        cur.next = prev;                  // reverse
        return rev(cur, nxt);             // move forward
    }
}

Key Takeaway. Tail recursion = iterative logic expressed recursively.


Middle of Linked List (Tortoise–Hare)

Problem. Return middle node (if even length, return second middle).

Idea. Move slow by 1 and fast by 2.

Complexity. O(n) time, O(1) space.

class Solution {
    public ListNode middleNode(ListNode head) {
        ListNode slow = head, fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;         // +1
            fast = fast.next.next;    // +2
        }
        return slow;
    }
}

Key Takeaway. Two-speed pointers find mid elegantly.


Remove Linked List Elements (Recursion)

Problem. Remove all nodes with value val.

Idea. Solve for head.next, then decide to keep or skip head.

Complexity. O(n) time, O(n) stack.

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if (head == null) return null;
        head.next = removeElements(head.next, val); // clean tail
        return head.val == val ? head.next : head;  // keep/skip head
    }
}

Key Takeaway. Post-order recursion on next then decide.


Merge Two Sorted Lists (In-place)

Problem. Merge two sorted lists without extra nodes.

Idea. Ensure l1.val <= l2.val, then splice runs until exhausted.

Complexity. O(m+n) time, O(1) space.

class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1 == null) return l2;
        if (l2 == null) return l1;
        if (l1.val > l2.val) { ListNode t = l1; l1 = l2; l2 = t; } // l1 head is smaller
        ListNode res = l1;

        while (l1 != null && l2 != null) {
            ListNode prev = null;
            // advance l1 while it stays <= l2
            while (l1 != null && l1.val <= l2.val) { prev = l1; l1 = l1.next; }
            // splice l2 block after prev
            prev.next = l2;
            // swap roles to keep invariant
            ListNode t = l1; l1 = l2; l2 = t;
        }
        return res;
    }
}

Key Takeaway. Keep smaller list as primary and splice the other in.


Remove N-th Node From End

Problem. Remove the n-th node from the end in one pass.

Idea. Use a dummy head; advance fast n steps, then move both until fast is at tail.

Complexity. O(n) time, O(1) space.

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0, head), slow = dummy, fast = dummy;
        for (int i = 0; i < n; i++) fast = fast.next;   // gap n
        while (fast.next != null) { slow = slow.next; fast = fast.next; }
        slow.next = slow.next.next; // delete target
        return dummy.next;
    }
}

Key Takeaway. Dummy node + two pointers avoids edge cases.


Delete Given Node (Only Node Ref)

Problem. Delete a node when only pointer to that node is given (non-tail).

Idea. Copy next node’s data and bypass it.

Complexity. O(1) time, O(1) space.

class Solution {
    public void deleteNode(ListNode node) {
        node.val = node.next.val;         // copy data
        node.next = node.next.next;       // skip next
    }
}

Key Takeaway. Overwrite with next; only works if node is not tail.


Odd–Even Linked List

Problem. Group odd-indexed nodes followed by even-indexed nodes (1-indexed).

Idea. Keep two chains (odd and even), then stitch.

Complexity. O(n) time, O(1) space.

class Solution {
    public ListNode oddEvenList(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode odd = head, even = head.next, evenHead = even;
        while (even != null && even.next != null) {
            odd.next = even.next; odd = odd.next;   // link odd chain
            even.next = odd.next; even = even.next; // link even chain
        }
        odd.next = evenHead; // stitch
        return head;
    }
}

Key Takeaway. Maintain two lanes and stitch at the end.


Add Two Numbers

Problem. Add two numbers where digits are stored in reverse order.

Idea. Walk both lists, sum digits + carry, create output nodes.

Complexity. O(max(m,n)) time, O(1) extra (excluding result list).

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode();
        ListNode cur = dummy;
        int carry = 0;

        while (l1 != null || l2 != null || carry != 0) {
            int sum = carry + (l1 != null ? l1.val : 0) + (l2 != null ? l2.val : 0);
            carry = sum / 10;
            cur.next = new ListNode(sum % 10);
            cur = cur.next;
            if (l1 != null) l1 = l1.next;
            if (l2 != null) l2 = l2.next;
        }
        return dummy.next;
    }
}

Key Takeaway. Digit-by-digit with carry (schoolbook addition).


Intersection of Two Linked Lists (Two Pointers)

Problem. Find the node where two singly linked lists intersect (or null).

Idea. Traverse A then B for pointer a, and B then A for pointer b. They align by length.

Complexity. O(m+n) time, O(1) space.

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) return null;
        ListNode a = headA, b = headB;
        while (a != b) {                 // both will traverse A+B
            a = (a == null) ? headB : a.next;
            b = (b == null) ? headA : b.next;
        }
        return a; // either intersection or null
    }
}

Key Takeaway. The A+B / B+A trick equalizes path lengths.


Detect Cycle (Floyd)

Problem. Determine if a cycle exists in a linked list.

Idea. Tortoise–Hare; if they ever meet, a cycle exists.

Complexity. O(n) time, O(1) space.

public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head == null || head.next == null) return false;
        ListNode slow = head, fast = head;
        while (fast != null && fast.next != null) {
            slow = slow.next; fast = fast.next.next;
            if (slow == fast) return true; // met inside cycle
        }
        return false;
    }
}

Key Takeaway. Floyd’s = detect cycle with two speeds.


Reverse Nodes in k-Group

Problem. Reverse nodes in groups of size k; leftover < k stays as-is.

Idea. For each block of size k, do head-insertion k-1 times.

Complexity. O(n) time, O(1) space.

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        if (head == null || k == 1) return head;
        ListNode dummy = new ListNode(0); dummy.next = head;
        ListNode cur = dummy, nxt = dummy, pre = dummy;
        int count = 0;

        // count nodes
        while (cur.next != null) { cur = cur.next; count++; }

        // reverse each full k-block
        while (count >= k) {
            cur = pre.next;          // first node of block
            nxt = cur.next;          // node to bring to front
            for (int i = 1; i < k; i++) {
                cur.next = nxt.next; // detach nxt
                nxt.next = pre.next; // insert nxt after pre
                pre.next = nxt;      // new front becomes nxt
                nxt = cur.next;      // advance nxt
            }
            pre = cur;               // move pre to end of reversed block
            count -= k;
        }
        return dummy.next;
    }
}

Key Takeaway. Head-insertion inside each k-block.


Palindrome Linked List (Reverse Second Half)

Problem. Check if linked list is palindrome.

Idea. Find mid, reverse second half, compare halves, (optionally restore).

Complexity. O(n) time, O(1) space.

class Solution {
    public boolean isPalindrome(ListNode head) {
        if (head == null || head.next == null) return true;

        // find middle (slow ends at first half end for even)
        ListNode slow = head, fast = head;
        while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; }

        // reverse second half starting at slow.next (for even, slow is last of first half)
        slow.next = reverse(slow.next);
        slow = slow.next; // start of reversed second half

        // compare halves
        ListNode left = head;
        while (slow != null) {
            if (left.val != slow.val) return false;
            left = left.next; slow = slow.next;
        }

        // (optional) restore: slow was reversed head; reverse again if needed
        return true;
    }

    private ListNode reverse(ListNode h) {
        ListNode prev = null, cur = h;
        while (cur != null) { ListNode nxt = cur.next; cur.next = prev; prev = cur; cur = nxt; }
        return prev;
    }
}

Key Takeaway. Reverse 2nd half → compare → (restore optional).


Detect Cycle II (Start Node)

Problem. If a cycle exists, return the node where the cycle begins.

Idea. After meeting inside the cycle, set one pointer to head, move both 1-step; they meet at start.

Complexity. O(n) time, O(1) space.

public class Solution {
    public ListNode detectCycle(ListNode head) {
        if (head == null || head.next == null) return null;
        ListNode slow = head, fast = head;

        // first meeting
        while (fast != null && fast.next != null) {
            slow = slow.next; fast = fast.next.next;
            if (slow == fast) { // cycle found
                fast = head;    // reset one pointer
                while (slow != fast) { slow = slow.next; fast = fast.next; }
                return slow;    // start of cycle
            }
        }
        return null; // no cycle
    }
}

Key Takeaway. Floyd + reset meets at cycle start.


Flatten a Linked List (Bottom Pointers)

Problem. Given a list where next links heads horizontally and each node has a sorted vertical list via bottom, flatten into one sorted bottom-linked list.

Idea. Flatten from right to left; repeatedly merge two sorted bottom-lists.

Complexity. O(N) over total nodes, O(1) extra.

class GfG {
    // merge two bottom-sorted lists
    Node merge(Node a, Node b) {
        Node dummy = new Node(0);
        Node cur = dummy;
        while (a != null && b != null) {
            if (a.data < b.data) { cur.bottom = a; a = a.bottom; }
            else { cur.bottom = b; b = b.bottom; }
            cur = cur.bottom;
        }
        cur.bottom = (a != null) ? a : b;
        return dummy.bottom;
    }

    // flatten starting from the rightmost list
    Node flatten(Node root) {
        if (root == null || root.next == null) return root;
        root.next = flatten(root.next);     // flatten rest
        root = merge(root, root.next);      // merge current with flattened rest
        return root;
    }
}

Key Takeaway. Divide-and-merge along bottom pointers.


Rotate List

Problem. Rotate list to the right by k places.

Idea. Find length, make it a ring, move len - (k % len) steps, break.

Complexity. O(n) time, O(1) space.

class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        if (head == null || head.next == null || k == 0) return head;

        // compute length and get tail
        int len = 1;
        ListNode tail = head;
        while (tail.next != null) { tail = tail.next; len++; }

        // make circular
        tail.next = head;

        // find new tail: step len - (k % len) times
        k = len - (k % len);
        while (k-- > 0) tail = tail.next;

        // new head is next, break the ring
        head = tail.next;
        tail.next = null;

        return head;
    }
}

Key Takeaway. Ring trick → step → break.


Copy List with Random Pointer (O(1) extra)

Problem. Clone a list where each node has next and random pointer.

Idea.

  1. Interleave clone nodes, 2) set random via cur.random.next, 3) split lists.

Complexity. O(n) time, O(1) extra space.

class Solution {
    public Node copyRandomList(Node head) {
        if (head == null) return null;

        // 1) interleave copied nodes
        Node cur = head;
        while (cur != null) {
            Node copy = new Node(cur.val);
            copy.next = cur.next;
            cur.next = copy;
            cur = copy.next;
        }

        // 2) assign random pointers
        cur = head;
        while (cur != null) {
            if (cur.random != null) cur.next.random = cur.random.next;
            cur = cur.next.next;
        }

        // 3) separate lists
        cur = head;
        Node pseudo = new Node(0), copyCur = pseudo;
        while (cur != null) {
            copyCur.next = cur.next;            // take copy
            cur.next = cur.next.next;           // restore original next
            copyCur = copyCur.next;
            cur = cur.next;
        }

        return pseudo.next;
    }
}

Key Takeaway. Interweave clones → set randoms → split.


Sort List (Merge Sort on List)

Problem. Sort a singly linked list in ascending order.

Idea. Split with slow/fast to find mid, recursively sort, then merge.

Complexity. O(n log n) time, O(log n) recursion stack.

class Solution {
    public ListNode sortList(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode mid = split(head);             // mid = start of right half
        ListNode left = sortList(head);
        ListNode right = sortList(mid);
        return merge(left, right);
    }

    // split list into [head..prev] and [slow..end]; return start of right half
    private ListNode split(ListNode head) {
        ListNode slow = head, fast = head, prev = null;
        while (fast != null && fast.next != null) {
            prev = slow; slow = slow.next; fast = fast.next.next;
        }
        prev.next = null; // cut
        return slow;      // start of right half
    }

    private ListNode merge(ListNode a, ListNode b) {
        ListNode dummy = new ListNode();
        ListNode t = dummy;
        while (a != null && b != null) {
            if (a.val < b.val) { t.next = a; a = a.next; }
            else { t.next = b; b = b.next; }
            t = t.next;
        }
        t.next = (a != null) ? a : b;
        return dummy.next;
    }
}

Key Takeaway. Merge sort is natural for linked lists (no random access).