2677. Chunk Array
Problem Description
This problem asks you to divide an array into smaller chunks of a specified size.
Given an input array arr
and a chunk size size
, you need to create a new array where:
- Each element is a subarray containing elements from the original array
- Each subarray should have length equal to
size
- The last subarray may have fewer elements if the total array length isn't perfectly divisible by
size
For example:
- If
arr = [1, 2, 3, 4, 5]
andsize = 2
, the result would be[[1, 2], [3, 4], [5]]
- If
arr = [1, 2, 3, 4, 5, 6]
andsize = 3
, the result would be[[1, 2, 3], [4, 5, 6]]
The solution iterates through the array in steps of size
, using the slice
method to extract chunks. Starting at index i
, it takes elements from i
to i + size
. The loop increments by size
each iteration (i += size
), ensuring non-overlapping chunks. When the remaining elements are fewer than size
, slice
automatically handles this by returning only the available elements.
Intuition
The key insight is to think of the array as a continuous sequence that needs to be "cut" at regular intervals. Imagine you have a long piece of bread that you want to cut into equal slices of a specific width.
We need to process elements in groups of size
. The natural approach is to use a loop that jumps by size
positions each time, rather than moving one element at a time. This way, we directly land at the starting position of each new chunk.
For each chunk, we need to extract size
consecutive elements starting from our current position. The slice(start, end)
method is perfect for this - it extracts elements from index start
up to (but not including) end
. So arr.slice(i, i + size)
gives us exactly size
elements starting at position i
.
The beauty of this approach is that slice
handles edge cases automatically. When we reach the end of the array and there are fewer than size
elements remaining, slice
doesn't throw an error - it simply returns whatever elements are available. For instance, if we're at index 8 in a 10-element array and ask for slice(8, 11)
, it returns elements at indices 8 and 9 only.
By incrementing our loop counter by size
(i += size
), we ensure each element appears in exactly one chunk, and we process the entire array efficiently in O(n)
time.
Solution Approach
The implementation uses a straightforward iterative approach with array slicing:
-
Initialize Result Array: Create an empty array
ans
to store the chunked subarrays. -
Loop Through Original Array: Use a for loop with a special increment pattern:
- Initialize
i = 0
as the starting index - Store
n = arr.length
for efficiency (avoiding repeated length calculations) - Increment by
size
each iteration (i += size
) to jump to the start of each new chunk
- Initialize
-
Extract Chunks: In each iteration:
- Use
arr.slice(i, i + size)
to extract elements from indexi
toi + size - 1
- This creates a new subarray containing at most
size
elements - Push this subarray into the result array
ans
- Use
-
Handle Edge Cases: The
slice
method automatically handles the last chunk:- If
i + size
exceeds the array length,slice
returns only the remaining elements - No special logic needed for the last partial chunk
- If
Time Complexity: O(n)
where n
is the length of the input array, as we visit each element exactly once.
Space Complexity: O(n)
for storing the result array, which contains all original elements reorganized into chunks.
The algorithm processes chunks sequentially from left to right, making it intuitive and efficient. Each chunk is created independently using slice
, which creates a shallow copy of the selected elements.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the algorithm with arr = [1, 2, 3, 4, 5, 6, 7]
and size = 3
.
Initial Setup:
ans = []
(empty result array)n = 7
(length of array)i = 0
(starting position)
Iteration 1 (i = 0):
- Extract:
arr.slice(0, 3)
→[1, 2, 3]
- Push
[1, 2, 3]
toans
- Result so far:
ans = [[1, 2, 3]]
- Increment:
i = 0 + 3 = 3
Iteration 2 (i = 3):
- Extract:
arr.slice(3, 6)
→[4, 5, 6]
- Push
[4, 5, 6]
toans
- Result so far:
ans = [[1, 2, 3], [4, 5, 6]]
- Increment:
i = 3 + 3 = 6
Iteration 3 (i = 6):
- Extract:
arr.slice(6, 9)
→[7]
- Note: Even though we request up to index 9, only index 6 exists, so
slice
returns just[7]
- Note: Even though we request up to index 9, only index 6 exists, so
- Push
[7]
toans
- Result so far:
ans = [[1, 2, 3], [4, 5, 6], [7]]
- Increment:
i = 6 + 3 = 9
Loop Termination:
- Condition check:
i < n
→9 < 7
is false - Loop ends
Final Result: [[1, 2, 3], [4, 5, 6], [7]]
The array of 7 elements has been successfully divided into chunks of size 3, with the last chunk containing only 1 element (the remainder).
Solution Implementation
1def chunk(arr, size):
2 """
3 Splits an array into chunks of specified size
4
5 Args:
6 arr: The input array to be chunked
7 size: The size of each chunk
8
9 Returns:
10 A 2D array where each sub-array contains at most 'size' elements
11 """
12 # Initialize result list to store chunks
13 result = []
14
15 # Get the length of input array for efficiency
16 array_length = len(arr)
17
18 # Iterate through the array in steps of 'size'
19 for start_index in range(0, array_length, size):
20 # Extract a slice from current position to current position + size
21 # Python slicing automatically handles cases where end_index exceeds array length
22 current_chunk = arr[start_index:start_index + size]
23
24 # Add the current chunk to the result list
25 result.append(current_chunk)
26
27 return result
28
1/**
2 * Splits an array into chunks of specified size
3 * @param arr - The input array to be chunked
4 * @param size - The size of each chunk
5 * @return A 2D array where each sub-array contains at most 'size' elements
6 */
7public static Object[][] chunk(Object[] arr, int size) {
8 // Initialize a list to store chunks dynamically
9 List<Object[]> resultList = new ArrayList<>();
10
11 // Get the length of input array for efficiency
12 int arrayLength = arr.length;
13
14 // Iterate through the array in steps of 'size'
15 for (int startIndex = 0; startIndex < arrayLength; startIndex += size) {
16 // Calculate the end index for the current chunk
17 // Math.min ensures we don't exceed array bounds
18 int endIndex = Math.min(startIndex + size, arrayLength);
19
20 // Calculate the size of the current chunk
21 int chunkSize = endIndex - startIndex;
22
23 // Create a new array for the current chunk
24 Object[] currentChunk = new Object[chunkSize];
25
26 // Copy elements from the original array to the current chunk
27 System.arraycopy(arr, startIndex, currentChunk, 0, chunkSize);
28
29 // Add the current chunk to the result list
30 resultList.add(currentChunk);
31 }
32
33 // Convert the list to a 2D array and return
34 return resultList.toArray(new Object[resultList.size()][]);
35}
36
1#include <vector>
2#include <algorithm>
3
4/**
5 * Splits a vector into chunks of specified size
6 * @param arr - The input vector to be chunked
7 * @param size - The size of each chunk
8 * @returns A 2D vector where each sub-vector contains at most 'size' elements
9 */
10template<typename T>
11std::vector<std::vector<T>> chunk(const std::vector<T>& arr, int size) {
12 // Initialize result vector to store chunks
13 std::vector<std::vector<T>> result;
14
15 // Get the length of input vector for efficiency
16 int arrayLength = arr.size();
17
18 // Iterate through the vector in steps of 'size'
19 for (int startIndex = 0; startIndex < arrayLength; startIndex += size) {
20 // Calculate the end index for current chunk
21 // Use min to ensure we don't exceed array bounds
22 int endIndex = std::min(startIndex + size, arrayLength);
23
24 // Create current chunk using iterators
25 // Constructor creates a new vector from the range [begin, end)
26 std::vector<T> currentChunk(arr.begin() + startIndex, arr.begin() + endIndex);
27
28 // Add the current chunk to the result vector
29 result.push_back(currentChunk);
30 }
31
32 return result;
33}
34
1/**
2 * Splits an array into chunks of specified size
3 * @param arr - The input array to be chunked
4 * @param size - The size of each chunk
5 * @returns A 2D array where each sub-array contains at most 'size' elements
6 */
7function chunk(arr: any[], size: number): any[][] {
8 // Initialize result array to store chunks
9 const result: any[][] = [];
10
11 // Get the length of input array for efficiency
12 const arrayLength: number = arr.length;
13
14 // Iterate through the array in steps of 'size'
15 for (let startIndex: number = 0; startIndex < arrayLength; startIndex += size) {
16 // Extract a slice from current position to current position + size
17 // slice() automatically handles cases where endIndex exceeds array length
18 const currentChunk: any[] = arr.slice(startIndex, startIndex + size);
19
20 // Add the current chunk to the result array
21 result.push(currentChunk);
22 }
23
24 return result;
25}
26
Time and Space Complexity
Time Complexity: O(n)
The function iterates through the array once with a loop that runs from i = 0
to i < n
with increments of size
. The number of iterations is ⌈n/size⌉
where n
is the length of the input array. Inside each iteration, arr.slice(i, i + size)
is called, which takes O(min(size, n-i))
time to create a subarray. Since we're creating chunks of size size
(except possibly the last chunk), the total time complexity is:
- Number of chunks:
⌈n/size⌉
- Time per chunk:
O(size)
for most chunks - Total:
⌈n/size⌉ * O(size) = O(n)
Space Complexity: O(n)
The space complexity consists of:
- The output array
ans
which stores references to all the chunks:O(⌈n/size⌉)
for the array structure - The actual chunked subarrays created by
slice()
: Each element from the original array appears exactly once in the chunked subarrays, totalingO(n)
space - Total space:
O(n)
for storing all elements in the new chunked format
Note that this analysis excludes the input array from space complexity considerations as it's provided as input. The auxiliary space used is proportional to the input size since we're creating new arrays that collectively contain all elements from the original array.
Common Pitfalls
1. Modifying the Original Array Instead of Creating New Chunks
A common mistake is trying to use references or views instead of creating actual copies of the chunks, which can lead to unexpected behavior if the original array is modified later.
Problematic approach:
def chunk_wrong(arr, size):
result = []
for i in range(0, len(arr), size):
# This might cause issues in some contexts
result.append(arr[i:i+size]) # Still creates copies in Python, but conceptually wrong
return result
Better approach: Ensure you're creating independent chunks. In Python, slicing creates new lists by default, but be explicit about your intent:
def chunk(arr, size):
result = []
for i in range(0, len(arr), size):
# Explicitly create a new list for each chunk
chunk = list(arr[i:i+size]) # Makes intent clear
result.append(chunk)
return result
2. Not Handling Edge Cases for Invalid Input
Failing to validate the size
parameter can cause runtime errors or infinite loops.
Common issues:
size <= 0
: Would cause an infinite loop or ValueErrorsize
is not an integer- Empty array input
Solution with validation:
def chunk(arr, size):
# Handle edge cases
if not arr or size <= 0:
return [] if size <= 0 else [[]]
# Ensure size is an integer
size = int(size)
result = []
for i in range(0, len(arr), size):
result.append(arr[i:i+size])
return result
3. Off-by-One Errors in Manual Index Management
When implementing without using Python's convenient range function, developers often make indexing mistakes:
Error-prone approach:
def chunk_manual(arr, size):
result = []
i = 0
while i < len(arr):
# Easy to mess up the end index calculation
end = min(i + size - 1, len(arr) - 1) # Wrong! This would miss elements
result.append(arr[i:end]) # This would not include the element at 'end'
i += size
return result
Correct manual approach:
def chunk_manual(arr, size):
result = []
i = 0
while i < len(arr):
# Correct: slice end index is exclusive
end = min(i + size, len(arr))
result.append(arr[i:end])
i += size
return result
4. Memory Inefficiency with Large Arrays
For very large arrays, creating all chunks at once can be memory-intensive. Consider using a generator for memory efficiency:
Memory-efficient solution:
def chunk_generator(arr, size):
"""Returns a generator instead of creating all chunks at once"""
for i in range(0, len(arr), size):
yield arr[i:i+size]
# Usage:
# for chunk in chunk_generator(large_array, 1000):
# process(chunk) # Process one chunk at a time
5. Type Confusion with Different Data Structures
The solution assumes a list-like structure. Using it with strings or other iterables without consideration can lead to unexpected results:
# String input might not behave as expected
text = "hello"
chunks = chunk(text, 2) # Returns [['h', 'e'], ['l', 'l'], ['o']]
# Solution: Handle strings specially if needed
def chunk_flexible(arr, size):
if isinstance(arr, str):
# Keep string chunks as strings
return [arr[i:i+size] for i in range(0, len(arr), size)]
else:
# Regular list chunking
return [arr[i:i+size] for i in range(0, len(arr), size)]
What's the output of running the following function using input [30, 20, 10, 100, 33, 12]
?
1def fun(arr: List[int]) -> List[int]:
2 import heapq
3 heapq.heapify(arr)
4 res = []
5 for i in range(3):
6 res.append(heapq.heappop(arr))
7 return res
8
1public static int[] fun(int[] arr) {
2 int[] res = new int[3];
3 PriorityQueue<Integer> heap = new PriorityQueue<>();
4 for (int i = 0; i < arr.length; i++) {
5 heap.add(arr[i]);
6 }
7 for (int i = 0; i < 3; i++) {
8 res[i] = heap.poll();
9 }
10 return res;
11}
12
1class HeapItem {
2 constructor(item, priority = item) {
3 this.item = item;
4 this.priority = priority;
5 }
6}
7
8class MinHeap {
9 constructor() {
10 this.heap = [];
11 }
12
13 push(node) {
14 // insert the new node at the end of the heap array
15 this.heap.push(node);
16 // find the correct position for the new node
17 this.bubble_up();
18 }
19
20 bubble_up() {
21 let index = this.heap.length - 1;
22
23 while (index > 0) {
24 const element = this.heap[index];
25 const parentIndex = Math.floor((index - 1) / 2);
26 const parent = this.heap[parentIndex];
27
28 if (parent.priority <= element.priority) break;
29 // if the parent is bigger than the child then swap the parent and child
30 this.heap[index] = parent;
31 this.heap[parentIndex] = element;
32 index = parentIndex;
33 }
34 }
35
36 pop() {
37 const min = this.heap[0];
38 this.heap[0] = this.heap[this.size() - 1];
39 this.heap.pop();
40 this.bubble_down();
41 return min;
42 }
43
44 bubble_down() {
45 let index = 0;
46 let min = index;
47 const n = this.heap.length;
48
49 while (index < n) {
50 const left = 2 * index + 1;
51 const right = left + 1;
52
53 if (left < n && this.heap[left].priority < this.heap[min].priority) {
54 min = left;
55 }
56 if (right < n && this.heap[right].priority < this.heap[min].priority) {
57 min = right;
58 }
59 if (min === index) break;
60 [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61 index = min;
62 }
63 }
64
65 peek() {
66 return this.heap[0];
67 }
68
69 size() {
70 return this.heap.length;
71 }
72}
73
74function fun(arr) {
75 const heap = new MinHeap();
76 for (const x of arr) {
77 heap.push(new HeapItem(x));
78 }
79 const res = [];
80 for (let i = 0; i < 3; i++) {
81 res.push(heap.pop().item);
82 }
83 return res;
84}
85
Recommended Readings
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!