Bird
Raised Fist0
Interview Prepin-place-linked-listmediumAmazonGoogleFacebook

Sort List (Merge Sort on LL)

Choose your preparation mode4 modes available
🎯
Sort List (Merge Sort on LL)
mediumLINKED_LISTAmazonGoogleFacebook

Imagine you have a long chain of unsorted train cars and you want to reorder them efficiently without detaching and reattaching each car multiple times.

💡 Sorting a linked list is different from sorting arrays because you can't access elements by index directly. Beginners often struggle with splitting and merging linked lists efficiently without extra space.
📋
Problem Statement

Given the head of a singly linked list, sort the list in ascending order and return the sorted list. You must do this in O(n log n) time and O(1) space complexity (in-place).

The number of nodes in the list is in the range [0, 10^5]-10^5 <= Node.val <= 10^5
💡
Example
Input"head = [4, 2, 1, 3]"
Output[1, 2, 3, 4]

The list is sorted by repeatedly splitting and merging sublists until fully sorted.

  • Empty list → returns empty list
  • Single node list → returns the same single node
  • All nodes have the same value → returns the same list unchanged
  • Already sorted list → returns the same list without changes
⚠️
Common Mistakes
Not breaking the list properly when splitting

Leads to infinite loops or incorrect merges

✅ Set slow.next = None to split the list cleanly

Incorrectly handling empty or single node lists

Null pointer exceptions or wrong output

✅ Add base case checks for head == None or head.next == None

Merging lists without updating tail pointer correctly

Resulting list may be truncated or malformed

✅ Advance tail pointer after attaching nodes during merge

Using recursion without considering stack overflow for large lists

Runtime error due to deep recursion

✅ Use iterative bottom-up merge sort for large inputs

Sorting by swapping node values instead of rearranging nodes

May violate in-place constraints or cause confusion

✅ Merge by changing next pointers, not node values

🧠
Brute Force (Extract to Array, Sort, Rebuild)
💡 This approach exists to build intuition by converting the linked list to an array, sorting it, then reconstructing the list. It’s easy to implement but not in-place and uses extra space.

Intuition

Convert the linked list to an array, sort the array using built-in sorting, then rebuild the linked list from the sorted array.

Algorithm

  1. Traverse the linked list and store all node values in an array.
  2. Sort the array using a built-in efficient sorting algorithm.
  3. Create a new linked list by iterating over the sorted array and creating nodes.
  4. Return the head of the newly created sorted linked list.
💡 This approach is straightforward but uses extra memory and does not leverage linked list properties.
</>
Code
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

def sortList(head):
    if not head:
        return None
    arr = []
    current = head
    while current:
        arr.append(current.val)
        current = current.next
    arr.sort()
    dummy = ListNode(0)
    current = dummy
    for val in arr:
        current.next = ListNode(val)
        current = current.next
    return dummy.next

# Example usage:
# head = ListNode(4, ListNode(2, ListNode(1, ListNode(3))))
# sorted_head = sortList(head)
# while sorted_head:
#     print(sorted_head.val, end=' ')
#     sorted_head = sorted_head.next
Line Notes
arr = []Initialize array to store node values for easy sorting
while current:Traverse entire linked list to extract values
arr.sort()Use built-in sort for simplicity and efficiency
for val in arr:Rebuild linked list from sorted values
class ListNode {
    int val;
    ListNode next;
    ListNode(int val) { this.val = val; this.next = null; }
}

public class Solution {
    public ListNode sortList(ListNode head) {
        if (head == null) return null;
        List<Integer> arr = new ArrayList<>();
        ListNode current = head;
        while (current != null) {
            arr.add(current.val);
            current = current.next;
        }
        Collections.sort(arr);
        ListNode dummy = new ListNode(0);
        current = dummy;
        for (int val : arr) {
            current.next = new ListNode(val);
            current = current.next;
        }
        return dummy.next;
    }

    // Example main method
    // public static void main(String[] args) {
    //     ListNode head = new ListNode(4);
    //     head.next = new ListNode(2);
    //     head.next.next = new ListNode(1);
    //     head.next.next.next = new ListNode(3);
    //     Solution sol = new Solution();
    //     ListNode sorted = sol.sortList(head);
    //     while (sorted != null) {
    //         System.out.print(sorted.val + " ");
    //         sorted = sorted.next;
    //     }
    // }
}
Line Notes
List<Integer> arr = new ArrayList<>();Store node values in a dynamic array for sorting
while (current != null)Traverse linked list to collect all values
Collections.sort(arr);Sort values using Java's efficient built-in sort
for (int val : arr)Rebuild linked list from sorted values
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(nullptr) {}
};

