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.
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 splitsize, 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.lengthcan be as large as10^5, meaning we should aim for a linear time solution.sizeis always valid, between1andarr.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
- Initialize an empty result array that will store all chunks.
- Iterate through the array using an index that increases by
sizeeach 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
sizeelements. - 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.