DSA Notes
A complete reference of all essential linked list operations and patterns — reversal, merge, cycle detection, finding nth from end, palindrome check, flattening, sorting, partitioning, and the two-pointer technique — each with full implementation, complexity analysis, and explanations.
Introduction
Beyond basic insert and delete, linked lists have a rich set of operations and patterns that appear repeatedly in interviews and competitive programming. This lesson is a complete reference for all major linked list operation patterns, each explained from first principles with full implementations.
Mastering these patterns — especially the two-pointer (slow-fast) technique — unlocks solutions to dozens of problems that would otherwise seem very difficult.
Reversal Operations
Reverse Entire List — O(n), O(1) Space
def reverse_list(head):
"""
Classic three-pointer iterative reversal.
prev ← current → next_node
"""
prev = None
current = head
while current:
next_node = current.next # Save next
current.next = prev # Reverse pointer
prev = current # Advance prev
current = next_node # Advance current
return prev # New headReverse a Sub-List (Positions m to n) — O(n)
Reverse in Groups of K — O(n)
Merging and Sorting
Merge Two Sorted Lists — O(m+n), O(1)
def merge_sorted(l1, l2):
"""
Merge two sorted linked lists. Dummy head simplifies edge cases.
"""
dummy = Node(0)
current = dummy
while l1 and l2:
if l1.data <= l2.data:
current.next = l1
l1 = l1.next
else:
current.next = l2
l2 = l2.next
current = current.next
current.next = l1 or l2
return dummy.nextSort a Linked List (Merge Sort) — O(n log n), O(log n)
def sort_list(head):
"""
Sort a linked list using Merge Sort.
Time: O(n log n), Space: O(log n) for recursion stack.
Note: Unlike arrays, linked list Merge Sort uses O(1) extra space per merge.
"""
# Base case: empty or single node
if not head or not head.next:
return head
# Step 1: Find middle and split
mid = find_middle(head)
second_half = mid.next
mid.next = None # Split into two halves
# Step 2: Recursively sort both halves
left = sort_list(head)
right = sort_list(second_half)
# Step 3: Merge sorted halves
return merge_sorted(left, right)Palindrome Check — O(n), O(1)
def is_palindrome(head):
"""
Check if linked list is a palindrome.
Algorithm:
1. Find middle
2. Reverse second half
3. Compare first half with reversed second half
4. Restore the list (optional but good practice)
Time: O(n), Space: O(1)
"""
if not head or not head.next:
return True
# Step 1: Find middle
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# Step 2: Reverse second half (slow is now at middle)
second_half_head = reverse_list(slow)
second_half_copy = second_half_head # Save for restoration
# Step 3: Compare
first = head
second = second_half_head
is_palindrome_result = True
while second:
if first.data != second.data:
is_palindrome_result = False
break
first = first.next
second = second.next
# Step 4: Restore (reverse second half back)
reverse_list(second_half_copy)
return is_palindrome_resultRemove Duplicates
From Sorted List — O(n), O(1)
From Unsorted List — O(n), O(n)
def remove_duplicates_unsorted(head):
"""
Remove duplicates from an UNSORTED list.
Use a hash set to track seen values.
Time: O(n), Space: O(n)
"""
if not head:
return head
seen = {head.data}
current = head
while current.next:
if current.next.data in seen:
current.next = current.next.next # Skip duplicate
else:
seen.add(current.next.data)
current = current.next
return headPartitioning a Linked List
Partition Around a Value (Leetcode 86) — O(n), O(1)
def partition(head, x):
"""
Partition list so all nodes < x come before nodes >= x.
Maintains relative order within each partition.
"""
# Create two dummy heads for two sublists
less_dummy = Node(0)
greater_dummy = Node(0)
less = less_dummy
greater = greater_dummy
current = head
while current:
if current.data < x:
less.next = current
less = less.next
else:
greater.next = current
greater = greater.next
current = current.next
greater.next = None # End the greater list
less.next = greater_dummy.next # Connect less list to greater list
return less_dummy.nextIntersection of Two Lists — O(m+n), O(1)
def get_intersection_node(headA, headB):
"""
Find the node where two linked lists intersect.
Key insight: If lengths differ, advance the longer list first.
Alternative: Two pointers switch to other list's head when they reach None.
"""
if not headA or not headB:
return None
pointerA = headA
pointerB = headB
# When a pointer reaches end, redirect to the other list's head
# After at most m+n steps, both pointers will either both be None (no intersection)
# or both at the intersection node
while pointerA != pointerB:
pointerA = pointerA.next if pointerA else headB
pointerB = pointerB.next if pointerB else headA
return pointerA # Either the intersection node or NoneFlattening a Multilevel List
def flatten(head):
"""
Flatten a multilevel doubly linked list where nodes may have a 'child' pointer.
Each child list must be inserted inline after its parent node.
Time: O(n), Space: O(1)
"""
if not head:
return head
current = head
while current:
if current.child:
child = current.child
next_node = current.next
# Connect current to child
current.next = child
child.prev = current
current.child = None
# Find end of child list
tail = child
while tail.next:
tail = tail.next
# Connect end of child list to next_node
tail.next = next_node
if next_node:
next_node.prev = tail
current = current.next
return headOperations Complexity Reference
| Operation | Time | Space | Technique |
|---|---|---|---|
| Reverse entire list | O(n) | O(1) | Three-pointer |
| Reverse sublist [m,n] | O(n) | O(1) | Inject at front |
| Reverse k-groups | O(n) | O(log n) | Recursion |
| Find middle | O(n) | O(1) | Slow-fast pointers |
| Detect cycle | O(n) | O(1) | Floyd's |
| Find cycle start | O(n) | O(1) | Floyd's two-phase |
| Nth from end | O(n) | O(1) | Two pointers, gap n |
| Delete nth from end | O(n) | O(1) | Two pointers + dummy |
| Palindrome check | O(n) | O(1) | Reverse half |
| Remove duplicates (sorted) | O(n) | O(1) | Skip adjacent dups |
| Remove duplicates (unsorted) | O(n) | O(n) | Hash set |
| Merge two sorted | O(m+n) | O(1) | Two-pointer merge |
| Sort linked list | O(n log n) | O(log n) | Merge sort |
| Partition around x | O(n) | O(1) | Two sublists |
| Intersection | O(m+n) | O(1) | Pointer switching |
| Flatten multilevel | O(n) | O(1) | Inline insertion |
Summary
The two core techniques for linked list problems:
1. Two-Pointer (Slow-Fast):
- Finding middle, detecting cycles, finding nth from end
- Palindrome check (find middle + reverse second half)
- General pattern: use different speeds or different starting offsets
2. Dummy Head Node:
- Simplifies edge cases (deleting head, merging lists)
- Makes code cleaner when modifying the start of a list
- Return
dummy.nextas the new head
These two techniques, combined with careful pointer manipulation (always save next before overwriting), solve the vast majority of linked list problems.
*Next Lesson: Reverse Linked List — Comprehensive deep dive into reversal — recursive reversal, partial reversal, and k-group reversal with full analysis.*
Exam Focus
Revise definitions, diagrams, examples, and short-answer points for Linked List Operations — Every Technique with Code and Complexity.
Interview Use
Prepare one clear explanation, one practical example, and one common mistake for this Data Structures & Algorithms topic.
Search Terms
data-structures-algorithms, data structures & algorithms, data, structures, algorithms, linked, lists, list
Related Data Structures & Algorithms Topics