ListNode* sortList(ListNode* head) {
    if (!head) return nullptr;
    vector<int> arr;
    ListNode* current = head;
    while (current) {
        arr.push_back(current->val);
        current = current->next;
    }
    sort(arr.begin(), arr.end());
    ListNode dummy(0);
    current = &dummy;
    for (int val : arr) {
        current->next = new ListNode(val);
        current = current->next;
    }
    return dummy.next;
}

// Example usage:
// int main() {
//     ListNode* head = new ListNode(4);
//     head->next = new ListNode(2);
//     head->next->next = new ListNode(1);
//     head->next->next->next = new ListNode(3);
//     ListNode* sorted = sortList(head);
//     while (sorted) {
//         cout << sorted->val << " ";
//         sorted = sorted->next;
//     }
//     return 0;
// }
Line Notes
vector<int> arr;Use vector to store node values for sorting
while (current)Traverse linked list to extract all values
sort(arr.begin(), arr.end());Sort values using STL sort
for (int val : arr)Rebuild linked list from sorted values
function ListNode(val, next = null) {
    this.val = val;
    this.next = next;
}

function sortList(head) {
    if (!head) return null;
    const arr = [];
    let current = head;
    while (current) {
        arr.push(current.val);
        current = current.next;
    }
    arr.sort((a, b) => a - b);
    const dummy = new ListNode(0);
    current = dummy;
    for (const val of arr) {
        current.next = new ListNode(val);
        current = current.next;
    }
    return dummy.next;
}

// Example usage:
// let head = new ListNode(4, new ListNode(2, new ListNode(1, new ListNode(3))));
// let sorted = sortList(head);
// while (sorted) {
//     console.log(sorted.val);
//     sorted = sorted.next;
// }
Line Notes
const arr = [];Initialize array to hold node values for sorting
while (current)Traverse linked list to collect all values
arr.sort((a, b) => a - b);Sort array numerically in ascending order
for (const val of arr)Rebuild linked list from sorted values
⏱
Complexity
TimeO(n log n)
SpaceO(n)

Extracting values takes O(n), sorting takes O(n log n), rebuilding takes O(n). Extra space is O(n) for the array.

💡 For n=20, this means about 20*log2(20) ≈ 86 operations plus storing 20 values in an array.
Interview Verdict: Accepted but not in-place; uses extra memory

This approach is easy to implement but doesn't meet the in-place constraint; good for initial understanding.

🧠
Recursive Merge Sort on Linked List
💡 This approach uses the classic divide-and-conquer merge sort adapted for linked lists, splitting the list with fast/slow pointers and merging sorted halves recursively.

Intuition

Split the list into two halves using fast and slow pointers, recursively sort each half, then merge the two sorted halves.

Algorithm

  1. If the list is empty or has one node, return it as it is already sorted.
  2. Use fast and slow pointers to find the middle of the list and split it into two halves.
  3. Recursively sort the left half and the right half.
  4. Merge the two sorted halves and return the merged list.
💡 The recursion and pointer manipulation can be tricky to visualize at first, but breaking down into splitting and merging helps.
</>
Code
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

def merge(l1, l2):
    dummy = ListNode(0)
    tail = dummy
    while l1 and l2:
        if l1.val < l2.val:
            tail.next = l1
            l1 = l1.next
        else:
            tail.next = l2
            l2 = l2.next
        tail = tail.next
    tail.next = l1 or l2
    return dummy.next

def sortList(head):
    if not head or not head.next:
        return head
    slow, fast = head, head.next
    while fast and fast.next:
        slow = slow.next
        fast = fast.next.next
    mid = slow.next
    slow.next = None
    left = sortList(head)
    right = sortList(mid)
    return merge(left, right)

