LeetCode 2677 - Chunk Array

This problem asks us to split an input array into smaller subarrays, commonly called chunks, where each chunk has a fixed maximum size.

LeetCode Problem 2677

Difficulty: 🟢 Easy
Topics:

Solution

Problem Understanding

This problem asks us to split an input array into smaller subarrays, commonly called chunks, where each chunk has a fixed maximum size.

In other words, given an array arr and an integer size, we need to partition the array into consecutive groups such that each subarray contains at most size elements. Every element from the original array must appear exactly once and remain in the same relative order.

For example, if arr = [1,2,3,4,5] and size = 2, we divide the array into groups of two elements:

[1,2] [3,4] [5]

The last chunk is allowed to contain fewer than size elements when the array length is not evenly divisible by size.

The input consists of:

  • arr, the array we want to split
  • size, the desired size of each chunk

The output is a new two dimensional array where each inner array represents one chunk.

The constraints indicate that:

  • arr.length can be as large as 10^5, meaning we should aim for a linear time solution.
  • size is always valid, between 1 and arr.length + 1.
  • The array may be empty in practice, despite the listed constraints having a discrepancy with Example 4.

Because the array can become large, repeatedly shifting or copying unnecessary data could hurt performance. A good solution should process elements efficiently in one pass.

Several important edge cases are worth identifying early.

If size = 1, every element becomes its own chunk. For example:

[1,2,3] → [[1],[2],[3]]

If size > arr.length, then the entire array should become a single chunk because there are not enough elements to form multiple groups.

If arr is empty, the correct result is simply an empty array.

If arr.length is not divisible by size, the last chunk will contain fewer elements than the others, and the implementation must handle this correctly.

Approaches

Brute Force Approach

A straightforward way to solve this problem is to repeatedly extract chunks from the array using a loop and manually build each subarray element by element.

For every chunk, we could create an empty temporary list and append up to size elements into it. Once the chunk reaches the desired size, or we run out of elements, we append that chunk to the final answer.

This approach is correct because every element is processed exactly once and assigned to exactly one chunk in the same order it originally appeared.

However, if implemented naively using operations like repeatedly removing elements from the front of the array, such as pop(0) in Python or slice shifting, performance can degrade significantly. Removing from the front of an array typically costs O(n) because all remaining elements must be shifted.

Optimal Approach

The key observation is that chunk boundaries are predictable.

Instead of manually moving elements one at a time, we can step through the array in increments of size and directly take slices.

For every index i:

0, size, 2 * size, 3 * size, ...

we take:

arr[i:i+size]

This automatically produces a chunk of at most size elements.

The slicing operation naturally handles the last chunk, even if fewer than size elements remain. For example:

arr[3:6]

on an array with only five elements simply returns the remaining two items.

This approach is efficient, easy to understand, and runs in linear time.

Approach Time Complexity Space Complexity Notes
Brute Force O(n²) O(n) Repeated front removals can force shifting of elements
Optimal O(n) O(n) Iterates in chunk-sized steps and uses slicing

Algorithm Walkthrough

Optimal Algorithm

  1. Initialize an empty result array that will store all chunks.
  2. Iterate through the array using an index that increases by size each time.

Instead of moving one element at a time, we jump directly to the start of each chunk. This reduces unnecessary work and aligns perfectly with the chunking requirement. 3. For each starting index i, extract a slice:

arr[i:i+size]

This creates a subarray containing at most size elements. 4. Append the extracted chunk to the result array. 5. Continue until all elements have been processed. 6. Return the completed result array.

Why it works

The algorithm works because every chunk starts at an index that is a multiple of size. Since the loop advances by exactly size each time, no element is skipped or processed twice.

The slice arr[i:i+size] guarantees that:

  • The chunk contains at most size elements.
  • Elements remain in their original order.
  • The final chunk automatically contains the leftover elements if the array length is not divisible by size.

Thus, every element is placed into exactly one valid chunk, producing the correct output.

Python Solution

from typing import List, Any

class Solution:
    def chunk(self, arr: List[Any], size: int) -> List[List[Any]]:
        result = []

        for index in range(0, len(arr), size):
            result.append(arr[index:index + size])

        return result

This implementation follows the optimal algorithm directly.

We first initialize result, which stores the chunked subarrays.

The range(0, len(arr), size) loop advances in increments of size, meaning each iteration starts exactly where the next chunk should begin.

For each starting position, we take:

arr[index:index + size]

Python slicing is especially convenient because it automatically stops at the array boundary. This means we do not need special logic for the last chunk.

Finally, after processing all chunk boundaries, we return the completed result.

Go Solution

func chunk(arr []interface{}, size int) [][]interface{} {
	result := [][]interface{}{}

	for i := 0; i < len(arr); i += size {
		end := i + size
		if end > len(arr) {
			end = len(arr)
		}

		result = append(result, arr[i:end])
	}

	return result
}

