class Solution:
def maxSumAfterPartitioning(self, arr, k):
n = len(arr)
dp = [0] * (n + 1)
for i in range(1, n + 1):
cur_max = 0
for j in range(1, min(k, i) + 1):
cur_max = max(cur_max, arr[i - j])
dp[i] = max(dp[i], dp[i - j] + cur_max * j)
return dp[n]
class Solution {
public:
int maxSumAfterPartitioning(vector<int>& arr, int k) {
int n = arr.size();
vector<int> dp(n + 1, 0);
for (int i = 1; i <= n; ++i) {
int cur_max = 0;
for (int j = 1; j <= min(k, i); ++j) {
cur_max = max(cur_max, arr[i - j]);
dp[i] = max(dp[i], dp[i - j] + cur_max * j);
}
}
return dp[n];
}
};
class Solution {
public int maxSumAfterPartitioning(int[] arr, int k) {
int n = arr.length;
int[] dp = new int[n + 1];
for (int i = 1; i <= n; ++i) {
int curMax = 0;
for (int j = 1; j <= Math.min(k, i); ++j) {
curMax = Math.max(curMax, arr[i - j]);
dp[i] = Math.max(dp[i], dp[i - j] + curMax * j);
}
}
return dp[n];
}
}
var maxSumAfterPartitioning = function(arr, k) {
const n = arr.length;
const dp = new Array(n + 1).fill(0);
for (let i = 1; i <= n; ++i) {
let curMax = 0;
for (let j = 1; j <= Math.min(k, i); ++j) {
curMax = Math.max(curMax, arr[i - j]);
dp[i] = Math.max(dp[i], dp[i - j] + curMax * j);
}
}
return dp[n];
};
Given an integer array arr
and an integer k
, you are allowed to split the array into contiguous subarrays, where each subarray has at most k
elements. For each subarray, every element is replaced by the maximum element in that subarray, and the sum of all the elements in the modified array is calculated.
Your task is to determine the maximum possible sum obtainable by partitioning the array this way.
Example:
Input: arr = [1,15,7,9,2,5,10]
, k = 3
Output: 84
At first glance, this problem seems to require checking all possible ways to split the array into subarrays of length at most k
. For each possible partition, we would compute the sum after replacing each subarray's elements with the subarray's maximum value. However, the number of possible partitions grows very quickly, making brute-force approaches inefficient.
To optimize, we can think recursively: for each position in the array, we can try ending the last partition at that position with length 1 up to k
, and for each, compute the maximum sum possible. This naturally leads to a dynamic programming (DP) approach, where we store the best result up to each index and build up to the full array.
We use a bottom-up dynamic programming strategy to solve this problem efficiently.
dp
where dp[i]
represents the maximum sum we can obtain by partitioning the first i
elements of arr
.
dp[0] = 0
since an empty array has sum 0.
i
(from 1 to n
), we consider all possible partition sizes j
(from 1 to k
), as long as i - j ≥ 0
. For each, we:
cur_max
in the last j
elements ending at i-1
.dp[i-j] + cur_max * j
.j
to update dp[i]
.dp[n]
gives the answer for the full array.
We use a DP array because each state depends only on previous states, and lookups/updates are fast. This avoids redundant computation and ensures we only consider valid partitions.
Let's walk through the example arr = [1,15,7,9,2,5,10]
, k = 3
:
dp[1] = 1
dp[1] + 15 = 16
dp[0] + 15*2 = 30
dp[2] = 30
dp[2] + 7 = 37
dp[1] + 15*2 = 31
dp[0] + 15*3 = 45
dp[3] = 45
i = 4
through i = 7
, always checking the last 1, 2, or 3 elements, and updating dp[i]
with the best sum.
dp[7] = 84
At each step, we ensure we always use the best possible partition ending at that position.
i
(from 1 to n
), we check up to k
previous elements. So, time complexity is O(n * k)
.
dp
array of size n+1
, so O(n)
space.
This is efficient and works well within typical constraints.
The key insight is to use dynamic programming to efficiently compute the maximum sum after partitioning. By considering all possible partition sizes up to k
for each position, and always keeping track of the best sum so far, we avoid redundant computation. The DP approach is elegant because it transforms an exponential brute-force problem into a linear one with respect to the array size, making it practical for large inputs.