# Example usage:
# head = ListNode(4, ListNode(2, ListNode(1, ListNode(3))))
# sorted_head = sortList(head)
# while sorted_head:
#     print(sorted_head.val, end=' ')
#     sorted_head = sorted_head.next
Line Notes
if not head or not head.next:Base case: list with 0 or 1 node is already sorted
slow, fast = head, head.nextInitialize pointers to find middle for splitting
while fast and fast.next:Move fast pointer twice as fast to find midpoint
slow.next = NoneSplit the list into two halves by breaking link
class ListNode {
    int val;
    ListNode next;
    ListNode(int val) { this.val = val; this.next = null; }
}

public class Solution {
    private ListNode merge(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(0);
        ListNode tail = dummy;
        while (l1 != null && l2 != null) {
            if (l1.val < l2.val) {
                tail.next = l1;
                l1 = l1.next;
            } else {
                tail.next = l2;
                l2 = l2.next;
            }
            tail = tail.next;
        }
        tail.next = (l1 != null) ? l1 : l2;
        return dummy.next;
    }

    public ListNode sortList(ListNode head) {
        if (head == null || head.next == null) return head;
        ListNode slow = head, fast = head.next;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        ListNode mid = slow.next;
        slow.next = null;
        ListNode left = sortList(head);
        ListNode right = sortList(mid);
        return merge(left, right);
    }

    // Example main method
    // public static void main(String[] args) {
    //     ListNode head = new ListNode(4);
    //     head.next = new ListNode(2);
    //     head.next.next = new ListNode(1);
    //     head.next.next.next = new ListNode(3);
    //     Solution sol = new Solution();
    //     ListNode sorted = sol.sortList(head);
    //     while (sorted != null) {
    //         System.out.print(sorted.val + " ");
    //         sorted = sorted.next;
    //     }
    // }
}
Line Notes
if (head == null || head.next == null) return head;Base case for recursion: empty or single node list
ListNode slow = head, fast = head.next;Initialize pointers to find midpoint
while (fast != null && fast.next != null)Advance fast pointer twice as fast to find middle
slow.next = null;Break list into two halves by disconnecting at midpoint
#include <iostream>
using namespace std;

struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(nullptr) {}
};

ListNode* merge(ListNode* l1, ListNode* l2) {
    ListNode dummy(0);
    ListNode* tail = &dummy;
    while (l1 && l2) {
        if (l1->val < l2->val) {
            tail->next = l1;
            l1 = l1->next;
        } else {
            tail->next = l2;
            l2 = l2->next;
        }
        tail = tail->next;
    }
    tail->next = l1 ? l1 : l2;
    return dummy.next;
}

ListNode* sortList(ListNode* head) {
    if (!head || !head->next) return head;
    ListNode* slow = head;
    ListNode* fast = head->next;
    while (fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
    }
    ListNode* mid = slow->next;
    slow->next = nullptr;
    ListNode* left = sortList(head);
    ListNode* right = sortList(mid);
    return merge(left, right);
}

// Example usage:
// int main() {
//     ListNode* head = new ListNode(4);
//     head->next = new ListNode(2);
//     head->next->next = new ListNode(1);
//     head->next->next->next = new ListNode(3);
//     ListNode* sorted = sortList(head);
//     while (sorted) {
//         cout << sorted->val << " ";
//         sorted = sorted->next;
//     }
//     return 0;
// }
Line Notes
if (!head || !head->next) return head;Base case: list with 0 or 1 node is sorted
ListNode* slow = head; ListNode* fast = head->next;Initialize pointers to find midpoint
while (fast && fast->next)Move fast pointer twice as fast to find middle
slow->next = nullptr;Split list into two halves by breaking link
function ListNode(val, next = null) {
    this.val = val;
    this.next = next;
}

function merge(l1, l2) {
    const dummy = new ListNode(0);
    let tail = dummy;
    while (l1 && l2) {
        if (l1.val < l2.val) {
            tail.next = l1;
            l1 = l1.next;
        } else {
            tail.next = l2;
            l2 = l2.next;
        }
        tail = tail.next;
    }
    tail.next = l1 || l2;
    return dummy.next;
}

function sortList(head) {
    if (!head || !head.next) return head;
    let slow = head, fast = head.next;
    while (fast && fast.next) {
        slow = slow.next;
        fast = fast.next.next;
    }
    const mid = slow.next;
    slow.next = null;
    const left = sortList(head);
    const right = sortList(mid);
    return merge(left, right);
}

