Facebook Pixel

769. Max Chunks To Make Sorted

Problem Description

You have an integer array arr of length n that contains a permutation of all integers from 0 to n-1 (each number appears exactly once).

Your task is to split this array into chunks (consecutive subarrays), where each chunk can be sorted independently. After sorting each chunk individually and then concatenating them back together, the final result should be the same as if you had sorted the entire array.

For example, if arr = [1,0,2,3,4], you could split it into chunks [1,0] and [2,3,4]. After sorting each chunk, you get [0,1] and [2,3,4], which when concatenated gives [0,1,2,3,4] - the sorted array.

The goal is to find the maximum number of chunks you can create while still maintaining this property.

The key insight is that since the array contains a permutation of [0, 1, 2, ..., n-1], in the final sorted array, each element i should be at position i. A chunk can end at position i if and only if all elements from 0 to i have been seen within the first i+1 positions. This happens when the maximum value encountered so far equals the current index i.

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

Intuition

Let's think about what makes a valid chunk. Since we want the concatenated sorted chunks to equal the fully sorted array, we need to understand when we can safely "cut" the array.

In a sorted permutation of [0, 1, 2, ..., n-1], element 0 should be at index 0, element 1 at index 1, and so on. Each element's value equals its target position.

Consider this key observation: if we've seen all numbers from 0 to i within the first i+1 positions of the array, then we can make a cut after position i. Why? Because after sorting this chunk, all these numbers will be in their correct final positions (0 through i), and they won't need to move beyond this chunk.

How can we check if we've seen all numbers from 0 to i? Since our array is a permutation, if the maximum value we've encountered up to position i is exactly i, then we must have seen all values from 0 to i. This is because:

  • We have i+1 positions (from index 0 to i)
  • The maximum value seen is i
  • Since it's a permutation with no duplicates, we must have exactly the numbers 0, 1, 2, ..., i in some order

For example, if at index 2 the maximum value seen so far is 2, we know we've seen values 0, 1, and 2 in the first three positions. These can be sorted independently without affecting the rest of the array.

This leads to our greedy approach: traverse the array, track the maximum value seen so far, and whenever this maximum equals the current index, we can create a chunk boundary there.

Learn more about Stack, Greedy, Sorting and Monotonic Stack patterns.

Solution Approach

The solution implements a greedy one-pass algorithm that tracks the maximum value seen so far and counts valid chunk boundaries.

Algorithm Steps:

  1. Initialize two variables:

    • mx = 0: tracks the maximum value seen so far
    • ans = 0: counts the number of chunks
  2. Iterate through the array using enumeration to get both index i and value v:

    • Update the maximum: mx = max(mx, v)
    • Check if we can make a cut: if i == mx, it means all numbers from 0 to i are present in the first i+1 positions
    • If we can make a cut, increment the chunk count: ans += 1
  3. Return the total number of chunks

Why this works:

When i == mx, it guarantees that:

  • The largest number we've seen is i
  • We've processed i+1 elements (indices 0 through i)
  • Since the array is a permutation without duplicates, we must have exactly the numbers {0, 1, 2, ..., i} in some order
  • These numbers can be sorted independently to occupy positions 0 through i in the final sorted array

Example walkthrough:

For arr = [1, 0, 2, 3, 4]:

  • i=0, v=1: mx=1, i≠mx, no chunk
  • i=1, v=0: mx=1, i=mx=1, create chunk → ans=1
  • i=2, v=2: mx=2, i=mx=2, create chunk → ans=2
  • i=3, v=3: mx=3, i=mx=3, create chunk → ans=3
  • i=4, v=4: mx=4, i=mx=4, create chunk → ans=4

Result: 4 chunks [1,0], [2], [3], [4]

Time Complexity: O(n) - single pass through the array
Space Complexity: O(1) - only using two variables

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 the solution with arr = [0, 2, 1, 4, 3]:

Initial state: mx = 0, ans = 0

Step 1: Index 0, Value 0

  • Update maximum: mx = max(0, 0) = 0
  • Check if we can create a chunk: i == mx0 == 0
  • We've seen all numbers from 0 to 0 (just the number 0) in the first position
  • Increment chunk count: ans = 1
  • First chunk identified: [0]

