Facebook Pixel

725. Split Linked List in Parts

Problem Description

You are given the head of a singly linked list and an integer k. Your task is to split this linked list into exactly k consecutive parts.

The key requirements are:

  1. Equal Distribution: Each part should have approximately equal length. Specifically, no two parts should differ in size by more than 1 node.

  2. Order Preservation: The parts must maintain the original order of nodes from the input list. You cannot rearrange nodes.

  3. Size Priority: When the list cannot be divided evenly, the earlier parts should be larger. If you have extra nodes after dividing evenly, distribute them to the first few parts.

  4. Handling Small Lists: If the linked list has fewer than k nodes, some parts will be empty (null/None).

For example:

  • If you have a list with 10 nodes and k = 3, the parts would have sizes [4, 3, 3]
  • If you have a list with 5 nodes and k = 3, the parts would have sizes [2, 2, 1]
  • If you have a list with 3 nodes and k = 5, the parts would have sizes [1, 1, 1, null, null]

The function should return an array of k elements, where each element is the head of one part of the split linked list. Each part is itself a properly terminated linked list (the last node's next pointer is set to null).

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

To split a linked list into k parts as evenly as possible, we need to figure out how many nodes each part should contain. The natural approach is to think about division.

If we have n nodes total and need k parts, the base size of each part would be n // k (integer division). However, when n is not perfectly divisible by k, we'll have some leftover nodes given by n % k.

Where should these extra nodes go? According to the problem requirements, earlier parts should be larger or equal in size to later parts. This means we should distribute the extra nodes to the first few parts, giving each of them one additional node.

For example, if n = 10 and k = 3:

  • Base size: 10 // 3 = 3
  • Extra nodes: 10 % 3 = 1
  • Part sizes: The first part gets 3 + 1 = 4 nodes, and the remaining two parts get 3 nodes each

This leads us to the key insight: the first (n % k) parts will have size (n // k) + 1, and the remaining parts will have size (n // k).

Once we know the size of each part, the actual splitting becomes straightforward:

  1. First, traverse the list once to count the total number of nodes
  2. Calculate the base size and number of extra nodes
  3. For each part, traverse the appropriate number of nodes
  4. Cut the connection between the current part and the next part by setting the last node's next pointer to None
  5. Move to the beginning of the next part and repeat

The beauty of this approach is that it handles all edge cases naturally - if n < k, some parts will have size 0 and will remain as None in our result array.

Learn more about Linked List patterns.

Solution Approach

The implementation follows a two-pass approach to split the linked list into k parts.

Step 1: Count the total nodes

First, we traverse the entire linked list to count the total number of nodes n:

n = 0
cur = head
while cur:
    n += 1
    cur = cur.next

Step 2: Calculate part sizes

Using the divmod function, we calculate:

  • cnt: the base size of each part (n // k)
  • mod: the number of extra nodes (n % k)
cnt, mod = divmod(n, k)

This tells us that the first mod parts will have cnt + 1 nodes, while the remaining parts will have cnt nodes.

Step 3: Initialize the result array

We create an array of size k initialized with None values to store the head of each part:

ans = [None] * k

Step 4: Split the linked list

We iterate k times to create each part:

cur = head
for i in range(k):
    if cur is None:
        break
    ans[i] = cur
    m = cnt + int(i < mod)
    for _ in range(1, m):
        cur = cur.next
    nxt = cur.next
    cur.next = None
    cur = nxt

For each part i:

  • We store the current node as the head of this part: ans[i] = cur
  • We calculate the size of this part: m = cnt + int(i < mod)
    • If i < mod, this part gets an extra node, so size is cnt + 1
    • Otherwise, size is cnt
  • We traverse m - 1 nodes (starting from 1 since we're already at the first node)
  • We save the next node before cutting the connection: nxt = cur.next
  • We terminate the current part by setting: cur.next = None
  • We move to the beginning of the next part: cur = nxt

The if cur is None: break check handles the case where we have fewer than k nodes, ensuring we don't try to process non-existent nodes.

Time and Space Complexity:

  • Time: O(n + k) where n is the number of nodes. We traverse the list once to count nodes and once more to split it.
  • Space: O(k) for the result array storing the heads of each part.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through splitting a linked list 1 -> 2 -> 3 -> 4 -> 5 into k = 3 parts.

Step 1: Count nodes We traverse the list and count n = 5 nodes.

Step 2: Calculate part sizes

  • Base size: cnt = 5 // 3 = 1
  • Extra nodes: mod = 5 % 3 = 2

This means the first 2 parts get 1 + 1 = 2 nodes each, and the last part gets 1 node.

Step 3: Create result array Initialize ans = [None, None, None]

Step 4: Split the list

Part 0 (i=0):

  • Current position: node 1
  • Store head: ans[0] = node 1
  • Size calculation: m = 1 + 1 = 2 (since 0 < 2)
  • Traverse 1 more node to reach node 2
  • Save next: nxt = node 3
  • Cut connection: node 2.next = None
  • Move forward: cur = node 3
  • Part 0: 1 -> 2 -> None

Part 1 (i=1):

  • Current position: node 3
  • Store head: ans[1] = node 3
  • Size calculation: m = 1 + 1 = 2 (since 1 < 2)
  • Traverse 1 more node to reach node 4
  • Save next: nxt = node 5
  • Cut connection: node 4.next = None
  • Move forward: cur = node 5
  • Part 1: 3 -> 4 -> None

Part 2 (i=2):

  • Current position: node 5
  • Store head: ans[2] = node 5
  • Size calculation: m = 1 + 0 = 1 (since 2 ≮ 2)
  • No traversal needed (already at the only node)
  • Save next: nxt = None
  • Cut connection: node 5.next = None (already None)
  • Move forward: cur = None
  • Part 2: 5 -> None

Final Result: [1->2, 3->4, 5] representing three separate linked lists with sizes [2, 2, 1].

Solution Implementation

1# Definition for singly-linked list.
2# class ListNode:
3#     def __init__(self, val=0, next=None):
4#         self.val = val
5#         self.next = next
6
7from typing import List, Optional
8
9class Solution:
10    def splitListToParts(
11        self, head: Optional[ListNode], k: int
12    ) -> List[Optional[ListNode]]:
13        # Step 1: Calculate the total length of the linked list
14        total_length = 0
15        current = head
16        while current:
17            total_length += 1
18            current = current.next
19      
20        # Step 2: Calculate base size and number of parts that need an extra node
21        # base_size: minimum nodes each part should have
22        # extra_parts: number of parts that should have one extra node
23        base_size, extra_parts = divmod(total_length, k)
24      
25        # Step 3: Initialize result array with k None values
26        result = [None] * k
27      
28        # Step 4: Split the linked list into k parts
29        current = head
30        for part_index in range(k):
31            # If no more nodes left, remaining parts stay None
32            if current is None:
33                break
34          
35            # Store the head of current part
36            result[part_index] = current
37          
38            # Calculate size of current part
39            # First 'extra_parts' parts get one extra node
40            current_part_size = base_size + (1 if part_index < extra_parts else 0)
41          
42            # Traverse to the last node of current part
43            for _ in range(1, current_part_size):
44                current = current.next
45          
46            # Disconnect current part from the rest of the list
47            next_part_head = current.next
48            current.next = None
49          
50            # Move to the beginning of next part
51            current = next_part_head
52      
53        return result
54
1/**
2 * Definition for singly-linked list.
3 * public class ListNode {
4 *     int val;
5 *     ListNode next;
6 *     ListNode() {}
7 *     ListNode(int val) { this.val = val; }
8 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
9 * }
10 */
11class Solution {
12    public ListNode[] splitListToParts(ListNode head, int k) {
13        // Step 1: Calculate the total length of the linked list
14        int totalLength = 0;
15        for (ListNode current = head; current != null; current = current.next) {
16            totalLength++;
17        }
18      
19        // Step 2: Calculate the base size of each part and the number of parts that need an extra node
20        int basePartSize = totalLength / k;  // Minimum size of each part
21        int extraNodes = totalLength % k;     // Number of parts that will have one extra node
22      
23        // Step 3: Initialize the result array to store the head of each part
24        ListNode[] result = new ListNode[k];
25        ListNode current = head;
26      
27        // Step 4: Split the list into k parts
28        for (int i = 0; i < k && current != null; i++) {
29            // Set the head of the current part
30            result[i] = current;
31          
32            // Calculate the size of the current part
33            // Parts with index < extraNodes get one additional node
34            int currentPartSize = basePartSize + (i < extraNodes ? 1 : 0);
35          
36            // Traverse to the last node of the current part
37            for (int j = 1; j < currentPartSize; j++) {
38                current = current.next;
39            }
40          
41            // Disconnect the current part from the rest of the list
42            ListNode nextPartHead = current.next;
43            current.next = null;
44            current = nextPartHead;
45        }
46      
47        return result;
48    }
49}
50
1/**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 *     int val;
5 *     ListNode *next;
6 *     ListNode() : val(0), next(nullptr) {}
7 *     ListNode(int x) : val(x), next(nullptr) {}
8 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
9 * };
10 */
11class Solution {
12public:
13    vector<ListNode*> splitListToParts(ListNode* head, int k) {
14        // Step 1: Calculate the total length of the linked list
15        int totalLength = 0;
16        for (ListNode* current = head; current != nullptr; current = current->next) {
17            ++totalLength;
18        }
19      
20        // Step 2: Calculate base size for each part and number of parts that need an extra node
21        int basePartSize = totalLength / k;  // Base size of each part
22        int extraNodes = totalLength % k;    // Number of parts that will have one extra node
23      
24        // Step 3: Initialize result vector with k null pointers
25        vector<ListNode*> result(k, nullptr);
26      
27        // Step 4: Split the linked list into k parts
28        ListNode* current = head;
29        for (int partIndex = 0; partIndex < k && current != nullptr; ++partIndex) {
30            // Set the head of the current part
31            result[partIndex] = current;
32          
33            // Calculate the size of the current part
34            // First 'extraNodes' parts will have size (basePartSize + 1)
35            // Remaining parts will have size basePartSize
36            int currentPartSize = basePartSize + (partIndex < extraNodes ? 1 : 0);
37          
38            // Traverse to the last node of the current part
39            for (int nodeCount = 1; nodeCount < currentPartSize; ++nodeCount) {
40                current = current->next;
41            }
42          
43            // Disconnect the current part from the rest of the list
44            ListNode* nextPartHead = current->next;
45            current->next = nullptr;
46          
47            // Move to the beginning of the next part
48            current = nextPartHead;
49        }
50      
51        return result;
52    }
53};
54
1/**
2 * Definition for singly-linked list.
3 * class ListNode {
4 *     val: number
5 *     next: ListNode | null
6 *     constructor(val?: number, next?: ListNode | null) {
7 *         this.val = (val===undefined ? 0 : val)
8 *         this.next = (next===undefined ? null : next)
9 *     }
10 * }
11 */
12
13/**
14 * Splits a linked list into k consecutive parts.
15 * Each part should have equal size if possible. The first few parts may have one extra node.
16 * 
17 * @param head - The head of the linked list to split
18 * @param k - The number of parts to split the list into
19 * @returns An array of k linked list heads representing the split parts
20 */
21function splitListToParts(head: ListNode | null, k: number): Array<ListNode | null> {
22    // Calculate the total length of the linked list
23    let totalLength: number = 0;
24    for (let currentNode: ListNode | null = head; currentNode !== null; currentNode = currentNode.next) {
25        totalLength++;
26    }
27  
28    // Calculate base size of each part and how many parts need an extra node
29    const basePartSize: number = Math.floor(totalLength / k);
30    const partsWithExtraNode: number = totalLength % k;
31  
32    // Initialize result array with k null elements
33    const result: Array<ListNode | null> = Array(k).fill(null);
34  
35    let currentNode: ListNode | null = head;
36  
37    // Split the list into k parts
38    for (let partIndex: number = 0; partIndex < k && currentNode !== null; partIndex++) {
39        // Set the head of current part
40        result[partIndex] = currentNode;
41      
42        // Calculate size of current part (base size + 1 if it needs an extra node)
43        const currentPartSize: number = basePartSize + (partIndex < partsWithExtraNode ? 1 : 0);
44      
45        // Traverse to the last node of current part
46        for (let nodeIndex: number = 1; nodeIndex < currentPartSize; nodeIndex++) {
47            currentNode = currentNode.next!;
48        }
49      
50        // Disconnect current part from the rest of the list
51        const nextPartHead: ListNode | null = currentNode.next;
52        currentNode.next = null;
53        currentNode = nextPartHead;
54    }
55  
56    return result;
57}
58

Time and Space Complexity

Time Complexity: O(n)

The algorithm consists of two main phases:

  1. First traversal to count the total number of nodes: This takes O(n) time where n is the length of the linked list.
  2. Second traversal to split the list into k parts: This also takes O(n) time as we visit each node exactly once to either assign it to a part or disconnect it from the next part.

Since both phases are sequential and each takes O(n) time, the overall time complexity is O(n) + O(n) = O(n).

Space Complexity: O(k)

The space complexity comes from:

  • The answer array ans which stores k references to the head nodes of each part: O(k) space
  • A few constant extra variables (n, cur, cnt, mod, m, nxt, i): O(1) space

Therefore, the total space complexity is O(k) + O(1) = O(k), where k is the number of parts to split the list into.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Off-by-One Error When Traversing Each Part

The Problem: A frequent mistake is incorrectly handling the loop that traverses to the end of each part. Developers often write:

# INCORRECT - traverses too many nodes
for _ in range(current_part_size):
    current = current.next

This traverses current_part_size nodes forward from the current position, but since we're already at the first node of the part, we end up going one node too far.

Why It Happens: The confusion arises because current already points to the first node of the part when we start processing it. If a part should have 3 nodes and we're already at node 1, we only need to move forward 2 times to reach node 3, not 3 times.

The Solution: Start the loop from 1 instead of 0:

# CORRECT - traverses exactly to the last node of the part
for _ in range(1, current_part_size):
    current = current.next

Pitfall 2: Not Handling Edge Cases with Empty Parts

The Problem: When the linked list has fewer nodes than k, some parts will be empty. Forgetting to check if current is None before processing can lead to AttributeError:

# INCORRECT - will crash if current is None
for part_index in range(k):
    result[part_index] = current  # current might be None
    current_part_size = base_size + (1 if part_index < extra_parts else 0)
    for _ in range(1, current_part_size):
        current = current.next  # AttributeError if current is None!

The Solution: Add a check for None before processing each part:

# CORRECT - safely handles when we run out of nodes
for part_index in range(k):
    if current is None:
        break  # Remaining parts will stay None
    result[part_index] = current
    # ... rest of the logic

Pitfall 3: Forgetting to Disconnect Parts

The Problem: A subtle but critical error is forgetting to set current.next = None after identifying the last node of each part. Without this, all parts would still be connected, and each part would contain all remaining nodes instead of just its allocated portion.

# INCORRECT - parts remain connected
current = head
for part_index in range(k):
    result[part_index] = current
    current_part_size = base_size + (1 if part_index < extra_parts else 0)
    for _ in range(1, current_part_size):
        current = current.next
    current = current.next  # Just moving forward without disconnecting!

The Solution: Always disconnect the current part from the rest before moving forward:

# CORRECT - properly disconnects each part
next_part_head = current.next  # Save the next part's head
current.next = None            # Disconnect current part
current = next_part_head        # Move to next part

Pitfall 4: Integer Division Confusion

The Problem: In some programming languages or Python 2, division might not work as expected:

# Potentially problematic in Python 2 or other languages
base_size = total_length / k  # Might give float instead of int

The Solution: Use divmod() which clearly returns integer quotient and remainder, or use floor division:

# CORRECT - clear integer division
base_size, extra_parts = divmod(total_length, k)
# OR
base_size = total_length // k
extra_parts = total_length % k
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Which technique can we use to find the middle of a linked list?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More