// Example usage:
// let head = new ListNode(4, new ListNode(2, new ListNode(1, new ListNode(3))));
// let sorted = sortList(head);
// while (sorted) {
//     console.log(sorted.val);
//     sorted = sorted.next;
// }
Line Notes
if (!head || !head.next) return head;Base case: empty or single node list is sorted
let slow = head, fast = head.next;Initialize pointers to find midpoint
while (fast && fast.next)Advance fast pointer twice as fast to find middle
slow.next = null;Split list into two halves by breaking link
⏱
Complexity
TimeO(n log n)
SpaceO(log n) due to recursion stack

Splitting and merging each level takes O(n), and there are O(log n) levels of recursion.

💡 For n=20, about 20*log2(20) ≈ 86 operations plus recursion overhead.
Interview Verdict: Accepted and meets time and space constraints except recursion stack

This is the standard approach for linked list sorting, efficient and commonly accepted in interviews.

🧠
Bottom-Up Iterative Merge Sort on Linked List
💡 This approach eliminates recursion by iteratively merging sublists of increasing size, achieving O(1) space complexity and avoiding stack overflow.

Intuition

Start by merging pairs of nodes (size=1), then merge pairs of sorted lists of size 2, then size 4, and so on until the entire list is sorted.

Algorithm

  1. Calculate the length of the linked list.
  2. Iteratively merge sublists of size 1, then 2, then 4, doubling each time until size >= length.
  3. For each pass, traverse the list and merge pairs of sublists of the current size.
  4. Return the fully sorted list after all passes.
💡 This approach is more complex to implement but uses constant space and is robust for large inputs.
</>
Code
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

def merge(l1, l2):
    dummy = ListNode(0)
    tail = dummy
    while l1 and l2:
        if l1.val < l2.val:
            tail.next = l1
            l1 = l1.next
        else:
            tail.next = l2
            l2 = l2.next
        tail = tail.next
    tail.next = l1 or l2
    return dummy.next

def sortList(head):
    if not head or not head.next:
        return head
    # Get length
    length = 0
    node = head
    while node:
        length += 1
        node = node.next
    dummy = ListNode(0, head)
    size = 1
    while size < length:
        cur = dummy.next
        tail = dummy
        while cur:
            left = cur
            right = split(left, size)
            cur = split(right, size)
            merged = merge(left, right)
            tail.next = merged
            while tail.next:
                tail = tail.next
        size <<= 1
    return dummy.next

def split(head, size):
    for i in range(size - 1):
        if head:
            head = head.next
        else:
            break
    if not head:
        return None
    second = head.next
    head.next = None
    return second

# Example usage:
# head = ListNode(4, ListNode(2, ListNode(1, ListNode(3))))
# sorted_head = sortList(head)
# while sorted_head:
#     print(sorted_head.val, end=' ')
#     sorted_head = sorted_head.next
Line Notes
length = 0Calculate total length to control merge passes
while size < length:Double sublist size each iteration
right = split(left, size)Split list into sublists of given size
tail.next = mergedAttach merged sorted sublist to result
class ListNode {
    int val;
    ListNode next;
    ListNode(int val) { this.val = val; this.next = null; }
}

public class Solution {
    private ListNode merge(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(0);
        ListNode tail = dummy;
        while (l1 != null && l2 != null) {
            if (l1.val < l2.val) {
                tail.next = l1;
                l1 = l1.next;
            } else {
                tail.next = l2;
                l2 = l2.next;
            }
            tail = tail.next;
        }
        tail.next = (l1 != null) ? l1 : l2;
        return dummy.next;
    }

    private ListNode split(ListNode head, int size) {
        for (int i = 1; head != null && i < size; i++) {
            head = head.next;
        }
        if (head == null) return null;
        ListNode second = head.next;
        head.next = null;
        return second;
    }

    public ListNode sortList(ListNode head) {
        if (head == null || head.next == null) return head;
        int length = 0;
        ListNode node = head;
        while (node != null) {
            length++;
            node = node.next;
        }
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        for (int size = 1; size < length; size <<= 1) {
            ListNode cur = dummy.next;
            ListNode tail = dummy;
            while (cur != null) {
                ListNode left = cur;
                ListNode right = split(left, size);
                cur = split(right, size);
                tail.next = merge(left, right);
                while (tail.next != null) tail = tail.next;
            }
        }
        return dummy.next;
    }

