Given an array arr
that is a permutation of the numbers 0
to n-1
(where n
is the length of the array), your goal is to split the array into the maximum number of "chunks" such that sorting each chunk individually and concatenating them results in a fully sorted array.
You must find the largest number of chunks possible. Each chunk must be a contiguous subarray, and you cannot reuse elements between chunks. The array always contains each number from 0
to n-1
exactly once.
At first glance, you might consider trying every possible way to split the array and check if sorting the chunks gives the sorted array. However, this brute-force approach is inefficient.
Instead, let's think about what it means for a chunk to be "sortable" in isolation. For a chunk ending at index i
, if the largest number we've seen so far is exactly i
, then all numbers from 0
up to i
must have already appeared. That means we can safely split here, because sorting this chunk will put those numbers in their correct place.
The optimization comes from recognizing we only need to track the maximum value we've seen as we scan the array from left to right.
max_so_far
).
chunks
), starting at 0.
i
, update max_so_far
to be the maximum of max_so_far
and arr[i]
.max_so_far == i
, it means all numbers from 0
to i
are present in the first i+1
elements, and we can make a chunk ending here.chunks
each time this condition is met.This is efficient because it only requires a single pass through the array and constant-time operations at each step.
Let's use the example arr = [1, 0, 2, 3, 4]
.
max_so_far = 0
, chunks = 0
.arr[0] = 1
, max_so_far = max(0,1) = 1
. max_so_far != 0
, so no chunk yet.arr[1] = 0
, max_so_far = max(1,0) = 1
. Now max_so_far == 1
, so we can make a chunk ending at index 1 ([1,0]
).chunks
to 1.arr[2] = 2
, max_so_far = max(1,2) = 2
. max_so_far == 2
, so we can make a chunk ending at index 2 ([2]
).chunks
to 2.arr[3] = 3
, max_so_far = max(2,3) = 3
. max_so_far == 3
, so we can make a chunk ending at index 3 ([3]
).chunks
to 3.arr[4] = 4
, max_so_far = max(3,4) = 4
. max_so_far == 4
, so we can make a chunk ending at index 4 ([4]
).chunks
to 4.
The final answer is 4
chunks.
O(2^n)
, since every position could be a split point.
O(n)
time and O(1)
extra space.
This efficiency comes from realizing we only need to track the current maximum to determine split points.
The key insight is that you can split the array at position i
if all numbers up to i
have already appeared. By tracking the running maximum, you can efficiently determine these split points in a single pass. This makes the solution both elegant and optimal for this permutation-based array chunking problem.
class Solution:
def maxChunksToSorted(self, arr):
max_so_far = 0
chunks = 0
for i, num in enumerate(arr):
max_so_far = max(max_so_far, num)
if max_so_far == i:
chunks += 1
return chunks
class Solution {
public:
int maxChunksToSorted(vector<int>& arr) {
int max_so_far = 0, chunks = 0;
for (int i = 0; i < arr.size(); ++i) {
max_so_far = max(max_so_far, arr[i]);
if (max_so_far == i) {
++chunks;
}
}
return chunks;
}
};
class Solution {
public int maxChunksToSorted(int[] arr) {
int maxSoFar = 0, chunks = 0;
for (int i = 0; i < arr.length; i++) {
maxSoFar = Math.max(maxSoFar, arr[i]);
if (maxSoFar == i) {
chunks++;
}
}
return chunks;
}
}
var maxChunksToSorted = function(arr) {
let maxSoFar = 0, chunks = 0;
for (let i = 0; i < arr.length; i++) {
maxSoFar = Math.max(maxSoFar, arr[i]);
if (maxSoFar === i) {
chunks++;
}
}
return chunks;
};