Step 2: Index 1, Value 2

  • Update maximum: mx = max(0, 2) = 2
  • Check if we can create a chunk: i == mx1 == 2
  • Cannot create a chunk here because we've seen value 2 but we're only at index 1
  • This means we need at least one more element to complete this chunk

Step 3: Index 2, Value 1

  • Update maximum: mx = max(2, 1) = 2
  • Check if we can create a chunk: i == mx2 == 2
  • We've seen all numbers from 0 to 2 (values: 0, 2, 1) in the first 3 positions
  • Increment chunk count: ans = 2
  • Second chunk identified: [2, 1]

Step 4: Index 3, Value 4

  • Update maximum: mx = max(2, 4) = 4
  • Check if we can create a chunk: i == mx3 == 4
  • Cannot create a chunk because maximum value 4 indicates we need to reach index 4

Step 5: Index 4, Value 3

  • Update maximum: mx = max(4, 3) = 4
  • Check if we can create a chunk: i == mx4 == 4
  • We've seen all numbers from 0 to 4 (values: 0, 2, 1, 4, 3) in all 5 positions
  • Increment chunk count: ans = 3
  • Third chunk identified: [4, 3]

Final Result: 3 chunks: [0] | [2, 1] | [4, 3]

Verification:

  • Sort each chunk: [0] | [1, 2] | [3, 4]
  • Concatenate: [0, 1, 2, 3, 4] ✓ (correctly sorted array)

Solution Implementation

1class Solution:
2    def maxChunksToSorted(self, arr: List[int]) -> int:
3        # Initialize maximum value seen so far and chunk count
4        max_value = 0
5        chunk_count = 0
6      
7        # Iterate through array with index and value
8        for index, value in enumerate(arr):
9            # Update the maximum value encountered so far
10            max_value = max(max_value, value)
11          
12            # If current index equals max value, we can form a valid chunk
13            # This means all elements from start to current position
14            # contain exactly the values 0 to index
15            if index == max_value:
16                chunk_count += 1
17      
18        return chunk_count
19
1class Solution {
2    public int maxChunksToSorted(int[] arr) {
3        // Initialize counter for number of chunks and track maximum value seen so far
4        int chunkCount = 0;
5        int maxValueSoFar = 0;
6      
7        // Iterate through each element in the array
8        for (int i = 0; i < arr.length; i++) {
9            // Update the maximum value encountered up to current index
10            maxValueSoFar = Math.max(maxValueSoFar, arr[i]);
11          
12            // Check if we can form a chunk at current position
13            // A chunk can be formed when the maximum value seen so far equals the current index
14            // This means all elements from 0 to i are present in the range [0, i]
15            if (i == maxValueSoFar) {
16                chunkCount++;
17            }
18        }
19      
20        // Return the total number of chunks that can be formed
21        return chunkCount;
22    }
23}
24
1class Solution {
2public:
3    int maxChunksToSorted(vector<int>& arr) {
4        int chunkCount = 0;      // Number of chunks that can be formed
5        int maxValueSoFar = 0;    // Maximum value encountered up to current index
6      
7        // Iterate through each element in the array
8        for (int i = 0; i < arr.size(); ++i) {
9            // Update the maximum value seen so far
10            maxValueSoFar = max(maxValueSoFar, arr[i]);
11          
12            // If the maximum value equals the current index,
13            // it means all elements from 0 to i can form a valid chunk
14            // because after sorting this chunk, elements will be in positions [0, i]
15            if (i == maxValueSoFar) {
16                chunkCount++;
17            }
18        }
19      
20        return chunkCount;
21    }
22};
23
1/**
2 * Finds the maximum number of chunks that can be made to sort the array.
3 * Each chunk can be sorted independently, and concatenating them results in a sorted array.
4 * 
5 * @param arr - Input array containing integers from 0 to n-1
6 * @returns The maximum number of chunks
7 */
8function maxChunksToSorted(arr: number[]): number {
9    const arrayLength: number = arr.length;
10    let chunkCount: number = 0;
11    let maxValueSoFar: number = 0;
12  
13    // Iterate through the array to find valid chunk boundaries
14    for (let currentIndex: number = 0; currentIndex < arrayLength; currentIndex++) {
15        // Update the maximum value encountered so far
16        maxValueSoFar = Math.max(arr[currentIndex], maxValueSoFar);
17      
18        // If the maximum value equals the current index, we can create a chunk
19        // This works because all values from 0 to currentIndex must be present
20        // in the subarray [0...currentIndex] for it to be sortable independently
21        if (maxValueSoFar === currentIndex) {
22            chunkCount++;
23        }
24    }
25  
26    return chunkCount;
27}
28