    // Example main method
    // public static void main(String[] args) {
    //     ListNode head = new ListNode(4);
    //     head.next = new ListNode(2);
    //     head.next.next = new ListNode(1);
    //     head.next.next.next = new ListNode(3);
    //     Solution sol = new Solution();
    //     ListNode sorted = sol.sortList(head);
    //     while (sorted != null) {
    //         System.out.print(sorted.val + " ");
    //         sorted = sorted.next;
    //     }
    // }
}
Line Notes
int length = 0;Calculate length of linked list for iteration control
for (int size = 1; size < length; size <<= 1)Double sublist size each pass
ListNode right = split(left, size);Split list into sublists of given size
tail.next = merge(left, right);Attach merged sorted sublist to result
#include <iostream>
using namespace std;

struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(nullptr) {}
};

ListNode* merge(ListNode* l1, ListNode* l2) {
    ListNode dummy(0);
    ListNode* tail = &dummy;
    while (l1 && l2) {
        if (l1->val < l2->val) {
            tail->next = l1;
            l1 = l1->next;
        } else {
            tail->next = l2;
            l2 = l2->next;
        }
        tail = tail->next;
    }
    tail->next = l1 ? l1 : l2;
    return dummy.next;
}

ListNode* split(ListNode* head, int size) {
    for (int i = 1; head && i < size; i++) {
        head = head->next;
    }
    if (!head) return nullptr;
    ListNode* second = head->next;
    head->next = nullptr;
    return second;
}

ListNode* sortList(ListNode* head) {
    if (!head || !head->next) return head;
    int length = 0;
    ListNode* node = head;
    while (node) {
        length++;
        node = node->next;
    }
    ListNode dummy(0);
    dummy.next = head;
    for (int size = 1; size < length; size <<= 1) {
        ListNode* cur = dummy.next;
        ListNode* tail = &dummy;
        while (cur) {
            ListNode* left = cur;
            ListNode* right = split(left, size);
            cur = split(right, size);
            tail->next = merge(left, right);
            while (tail->next) tail = tail->next;
        }
    }
    return dummy.next;
}

// Example usage:
// int main() {
//     ListNode* head = new ListNode(4);
//     head->next = new ListNode(2);
//     head->next->next = new ListNode(1);
//     head->next->next->next = new ListNode(3);
//     ListNode* sorted = sortList(head);
//     while (sorted) {
//         cout << sorted->val << " ";
//         sorted = sorted->next;
//     }
//     return 0;
// }
Line Notes
int length = 0;Calculate length of linked list for iteration control
for (int size = 1; size < length; size <<= 1)Double sublist size each pass
ListNode* right = split(left, size);Split list into sublists of given size
tail->next = merge(left, right);Attach merged sorted sublist to result
function ListNode(val, next = null) {
    this.val = val;
    this.next = next;
}

function merge(l1, l2) {
    const dummy = new ListNode(0);
    let tail = dummy;
    while (l1 && l2) {
        if (l1.val < l2.val) {
            tail.next = l1;
            l1 = l1.next;
        } else {
            tail.next = l2;
            l2 = l2.next;
        }
        tail = tail.next;
    }
    tail.next = l1 || l2;
    return dummy.next;
}

function split(head, size) {
    for (let i = 1; head && i < size; i++) {
        head = head.next;
    }
    if (!head) return null;
    const second = head.next;
    head.next = null;
    return second;
}

function sortList(head) {
    if (!head || !head.next) return head;
    let length = 0;
    let node = head;
    while (node) {
        length++;
        node = node.next;
    }
    const dummy = new ListNode(0, head);
    for (let size = 1; size < length; size <<= 1) {
        let cur = dummy.next;
        let tail = dummy;
        while (cur) {
            const left = cur;
            const right = split(left, size);
            cur = split(right, size);
            tail.next = merge(left, right);
            while (tail.next) tail = tail.next;
        }
    }
    return dummy.next;
}

