1043. Partition Array for Maximum Sum
Problem Description
You are given an integer array arr
and need to partition it into contiguous subarrays where each subarray has a length of at most k
. After creating these partitions, you transform each subarray by replacing all its elements with the maximum value found in that subarray.
For example, if you have a subarray [2, 1, 4]
, after transformation it becomes [4, 4, 4]
since 4 is the maximum value.
Your goal is to find the partitioning strategy that maximizes the sum of all elements in the transformed array.
Key points:
- Each partition must be a contiguous subarray (elements must be adjacent in the original array)
- Each partition can have at most
k
elements - After partitioning, all elements in a partition take the value of the maximum element in that partition
- You want to maximize the total sum after this transformation
Example walkthrough:
If arr = [1, 15, 7, 9, 2, 5, 10]
and k = 3
:
- One possible partition:
[1, 15, 7]
,[9, 2, 5]
,[10]
- After transformation:
[15, 15, 15]
,[9, 9, 9]
,[10]
- Sum =
15*3 + 9*3 + 10 = 45 + 27 + 10 = 82
But a better partition would be: [1]
, [15]
, [7, 9]
, [2, 5, 10]
- After transformation:
[1]
,[15]
,[9, 9]
,[10, 10, 10]
- Sum =
1 + 15 + 18 + 30 = 64
Actually, the optimal partition is: [1, 15]
, [7, 9, 2]
, [5, 10]
- After transformation:
[15, 15]
,[9, 9, 9]
,[10, 10]
- Sum =
30 + 27 + 20 = 77
The problem asks you to return this maximum possible sum.
Intuition
When looking at this problem, we need to make decisions about where to place partition boundaries. The key insight is that this is an optimization problem where each decision affects future possibilities - a classic dynamic programming scenario.
Let's think about building the solution from left to right. At each position in the array, we need to decide: "Where should the partition containing this element start?"
Consider when we've processed some elements and are now at position i
. We need to determine the optimal way to partition elements up to index i
. The crucial observation is that the last partition must end at position i
, but where should it begin?
Since each partition can have at most k
elements, the last partition could start at any position from i-k+1
to i
(creating partitions of size k
, k-1
, ..., down to size 1).
For each possible starting position j
of the last partition:
- The partition would contain elements from index
j
toi
- All these elements would become equal to the maximum value in this range
- The contribution to the total sum would be:
max_value × partition_size
Here's the key recursive insight: If we choose to start the last partition at position j
, then:
- Elements from
j
toi
form the last partition - Elements before position
j
must have already been optimally partitioned - Total sum = (optimal sum for elements before
j
) + (contribution from last partition)
This naturally leads to dynamic programming where f[i]
represents the maximum sum we can achieve by optimally partitioning the first i
elements. To compute f[i]
, we try all valid starting positions for the last partition and pick the one giving the maximum sum.
The beauty of this approach is that by considering all possible sizes for the last partition (from 1 to k
), we implicitly explore all valid partitioning strategies without having to explicitly enumerate them all.
Learn more about Dynamic Programming patterns.
Solution Approach
We implement the dynamic programming approach using a 1D array f
where f[i]
stores the maximum sum achievable by partitioning the first i
elements of the array.
Initialization:
- Create array
f
of sizen+1
initialized with zeros f[0] = 0
represents the base case (no elements means sum is 0)
Main Algorithm:
For each position i
from 1 to n
:
-
Initialize tracking variables:
mx = 0
to track the maximum value in the current partition being considered
-
Try all possible partition sizes:
- Iterate
j
fromi
down tomax(0, i-k)
- This means we're considering partitions ending at position
i-1
(in 0-indexed array) - The partition would start at position
j-1
(in 0-indexed array)
- Iterate
-
For each partition configuration:
- Update
mx = max(mx, arr[j-1])
to maintain the maximum value in range[j-1, i-1]
- Calculate the sum if we use this partition:
- Previous optimal sum:
f[j-1]
(optimal sum for firstj-1
elements) - Current partition contribution:
mx * (i - j + 1)
(max value × partition size)
- Previous optimal sum:
- Update
f[i] = max(f[i], f[j-1] + mx * (i-j+1))
- Update
Key Implementation Details:
The inner loop iterates backwards (j
from i
to max(0, i-k) + 1
) which allows us to:
- Incrementally track the maximum value as we expand the partition leftward
- Consider all valid partition sizes from 1 to min(k, i)
Index Mapping:
f[i]
represents the result for the firsti
elementsarr[j-1]
accesses the actual array element at positionj-1
(0-indexed)- The partition size is
(i - j + 1)
elements
Time Complexity: O(n * k)
where n
is the array length
Space Complexity: O(n)
for the DP array
The final answer is f[n]
, representing the maximum sum after optimally partitioning all n
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 a small example with arr = [1, 4, 1, 5, 3]
and k = 3
.
We'll build our DP array f
where f[i]
represents the maximum sum for the first i
elements.
Initial State:
f[0] = 0
(base case: no elements)
Processing i = 1 (first element: 1):
- Only one partition possible:
[1]
j = 1
: partition is[1]
, max = 1f[1] = f[0] + 1 × 1 = 0 + 1 = 1
Processing i = 2 (first two elements: 1, 4):
- Try partition ending at index 1:
j = 2
: partition[4]
, max = 4,f[2] = f[1] + 4 × 1 = 1 + 4 = 5
j = 1
: partition[1, 4]
, max = 4,f[2] = f[0] + 4 × 2 = 0 + 8 = 8
✓
- Best:
f[2] = 8
Processing i = 3 (first three elements: 1, 4, 1):
- Try partitions ending at index 2:
j = 3
: partition[1]
, max = 1,f[3] = f[2] + 1 × 1 = 8 + 1 = 9
j = 2
: partition[4, 1]
, max = 4,f[3] = f[1] + 4 × 2 = 1 + 8 = 9
j = 1
: partition[1, 4, 1]
, max = 4,f[3] = f[0] + 4 × 3 = 0 + 12 = 12
✓
- Best:
f[3] = 12
Processing i = 4 (first four elements: 1, 4, 1, 5):
- Try partitions ending at index 3:
j = 4
: partition[5]
, max = 5,f[4] = f[3] + 5 × 1 = 12 + 5 = 17
✓j = 3
: partition[1, 5]
, max = 5,f[4] = f[2] + 5 × 2 = 8 + 10 = 18
✓j = 2
: partition[4, 1, 5]
, max = 5,f[4] = f[1] + 5 × 3 = 1 + 15 = 16
j = 1
is invalid (partition size would be 4 > k = 3)
- Best:
f[4] = 18
Processing i = 5 (all five elements: 1, 4, 1, 5, 3):
- Try partitions ending at index 4:
j = 5
: partition[3]
, max = 3,f[5] = f[4] + 3 × 1 = 18 + 3 = 21
j = 4
: partition[5, 3]
, max = 5,f[5] = f[3] + 5 × 2 = 12 + 10 = 22
✓j = 3
: partition[1, 5, 3]
, max = 5,f[5] = f[2] + 5 × 3 = 8 + 15 = 23
✓j = 2
is invalid (partition size would be 4 > k = 3)
- Best:
f[5] = 23
Final Answer: 23
The optimal partitioning is [1, 4]
and [1, 5, 3]
, which after transformation becomes [4, 4]
and [5, 5, 5]
, giving sum = 8 + 15 = 23.
Solution Implementation
1class Solution:
2 def maxSumAfterPartitioning(self, arr: List[int], k: int) -> int:
3 """
4 Partition array into subarrays of at most k length to maximize sum.
5 Each subarray's elements become equal to the subarray's maximum value.
6
7 Args:
8 arr: Input array to partition
9 k: Maximum allowed size for each partition
10
11 Returns:
12 Maximum sum after optimal partitioning
13 """
14 n = len(arr)
15
16 # dp[i] represents the maximum sum for arr[0:i]
17 # dp[0] = 0 (empty array has sum 0)
18 dp = [0] * (n + 1)
19
20 # Process each position from 1 to n
21 for i in range(1, n + 1):
22 max_value = 0 # Track maximum value in current partition
23
24 # Try all possible partition sizes ending at position i
25 # j represents the starting position of current partition (1-indexed)
26 for j in range(i, max(0, i - k), -1):
27 # Update maximum value in current partition [j-1, i-1] (0-indexed)
28 max_value = max(max_value, arr[j - 1])
29
30 # Calculate sum if we partition at position j:
31 # dp[j-1]: best sum for elements before current partition
32 # max_value * (i - j + 1): sum of current partition after transformation
33 partition_length = i - j + 1
34 current_sum = dp[j - 1] + max_value * partition_length
35
36 # Update dp[i] with the best sum found so far
37 dp[i] = max(dp[i], current_sum)
38
39 return dp[n]
40
1class Solution {
2 public int maxSumAfterPartitioning(int[] arr, int k) {
3 int n = arr.length;
4 // dp[i] represents the maximum sum we can achieve by partitioning arr[0...i-1]
5 int[] dp = new int[n + 1];
6
7 // Iterate through each position in the array
8 for (int i = 1; i <= n; i++) {
9 int maxValue = 0;
10
11 // Try all possible partition sizes ending at position i
12 // j represents the starting position of the current partition (1-indexed)
13 for (int j = i; j > Math.max(0, i - k); j--) {
14 // Update the maximum value in the current partition
15 maxValue = Math.max(maxValue, arr[j - 1]);
16
17 // Calculate the sum if we partition from j to i:
18 // dp[j-1]: maximum sum before this partition
19 // maxValue * (i - j + 1): sum of current partition (all elements become maxValue)
20 dp[i] = Math.max(dp[i], dp[j - 1] + maxValue * (i - j + 1));
21 }
22 }
23
24 // Return the maximum sum after partitioning the entire array
25 return dp[n];
26 }
27}
28
1class Solution {
2public:
3 int maxSumAfterPartitioning(vector<int>& arr, int k) {
4 int n = arr.size();
5
6 // dp[i] represents the maximum sum we can achieve
7 // by partitioning arr[0...i-1] into subarrays of size at most k
8 vector<int> dp(n + 1, 0);
9
10 // Iterate through each position in the array
11 for (int i = 1; i <= n; ++i) {
12 int maxValue = 0;
13
14 // Try all possible partition sizes ending at position i
15 // j represents the starting position of the current partition (1-indexed)
16 for (int j = i; j > max(0, i - k); --j) {
17 // Update the maximum value in the current partition
18 maxValue = max(maxValue, arr[j - 1]);
19
20 // Calculate the sum if we partition from j to i
21 // dp[j-1]: max sum up to position j-1
22 // maxValue * (i - j + 1): contribution of current partition
23 // where all elements become maxValue
24 dp[i] = max(dp[i], dp[j - 1] + maxValue * (i - j + 1));
25 }
26 }
27
28 // Return the maximum sum after partitioning the entire array
29 return dp[n];
30 }
31};
32
1/**
2 * Partitions an array into contiguous subarrays of maximum length k
3 * to maximize the sum after changing all elements in each subarray
4 * to the maximum element in that subarray.
5 *
6 * @param arr - The input array to partition
7 * @param k - Maximum length of each partition
8 * @returns The maximum sum after partitioning
9 */
10function maxSumAfterPartitioning(arr: number[], k: number): number {
11 const arrayLength: number = arr.length;
12
13 // dp[i] represents the maximum sum for the first i elements
14 const dp: number[] = new Array(arrayLength + 1).fill(0);
15
16 // Process each position in the array
17 for (let currentPosition = 1; currentPosition <= arrayLength; currentPosition++) {
18 let maxElementInPartition: number = 0;
19
20 // Try all possible partition sizes ending at current position
21 // Start from current position and go back at most k elements
22 for (let partitionStart = currentPosition; partitionStart > Math.max(0, currentPosition - k); partitionStart--) {
23 // Update the maximum element in the current partition
24 maxElementInPartition = Math.max(maxElementInPartition, arr[partitionStart - 1]);
25
26 // Calculate partition length
27 const partitionLength: number = currentPosition - partitionStart + 1;
28
29 // Update dp value: previous partition sum + current partition sum
30 // Current partition sum = max element * partition length
31 dp[currentPosition] = Math.max(
32 dp[currentPosition],
33 dp[partitionStart - 1] + maxElementInPartition * partitionLength
34 );
35 }
36 }
37
38 return dp[arrayLength];
39}
40
Time and Space Complexity
Time Complexity: O(n × k)
The algorithm uses dynamic programming with two nested loops:
- The outer loop runs from
1
ton
, iteratingn
times - The inner loop runs from
i
tomax(0, i - k)
, which iterates at mostk
times for each value ofi
- Within the inner loop, all operations (
max
, array access, arithmetic) areO(1)
Therefore, the total time complexity is O(n × k)
.
Space Complexity: O(n)
The algorithm uses:
- An array
f
of sizen + 1
to store the dynamic programming states, which requiresO(n + 1) = O(n)
space - A few variables (
n
,i
,j
,mx
) that use constantO(1)
space
The total space complexity is O(n)
, where n
is the length of the array arr
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-One Errors in Index Management
The Pitfall:
The most common mistake in this problem is confusion between 0-indexed arrays and 1-indexed DP states. The DP array dp[i]
represents the result for the first i
elements, but array access uses 0-based indexing. This leads to errors like:
- Accessing
arr[j]
instead ofarr[j-1]
in the inner loop - Incorrectly calculating partition length as
(i - j)
instead of(i - j + 1)
- Wrong loop boundaries when iterating backwards
Example of Incorrect Code:
# WRONG: Common indexing mistakes
for i in range(1, n + 1):
max_value = 0
for j in range(i, max(0, i - k), -1):
max_value = max(max_value, arr[j]) # ERROR: should be arr[j-1]
partition_length = i - j # ERROR: should be i - j + 1
dp[i] = max(dp[i], dp[j] + max_value * partition_length) # ERROR: should be dp[j-1]
The Solution: Always remember the mapping:
dp[i]
= optimal sum for elementsarr[0]
througharr[i-1]
- When
j
iterates fromi
down tomax(0, i-k) + 1
, we're considering partition[j-1, i-1]
in 0-indexed terms - Access array elements using
arr[j-1]
, notarr[j]
- Use
dp[j-1]
for the previous partition's optimal sum
2. Incorrect Partition Size Calculation
The Pitfall:
Forgetting that when considering a partition from position j
to i
in the DP array, the actual partition size is (i - j + 1)
, not (i - j)
. This leads to incorrect sum calculations.
Example Scenario:
If i = 3
and j = 1
, the partition includes indices [0, 1, 2]
in the original array (3 elements), but developers often mistakenly calculate the size as 3 - 1 = 2
.
The Solution:
Always use partition_length = i - j + 1
when calculating the contribution of the current partition. Draw out small examples to verify your indexing logic.
3. Not Resetting Maximum Value for Each Position
The Pitfall:
Declaring max_value
outside the outer loop, causing it to accumulate values across different positions:
# WRONG: max_value not reset for each i
max_value = 0 # Declared outside - accumulates incorrectly
for i in range(1, n + 1):
for j in range(i, max(0, i - k), -1):
max_value = max(max_value, arr[j - 1])
# max_value now contains maximum from ALL previous iterations
The Solution:
Always initialize max_value = 0
inside the outer loop, immediately after starting to process each position i
. This ensures we're only tracking the maximum within the current partition being considered.
4. Incorrect Loop Boundary for j
The Pitfall:
Using range(i, max(1, i - k), -1)
instead of range(i, max(0, i - k), -1)
. This misses valid partitions when i <= k
.
Example:
When i = 2
and k = 3
, we should consider partitions of size 1 and 2. But using max(1, i - k)
would give max(1, -1) = 1
, causing us to only check j = 2
and j = 1
, missing the partition of the entire first two elements.
The Solution:
Use max(0, i - k)
to ensure we consider all valid partition sizes, especially for positions where i <= k
. The correct range ensures we check partitions of all sizes from 1 up to min(k, i)
.
Depth first search is equivalent to which of the tree traversal order?
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
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
Want a Structured Path to Master System Design Too? Don’t Miss This!