The Go implementation is conceptually identical to the Python solution, but Go requires explicit bounds handling.

Unlike Python slicing, Go does not allow slicing beyond array bounds. Because of this, we compute:

end := i + size

and clamp it to len(arr) when necessary.

Go slices are lightweight views into arrays, so arr[i:end] efficiently creates each chunk without manually copying elements.

The function returns an empty slice when the input array is empty, which correctly matches the expected behavior.

Worked Examples

Example 1

Input:

arr = [1,2,3,4,5]
size = 1

Since size = 1, each element forms its own chunk.

Iteration Index Slice Result
1 0 [1] [[1]]
2 1 [2] [[1],[2]]
3 2 [3] [[1],[2],[3]]
4 3 [4] [[1],[2],[3],[4]]
5 4 [5] [[1],[2],[3],[4],[5]]

Final output:

[[1],[2],[3],[4],[5]]

Example 2

Input:

arr = [1,9,6,3,2]
size = 3

We process the array in jumps of three.

Iteration Index Slice Result
1 0 [1,9,6] [[1,9,6]]
2 3 [3,2] [[1,9,6],[3,2]]

The final chunk contains only two elements because the array ends.

Final output:

[[1,9,6],[3,2]]

Example 3

Input:

arr = [8,5,3,2,6]
size = 6

Since size exceeds the array length, the whole array becomes one chunk.

Iteration Index Slice Result
1 0 [8,5,3,2,6] [[8,5,3,2,6]]

Final output:

[[8,5,3,2,6]]

Example 4

Input:

arr = []
size = 1

The loop never executes because the array is empty.

Iteration Index Slice Result
None None None []

Final output:

[]

Complexity Analysis

Measure Complexity Explanation
Time O(n) Each element is processed exactly once across all slices
Space O(n) The output stores all elements in chunked form

The algorithm runs in linear time because every element appears in exactly one chunk. Although slicing creates subarrays, the total number of copied elements across all slices equals n, keeping the total cost linear.

The space complexity is also O(n) because we must store the chunked output, which collectively contains all original elements.

Test Cases

solution = Solution()

# Example cases
assert solution.chunk([1, 2, 3, 4, 5], 1) == [[1], [2], [3], [4], [5]]  # size = 1
assert solution.chunk([1, 9, 6, 3, 2], 3) == [[1, 9, 6], [3, 2]]  # uneven final chunk
assert solution.chunk([8, 5, 3, 2, 6], 6) == [[8, 5, 3, 2, 6]]  # size > length
assert solution.chunk([], 1) == []  # empty array

# Boundary cases
assert solution.chunk([1], 1) == [[1]]  # single element
assert solution.chunk([1, 2], 2) == [[1, 2]]  # exact fit
assert solution.chunk([1, 2, 3, 4], 2) == [[1, 2], [3, 4]]  # perfectly divisible

# Uneven division
assert solution.chunk([1, 2, 3, 4, 5], 2) == [[1, 2], [3, 4], [5]]  # leftover chunk

# Large chunk size
assert solution.chunk([1, 2, 3], 4) == [[1, 2, 3]]  # size larger than array

# Mixed values
assert solution.chunk([1, "a", True, None], 2) == [[1, "a"], [True, None]]  # heterogeneous values
Test Why
[1,2,3,4,5], size=1 Verifies every element becomes its own chunk
[1,9,6,3,2], size=3 Validates incomplete final chunk
[8,5,3,2,6], size=6 Ensures large chunk size returns one subarray
[], size=1 Confirms empty input handling
[1], size=1 Smallest non empty case
[1,2], size=2 Exact division into one chunk
[1,2,3,4], size=2 Perfectly divisible case
[1,2,3,4,5], size=2 Tests remainder handling
[1,2,3], size=4 Chunk size exceeds length
Mixed value array Confirms generic handling of arbitrary values

Edge Cases

Empty Array

An empty array can easily be overlooked because many implementations assume at least one element exists. A buggy solution might attempt to process indices that do not exist.

This implementation handles it naturally because:

range(0, len(arr), size)

becomes:

range(0, 0, size)

which executes zero iterations and returns an empty result.

Chunk Size Larger Than Array Length

When size exceeds the array length, an incorrect implementation might attempt multiple invalid slices or accidentally create empty chunks.

This implementation correctly handles the case because the first slice:

arr[0:size]

simply returns the entire array.

No additional iterations occur.

Array Length Not Divisible by Chunk Size

This is one of the easiest places for off by one errors.

For example:

arr = [1,2,3,4,5]
size = 2

A faulty implementation could accidentally discard the last element or create an incorrect chunk size.

Here, Python slicing safely handles the remainder:

arr[4:6]

returns:

[5]

which ensures leftover elements are preserved correctly.

Chunk Size Equal to One

When size = 1, every element should become an independent subarray.

Some implementations may overcomplicate this case or accidentally group elements together.

This solution naturally handles it because the loop increments by one, producing slices of length one at every position.