// Example usage:
// let head = new ListNode(4, new ListNode(2, new ListNode(1, new ListNode(3))));
// let sorted = sortList(head);
// while (sorted) {
//     console.log(sorted.val);
//     sorted = sorted.next;
// }
Line Notes
let length = 0;Calculate length of linked list for iteration control
for (let size = 1; size < length; size <<= 1)Double sublist size each pass
const right = split(left, size);Split list into sublists of given size
tail.next = merge(left, right);Attach merged sorted sublist to result
⏱
Complexity
TimeO(n log n)
SpaceO(1) auxiliary space

Each pass merges pairs of sublists covering all nodes, doubling sublist size each pass, totaling O(log n) passes and O(n) work each.

💡 For n=20, about 86 operations with no recursion stack overhead, making it efficient for large inputs.
Interview Verdict: Accepted and optimal for in-place sorting without recursion

This approach is ideal for large inputs or environments where recursion depth is limited.

📊
All Approaches - One-Glance Tradeoffs
💡 In interviews, coding the recursive merge sort is usually best unless asked for iterative or space-optimized solutions.
ApproachTimeSpaceStack RiskReconstructUse In Interview
1. Brute ForceO(n log n)O(n)NoYesMention only - never code
2. Recursive Merge SortO(n log n)O(log n) recursion stackYes (deep recursion)YesCode this in most interviews
3. Bottom-Up Iterative Merge SortO(n log n)O(1) auxiliaryNoYesCode if asked for no recursion or optimal space
💼
Interview Strategy
💡 Use this guide to understand the problem deeply before interviews. Start with brute force to build intuition, then progress to recursive and iterative merge sort approaches.

How to Present

Clarify input/output and constraints.Describe brute force approach converting list to array and sorting.Explain recursive merge sort with fast/slow pointer splitting.Discuss iterative bottom-up merge sort to avoid recursion.Code the chosen approach and test with edge cases.

Time Allocation

Clarify: 3min → Approach: 5min → Code: 15min → Test: 7min. Total ~30min

What the Interviewer Tests

Understanding of linked list manipulation, ability to implement merge sort on linked list, handling edge cases, and optimizing for time and space.

Common Follow-ups

  • Can you sort the list in O(1) space? → Use bottom-up iterative merge sort.
  • What if the list is doubly linked? → Similar approach but easier splitting.
  • How to handle very large lists without recursion? → Use iterative approach.
  • Can you do quicksort on linked list? → Possible but less stable and efficient.
💡 These follow-ups test deeper understanding of space optimization, recursion limits, and alternative sorting methods.
🔍
Pattern Recognition

When to Use

1) Input is a singly linked list, 2) Need to sort in O(n log n) time, 3) In-place or O(1) space desired, 4) Random access not possible

Signature Phrases

'sort the linked list in ascending order''in-place sorting with O(n log n) time complexity'

NOT This Pattern When

Sorting arrays or using quicksort on linked lists (less stable and efficient)

Similar Problems

Merge Two Sorted Lists - merging technique used hereInsertion Sort List - alternative sorting method on linked list

Practice

(1/5)
1. You are given a sorted linked list where some elements appear more than once consecutively. The task is to remove all nodes that have duplicate numbers, leaving only distinct numbers from the original list. Which approach guarantees an optimal solution for this problem?
easy
A. Use a brute force nested loop to compare each node with all others and remove duplicates.
B. Use a single-pass iterative approach with a dummy head to skip all duplicates in one traversal.
C. Use a greedy approach that removes duplicates as soon as they are found without revisiting nodes.
D. Use dynamic programming to store counts of each value and then reconstruct the list.

Solution

  1. Step 1: Understand problem constraints

    The list is sorted, so duplicates appear consecutively, enabling a single pass to detect and skip duplicates.
  2. Step 2: Evaluate approaches

    Brute force nested loops are inefficient (O(n²)). Greedy removal without revisiting can leave some duplicates. DP is unnecessary and inefficient here. The single-pass iterative approach with a dummy head efficiently removes all duplicates in O(n) time.
  3. Final Answer:

    Option B -> Option B
  4. Quick Check:

    Single pass with dummy head handles duplicates correctly [OK]
Hint: Sorted list allows single-pass duplicate skipping [OK]
Common Mistakes:
  • Thinking greedy removal is enough
  • Using nested loops unnecessarily
  • Applying DP to a linked list problem
2. Given the following tail-recursive reversal code, what is the output printed when the input linked list is 1 -> 2 -> 3?
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

