【每日一题Day40】LC813最大平均值的和分组 | 序列DP

博客主要介绍了如何使用动态规划解决将数组划分为最多k个连续子数组,以最大化子数组平均值之和的问题。通过建立dp数组和预处理前缀和,遍历数组元素和子数组个数,找到最优解。代码实现中涉及到双层循环和最大值比较,最终返回dp[len][k]作为答案。

最大平均值的和分组【LC813】

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.

给定数组 nums 和一个整数 k 。我们将给定的数组 nums 分成 最多 k 个相邻的非空子数组 。 分数 由每个子数组内的平均值的总和构成。

注意我们必须使用 nums 数组中的每一个数进行分组,并且分数不一定需要是整数。

返回我们所能得到的最大 分数 是多少。答案误差在 10-6 内被视为是正确的。

隔离第4天…
学习使我忘记时间…

序列dp
  • 思路:将 n 个元素划分为k个连续子数组,最大化子数组的平均值之和。
  1. 确定dp数组(dp table)以及下标的含义

    dp[i][j]:将长度为[0,i]的nums分割为j个连续子数组时,得到的最大分数

  2. 确定递推公式

    j≤kj\le kjk时,nums[i]有两种选择,自成一组,或者并入前一组

    • 自成一组:dp[i][j]=dp[i−1][j−1]+num[i]dp[i][j] = dp[i-1][j-1] + num[i]dp[i][j]=dp[i1][j1]+num[i]

    • 并入前一组:对于每个nums[i],需要枚举前一组所有可能的初始位置,为了方便计算平均值,使用preSum数组计算前缀和,由于每个子数组至少包含一个元素,因此start∈[1,i)start\in[1,i)start[1,i)

      dp[i][j]=dp[start−1][j−1]+ave(nums[start,i])dp[i][j]=dp[start-1][j-1] + ave(nums[start,i])dp[i][j]=dp[start1][j1]+ave(nums[start,i])

    • 以上两个公式可合并为一个:当start=istart=istart=i时,新的一组只有nums[i]nums[i]nums[i]一个元素
      dp[i][j]=max(dp[i][j],dp[start−1][j−1]+ave(nums[start,i])) dp[i][j] = max(dp[i][j],dp[start-1][j-1]+ave(nums[start,i])) dp[i][j]=max(dp[i][j],dp[start1][j1]+ave(nums[start,i]))

      start∈[1,i] start\in[1,i] start[1,i]

    • preSum[i]preSum[i]preSum[i]为前i−1i-1i1个数组元素之和,因此连续子数组nums[start,i]nums[start,i]nums[start,i]的平均值可表示为
      ave=(preSum[i+1]−preSum[start−1])/(i−start+1) ave=(preSum[i+1] - preSum[start-1])/(i-start+1) ave=(preSum[i+1]preSum[start1])/(istart+1)

  3. dp数组如何初始化

    特殊处理只分为一组时的dp[i][1]

    • dp[0][1]=nums[0]dp[0][1] = nums[0]dp[0][1]=nums[0]
    • dp[1][1]=(nums[0]+nums[1])/2dp[1][1] = (nums[0] + nums[1])/2dp[1][1]=(nums[0]+nums[1])/2
    • dp[i][1]=(nums[0]+nums[1]+……+nums[i])/(i+1)=preSum[i+1]/(i+1)dp[i][1] = (nums[0] + nums[1] + ……+nums[i])/(i+1) = preSum[i+1]/(i+1)dp[i][1]=(nums[0]+nums[1]+……+nums[i])/(i+1)=preSum[i+1]/(i+1)
  4. 确定遍历顺序

    由dp公式可知,外层正序遍历i,内层正序遍历子数组个数j,最后遍历第j个子数组的可能的起始位置start

  5. 举例推导dp数组

  • 代码 :

    • 下标均从1开始遍历,代码较清晰
      • dp[i+1][j]:将长度为[0,i]的nums分割为j个连续子数组时,得到的最大分数
      • preSum[i+1]对应前i个nums
    • 只有数组元素大于等于j时才有可能划分为j个连续子数组
    class Solution {
        public double largestSumOfAverages(int[] nums, int k) {
            int len = nums.length;
            double[][] dp = new double[len + 1][k + 1];
            double[] preSum = new double[len + 1];
            for (int i = 1; i <= len; i++){
                preSum[i] = preSum[i-1] + nums[i-1];
            }
            for (int i = 1; i <= len; i++){
                dp[i][1] = preSum[i] / i;
            }
            for (int i = 1; i <= len; i++){
                for (int j = 2; j <= k && j <= i; j++){
                    // 自成一组 与start = i 合并
                    // dp[i][j] = Math.max(dp[i][j],dp[i-1][j-1] + nums[i-1]);
                    // 并入前一组nums[start,i]
                    for (int start = 2; start <= i; start++){
                        double ave = (preSum[i] - preSum[start - 1]) / (i - start + 1);
                        dp[i][j] = Math.max(dp[i][j],dp[start - 1][j - 1] + ave);
                    }
                }
            }
            return dp[len][k];
        }
    }
    
    • 复杂度

      • 时间复杂度:O(n2∗k)O(n^2*k)O(n2k)
      • 空间复杂度:O(n∗k)O(n*k)O(nk)
  • 代码:很丑

    dp[i][j]:将长度为[0,i]的nums分割为j个连续子数组时,得到的最大分数

    class Solution {
        public double largestSumOfAverages(int[] nums, int k) {
            int len = nums.length;
            double[][] dp = new double[len][k + 1];
            double[] preSum = new double[len + 1];
            for (int i = 0; i < len; i++){
                preSum[i+1] = preSum[i] + nums[i];
            }
            for (int i = 0; i < len; i++){
                dp[i][1] = preSum[i + 1] / (i+1);
            }
            for (int i = 0; i < len; i++){
                for (int j = 2; j <= k && j <= i + 1; j++){
                    // 自成一组 与start = i 合并
                    // dp[i][j] = Math.max(dp[i][j],dp[i-1][j-1] + nums[i-1]);
                    // 并入前一组nums[start,i]
                    for (int start = 1; start <= i; start++){
                        double ave = (preSum[i + 1] - preSum[start]) / (i - start + 1);
                        dp[i][j] = Math.max(dp[i][j],dp[start - 1][j - 1] + ave);
                    }
                }
            }
            return dp[len - 1][k];
        }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值