You are given an integer array nums
and an integer k
. You can partition the array into at most k
non-empty adjacent subarrays. The score of a partition is the sum of the averages of each subarray.
Note that the partition must use every integer in nums
, and that the score is not necessarily an integer.
Return the maximum score you can achieve of all the possible partitions. Answers within 10-6
of the actual answer will be accepted.
Example 1:
Input: nums = [9,1,2,3,9], k = 3 Output: 20.00000 Explanation: The best choice is to partition nums into [9], [1, 2, 3], [9]. The answer is 9 + (1 + 2 + 3) / 3 + 9 = 20. We could have also partitioned nums into [9, 1], [2], [3, 9], for example. That partition would lead to a score of 5 + 2 + 6 = 13, which is worse.
Example 2:
Input: nums = [1,2,3,4,5,6,7], k = 4 Output: 20.50000
Constraints:
1 <= nums.length <= 100
1 <= nums[i] <= 104
1 <= k <= nums.length
This problem asks to find the maximum score achievable by partitioning an array into at most k
non-empty adjacent subarrays. The score is the sum of the averages of each subarray.
Two solutions are presented: Memoized Search and Dynamic Programming. Both leverage prefix sums for efficient subarray sum calculations.
This approach uses a recursive function dfs(i, k)
representing the maximum average sum achievable starting from index i
with at most k
partitions remaining.
Base Cases:
i == n
(end of array): return 0 (no more partitions possible).k == 1
(only one partition left): return the average of the remaining subarray.Recursive Step:
j
from i + 1
to n
.i
to j - 1
.dfs(j, k - 1)
to find the maximum average sum for the remaining partitions.Memoization: A cache (@cache
in Python, f[][]
in other languages) is used to store the results of subproblems, avoiding redundant calculations. This significantly improves efficiency.
Time Complexity: O(n²k). The nested loops iterate through all possible partition points. Memoization reduces the complexity from exponential to polynomial.
Space Complexity: O(nk). The space is dominated by the memoization table.
This solution transforms the memoized search into a bottom-up dynamic programming approach.
State: f[i][j]
represents the maximum average sum achievable using the first i
elements and at most j
partitions.
Base Case: f[i][1]
(one partition) is simply the average of the first i
elements.
Transition: For f[i][j]
(j > 1), iterate through possible split points h
(0 ≤ h < i). The maximum score is the maximum of f[h][j-1] + (average of elements from h+1 to i)
.
Time Complexity: O(n²k). Similar to the memoized approach, the complexity arises from the nested loops.
Space Complexity: O(nk). The space is again dominated by the DP table f[i][j]
.
Memoized Search:
from functools import cache, lru_cache
class Solution:
def largestSumOfAverages(self, nums: List[int], k: int) -> float:
n = len(nums)
s = list(accumulate(nums, initial=0)) #prefix sum
@cache
def dfs(i: int, k: int) -> float:
if i == n:
return 0
if k == 1:
return (s[n] - s[i]) / (n - i)
ans = 0
for j in range(i + 1, n + 1):
ans = max(ans, (s[j] - s[i]) / (j - i) + dfs(j, k - 1))
return ans
return dfs(0, k)
Dynamic Programming:
from itertools import accumulate
class Solution:
def largestSumOfAverages(self, nums: List[int], k: int) -> float:
n = len(nums)
f = [[0.0] * (k + 1) for _ in range(n + 1)]
s = list(accumulate(nums, initial=0))
for i in range(1, n + 1):
f[i][1] = s[i] / i
for j in range(2, min(i + 1, k + 1)):
for h in range(i):
f[i][j] = max(f[i][j], f[h][j - 1] + (s[i] - s[h]) / (i - h))
return f[n][k]
The code in other languages (Java, C++, Go, TypeScript) follows similar logic, with adjustments for syntax and data structures. The core algorithms remain the same. Choose whichever method (memoized search or dynamic programming) best suits your preference and the specific requirements of your programming environment.