Time and Space Complexity

The time complexity of this algorithm is O(n), where n is the length of the array arr. This is because the code iterates through the array exactly once using a single for loop with enumerate(arr). Each operation inside the loop (comparison with max(), checking equality, and incrementing counter) takes constant time O(1).

The space complexity is O(1) as the algorithm only uses a fixed amount of extra space regardless of the input size. The variables mx and ans are simple integers that don't grow with the input size, and the loop variables i and v also use constant space.

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

Common Pitfalls

Pitfall 1: Misunderstanding the Chunk Boundary Condition

The Mistake: Developers often incorrectly assume that a chunk can end whenever the current element equals its index (arr[i] == i), thinking this means the element is "in the right place."

# INCORRECT approach
def maxChunksToSorted(self, arr: List[int]) -> int:
    chunk_count = 0
    for i, v in enumerate(arr):
        if v == i:  # Wrong condition!
            chunk_count += 1
    return chunk_count

Why it fails: Consider arr = [2, 0, 1, 3]. At index 3, arr[3] == 3, but you cannot split here into [2, 0, 1] and [3] because sorting [2, 0, 1] gives [0, 1, 2], not the required positions.

The Fix: Use the maximum value condition (max_value == index) instead, which ensures all values from 0 to index are present in the first index+1 positions.

Pitfall 2: Off-by-One Errors with Array Bounds

The Mistake: Confusion about whether to check max_value == index or max_value == index + 1, especially when thinking about "we've seen index+1 elements."

# INCORRECT - using wrong comparison
def maxChunksToSorted(self, arr: List[int]) -> int:
    max_value = 0
    chunk_count = 0
    for i, v in enumerate(arr):
        max_value = max(max_value, v)
        if max_value == i + 1:  # Wrong! This doesn't align with 0-indexed arrays
            chunk_count += 1
    return chunk_count

The Fix: Remember that in a 0-indexed array containing values 0 to n-1:

  • At index i, we've seen i+1 elements
  • The maximum possible value among these elements should be i (not i+1)
  • Therefore, the correct condition is max_value == i

Pitfall 3: Forgetting to Handle Edge Cases

The Mistake: Not considering special cases like single-element arrays or already sorted arrays.

# Potentially problematic if not careful
def maxChunksToSorted(self, arr: List[int]) -> int:
    if not arr:  # Unnecessary check - problem guarantees non-empty array
        return 0
    # ... rest of code

Why the original solution works: The algorithm naturally handles all edge cases:

  • Single element [0]: Returns 1 chunk correctly
  • Already sorted [0,1,2,3]: Returns n chunks (maximum possible)
  • Reverse sorted [3,2,1,0]: Returns 1 chunk (minimum possible)

Pitfall 4: Incorrect Maximum Value Initialization

The Mistake: Initializing the maximum value to a wrong starting value, such as the first element or negative infinity.

# INCORRECT initialization
def maxChunksToSorted(self, arr: List[int]) -> int:
    max_value = arr[0]  # Wrong! Should start at 0 or handle first element specially
    chunk_count = 0
    for i in range(1, len(arr)):  # Missing first element
        max_value = max(max_value, arr[i])
        if max_value == i:
            chunk_count += 1
    return chunk_count

The Fix: Initialize max_value = 0 or -1 (since all values are non-negative), and process all elements from index 0. The algorithm works because we're guaranteed the array contains exactly the values 0 through n-1.

Discover Your Strengths and Weaknesses: Take Our 3-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