Facebook Pixel

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] and size = 2, the result would be [[1, 2], [3, 4], [5]]
  • If arr = [1, 2, 3, 4, 5, 6] and size = 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.

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

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:

  1. Initialize Result Array: Create an empty array ans to store the chunked subarrays.

  2. 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
  3. Extract Chunks: In each iteration:

    • Use arr.slice(i, i + size) to extract elements from index i to i + size - 1
    • This creates a new subarray containing at most size elements
    • Push this subarray into the result array ans
  4. 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

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 Evaluator

Example 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] to ans
  • 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] to ans
  • 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]
  • Push [7] to ans
  • Result so far: ans = [[1, 2, 3], [4, 5, 6], [7]]
  • Increment: i = 6 + 3 = 9

Loop Termination:

  • Condition check: i < n9 < 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, totaling O(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 ValueError
  • size 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)]
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

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

Load More