def reverseList(head):
    def helper(prev, curr):
        if not curr:
            return prev
        next_temp = curr.next
        curr.next = prev
        return helper(curr, next_temp)
    return helper(None, head)

head = ListNode(1, ListNode(2, ListNode(3)))
new_head = reverseList(head)
curr = new_head
while curr:
    print(curr.val, end=' ')
    curr = curr.next
print()
easy
A. 3 2 1
B. 1 2 3
C. 3 1 2
D. 2 3 1

Solution

  1. Step 1: Trace helper calls

    Initial call: helper(None, node1(1)) - next_temp = node2(2) - node1.next = None - call helper(node1, node2) Next call: helper(node1, node2) - next_temp = node3(3) - node2.next = node1 - call helper(node2, node3) Next call: helper(node2, node3) - next_temp = None - node3.next = node2 - call helper(node3, None) Next call: helper(node3, None) returns node3 as new head.
  2. Step 2: Print reversed list

    Starting from node3, print values: 3 -> 2 -> 1
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Reversal correctly outputs 3 2 1 [OK]
Hint: Pointers reversed during recursion descent [OK]
Common Mistakes:
  • Confusing original order with reversed output
  • Off-by-one in pointer updates
3. What is the time and space complexity of the optimal in-place iterative merge algorithm for merging two sorted linked lists of lengths n and m?
medium
A. Time: O(max(n, m)), Space: O(1)
B. Time: O(n·m), Space: O(1)
C. Time: O(n + m), Space: O(n + m)
D. Time: O(n + m), Space: O(1)

Solution

  1. Step 1: Analyze time complexity

    The algorithm traverses each node exactly once, either from l1 or l2, so total steps are proportional to n + m.
  2. Step 2: Analyze space complexity

    It rearranges pointers in-place without allocating new nodes or recursion stack, so auxiliary space is O(1).
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Linear time and constant space for in-place merge [OK]
Hint: In-place merge visits each node once, no extra space [OK]
Common Mistakes:
  • Confusing time as O(n·m) due to nested loops
  • Assuming recursion adds O(n + m) stack space
  • Thinking space is O(n + m) due to new list creation
4. Examine the following buggy code intended to remove duplicates from a sorted linked list. Which line contains the subtle bug that can cause an infinite loop or incorrect skipping of nodes?
medium
A. Line 5: current.next = current.next.next
B. Line 4: if current.val == current.next.val:
C. Line 7: current = current.next
D. Line 2: current = head

Solution

  1. Step 1: Understand pointer updates

    When duplicates are found, current.next is updated to skip duplicate nodes; current pointer stays to check next node.
  2. Step 2: Identify bug in pointer advancement

    If current pointer is moved unconditionally after removal, it may skip nodes or cause infinite loops. The else clause must move current only when no duplicate is removed.
  3. Final Answer:

    Option A -> Option A
  4. Quick Check:

    Incorrect pointer advancement causes skipping or infinite loops [OK]
Hint: Only advance current when no duplicate removed [OK]
Common Mistakes:
  • Advancing current pointer after removing duplicates
5. Suppose the problem is modified so that the linked list is circular (the last node points back to the head). Which of the following modifications to the recursive swapPairs function correctly handles swapping nodes in pairs without causing infinite recursion or losing nodes?
hard
A. Modify the base case to stop recursion when the next node is the head, breaking the cycle.
B. Add a visited set to track nodes and stop recursion when a node repeats.
C. Use the same recursive approach without changes; it naturally terminates on circular lists.
D. Convert the circular list to a linear list by breaking the cycle before swapping.

Solution

  1. Step 1: Understand circular list implications

    In a circular list, naive recursion never reaches a null node, causing infinite recursion.
  2. Step 2: Modify base case to detect cycle

    Stopping recursion when next node is the head prevents infinite loops and preserves all nodes.
  3. Step 3: Compare alternatives

    Using a visited set adds overhead; breaking cycle beforehand changes problem constraints; naive recursion fails.
  4. Final Answer:

    Option A -> Option A
  5. Quick Check:

    Base case adjusted for cycle detection prevents infinite recursion [OK]
Hint: Detect cycle in base case to stop recursion [OK]
Common Mistakes:
  • Ignoring cycle causes infinite recursion
  • Trying naive recursion without cycle check
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard