LeetCode 813. 最大平均值和的分组
题目描述

题意分析
把整个数组分成至多
k个非空连续段,每个元素恰好属于一段,得分是各段平均值的和。分段不能改变原顺序,也不能遗漏元素,平均值和最终得分都可能不是整数。本题所有元素都是正数。若把一段拆成两段,原平均值是两个新平均值的加权平均,不会超过其中较大的一个;两个正平均值之和则更大。因此还有可拆分的段时,增加一组会提高得分,最优解可以按恰好
k组求出。
解法:前缀和 + 分组动态规划
核心思路
[!blue]
定义
dp[g][i]为前i个元素恰好分成g个非空连续段的最大得分。考虑最后一组从下标j开始:前j个元素必须分成g-1组,最后一组则固定为nums[j..i-1]。用
prefix[i]保存前i个元素的和,最后一组的平均值为(prefix[i]-prefix[j])/(i-j),常数时间就能计算。枚举最后切点后,转移为:
dp[g][i] = max(dp[g-1][j] + (prefix[i]-prefix[j])/(i-j)),其中g-1 <= j < i。
j >= g-1保证前面有足够元素组成g-1个非空组,j < i保证最后一组非空。最后一组确定后,前面部分必须取最优划分,否则替换成更优方案就能提高总分;遍历全部合法切点,又覆盖了所有可能的最后一组,因此转移既不会漏解,也不会取到非法划分。
解题步骤
- 建立长度为
n+1的浮点前缀和,令prefix[0]=0。- 一组时不能切分,初始化
dp[1][i]=prefix[i]/i。- 组数
g从 2 递增到k,前缀长度i从g到n,枚举j从g-1到i-1更新状态。所需的上一组数状态已经计算完毕。- 返回
dp[k][n],表示全部元素恰好分成k组的最大得分。未使用的状态虽然默认是 0,但循环只读取元素数足够的合法状态;本题得分为正,合法候选也一定能更新初值。
k=1时直接得到整个数组的平均值,k=n时每个元素单独一组,得分等于总和。除法必须使用double或float64,避免小数部分被截断。
代码实现
class Solution {
public double largestSumOfAverages(int[] nums, int k) {
int n = nums.length;
k = Math.min(k, n);
double[] prefix = new double[n + 1];
for (int i = 0; i < n; i++) {
prefix[i + 1] = prefix[i] + nums[i];
}
double[][] dp = new double[k + 1][n + 1];
for (int i = 1; i <= n; i++) {
// 一组时必须包含整个前缀,不能只取末元素
dp[1][i] = prefix[i] / i;
}
for (int group = 2; group <= k; group++) {
for (int i = group; i <= n; i++) {
// 前面留足组数,且最后一组必须非空
for (int j = group - 1; j < i; j++) {
double avg = (prefix[i] - prefix[j]) / (i - j);
dp[group][i] = Math.max(dp[group][i], dp[group - 1][j] + avg);
}
}
}
return dp[k][n];
}
}
func largestSumOfAverages(nums []int, k int) float64 {
n := len(nums)
if k > n {
k = n
}
prefix := make([]float64, n+1)
for i := 0; i < n; i++ {
prefix[i+1] = prefix[i] + float64(nums[i])
}
dp := make([][]float64, k+1)
for i := 0; i <= k; i++ {
dp[i] = make([]float64, n+1)
}
for i := 1; i <= n; i++ {
// 一组时必须包含整个前缀,不能只取末元素
dp[1][i] = prefix[i] / float64(i)
}
for group := 2; group <= k; group++ {
for i := group; i <= n; i++ {
// 前面留足组数,且最后一组必须非空
for j := group - 1; j < i; j++ {
avg := (prefix[i] - prefix[j]) / float64(i-j)
val := dp[group-1][j] + avg
if val > dp[group][i] {
dp[group][i] = val
}
}
}
}
return dp[k][n]
}
复杂度分析
- 时间复杂度:$O(kn^2)$,共有 $O(kn)$ 个状态,每个状态最多枚举 $n$ 个最后切点。
- 空间复杂度:$O(kn+n)$,分组表与前缀和。
关键点总结
[!green]
- 状态按恰好组数定义,题目的正数条件保证用满允许组数最优。
i和j表示前缀元素个数,最后一段为半开区间[j,i),长度是i-j。- 每次只新增最后一组的平均值,前面的得分直接复用上一组数的最优状态。
易错点总结
[!yellow]
- 切点取到 i 会出现空组与零除数。
- 一组初值直接取最后元素,无法表示整个前缀。
- 区间和端点减错,会把邻组元素重复计入。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 410. 分割数组的最大值 | 困难 | 同样选择连续分段,但本题最大化各段平均值之和,不能照搬最大段和的二分可行性。 |
| 1278. 分割回文串 III | 困难 | 同样按前缀长度与段数做分割DP,每段代价不同,本题用前缀和计算平均值。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!