Facebook Pixel

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.

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

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 to i
  • 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 to i 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 size n+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:

  1. Initialize tracking variables:

    • mx = 0 to track the maximum value in the current partition being considered
  2. Try all possible partition sizes:

    • Iterate j from i down to max(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)
  3. 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 first j-1 elements)
      • Current partition contribution: mx * (i - j + 1) (max value × partition size)
    • Update f[i] = max(f[i], f[j-1] + mx * (i-j+1))

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 first i elements
  • arr[j-1] accesses the actual array element at position j-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 Evaluator

Example 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 = 1
  • f[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 to n, iterating n times
  • The inner loop runs from i to max(0, i - k), which iterates at most k times for each value of i
  • Within the inner loop, all operations (max, array access, arithmetic) are O(1)

Therefore, the total time complexity is O(n × k).

Space Complexity: O(n)

The algorithm uses:

  • An array f of size n + 1 to store the dynamic programming states, which requires O(n + 1) = O(n) space
  • A few variables (n, i, j, mx) that use constant O(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 of arr[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 elements arr[0] through arr[i-1]
  • When j iterates from i down to max(0, i-k) + 1, we're considering partition [j-1, i-1] in 0-indexed terms
  • Access array elements using arr[j-1], not arr[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).

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Depth first search is equivalent to which of the tree traversal order?


Recommended Readings

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

Load More