Problem of The Day: Partition Array for Maximum Sum
Problem Statement

Scratch Notes

Brute Force - TLE
Attempted to use backtrack to solve
class Solution:
def maxSumAfterPartitioning(self, arr: List[int], k: int) -> int:
subarrays_max = []
N = len(arr)
def dfs(idx):
if idx >= N:
return sum(subarrays_max)
res = 0
for i in range(1, k + 1):
val = max(arr[idx:idx + i])
for _ in range(len(arr[idx:idx + i])):
subarrays_max.append(val)
res = max(res, dfs(idx + i))
for _ in range(len(arr[idx:idx + i])):
subarrays_max.pop()
return res
return dfs(0)
Other implementation
class Solution:
def maxSumAfterPartitioning(self, arr: List[int], k: int) -> int:
N = len(arr)
def dfs(idx, curr):
if idx >= N:
re = 0
for subarray in curr:
max_val = max(subarray)
for _ in range(len(subarray)):
re += max_val
return re
res = 0
for i in range(1, k + 1):
res = max(res, dfs(idx + i, curr + [arr[idx:idx + i]]))
return res
return dfs(0, [])
Memoization Approach - Accepted
Intuition
The key idea is likely to consider all possible partitioning strategies and find the one that maximizes the sum.
Approach
The provided code is using a recursive approach with memoization. It defines a function dfs that takes an index i as a parameter, indicating the current position in the array. The function explores all possible partitions by considering subarrays of at most size k, calculating the sum for each partition, and recursively moving to the next position.
The memo dictionary is used to store already computed results for a given index, avoiding redundant calculations.
Complexity
-
Time complexity: O(n*k) the function
dfsis called for each index from 0 to N-1, and for each call, it explores up to the next k elements. -
Space complexity: The space complexity is determined by the size of the memo dictionary, which is proportional to the number of unique subproblems. In the worst case, it could be O(n), where n is the length of the input array.
Code
class Solution:
def maxSumAfterPartitioning(self, arr: List[int], k: int) -> int:
N = len(arr)
memo = defaultdict()
def dfs(i):
if i >= N:
return 0
if i in memo:
return memo[i]
curr_max = 0
res = 0
for j in range(i, min(N, i + k)):
curr_max = max(curr_max, arr[j])
window_size = j - i + 1
res = max(res, curr_max * window_size + dfs(j + 1))
memo[i] = res
return res
return dfs(0)
Editor Solution
Dynamic Programming - 1D array
class Solution:
def maxSumAfterPartitioning(self, arr: List[int], k: int) -> int:
N = len(arr)
dp = [0] * N
for i in range(N - 1, -1, -1):
curr_max = 0
for j in range(i, min(N, i + k)):
curr_max = max(curr_max, arr[j])
window_size = j - i + 1
prev = dp[j + 1] if j + 1 < N else 0
dp[i] = max(dp[i], curr_max * window_size + prev)
return dp[0]
Dynamic Programming - 1D array fixed K size
The idea is to realize that the 1D dynamic programming approach only use k-size of previous subarray for the current computation. Therefore, we can reduce space by utilizing just k-size array instead of N-size array to cache the previous sub-problem.
class Solution:
def maxSumAfterPartitioning(self, arr: List[int], k: int) -> int:
N = len(arr)
K = k + 1
dp = [0] * K
for i in range(N - 1, -1, -1):
curr_max = 0
for j in range(i, min(N, i + k)):
curr_max = max(curr_max, arr[j])
window_size = j - i + 1
prev = dp[(j + 1) % K]
dp[i % K] = max(dp[i % K], curr_max * window_size + prev)
print(dp)
return dp[0]