三个无重叠子数组的最大和

689. 三个无重叠子数组的最大和

给定数组 nums 由正整数组成,找到三个互不重叠的子数组的最大和。

每个子数组的长度为k,我们要使这3*k个项的和最大化。

返回每个区间起始索引的列表(索引从 0 开始)。如果有多个结果,返回字典序最小的一个。

示例
输入: [1,2,1,2,6,7,5,1], 2
输出: [0, 3, 5]
解释: 子数组 [1, 2], [2, 6], [7, 5] 对应的起始索引为 [0, 3, 5]。
我们也可以取 [2, 1], 但是结果 [1, 3, 5] 在字典序上更大。
注意:
  • nums.length的范围在[1, 20000]之间。
  • nums[i]的范围在[1, 65535]之间。
  • k的范围在[1, floor(nums.length / 3)]之间。

题解

  1. 假设nums的长度为len,求出nums数组所有连续k个子序列的和的数组sums,已知nums的长度为len,sums的长度则为len-k+1,用t表示。
  2. 那么题目就可以转化为求sums数组,选3个数,这三个数必须满足,相邻间隔不小于k,当和最大时,脚标最小的三个数。
  3. 定义dp数组,dp[i][j]表示的是从[0, i]的范围内,选择j个数得到的最大和(j个数的相邻间隔为k)
  4. 定义path数组,path[i][j]表示的是当得到dp[i][j]的时候,选择的最后一个数的脚标
定义dp数组的状态转移方程

sums[i]分为选择 和 不选择
当选择sums[i] 时 ,dp[i][j] = dp[i-k][j-1] + sums[i];
当不选择sums[i] 时, dp[i][j] = dp[i-1][j];
最终求最大值: dp[i][j] = max{dp[i-k][j-1] + sums[i], dp[i-1][j]}

初始化dp数组需要注意的是
要在sums数组中选择j个数,并且满足相邻的数间隔不小于k,那么sums的长度必须满足t > (j-1)*k,由于i代表的是脚标,所以只有在i >= (j-1)*k的时候,dp数组才有意义
边界问题:当k=1时,比较特殊,可以单独初始化,因为k=1时,哪怕sums只有一个数,也可以选择,但是如果k>1时,比如k=2,当sums有2个数的时候,其实是无法选择的。

以 [1,2,1,2,6,7,5,1], 2 输入为例,
sums数组为:
[3, 3, 3, 8, 13, 12, 6]
得到的dp数组和path数组分别为:

dp0123
00300
10300
20360
308110
40131619
50132023
60132023
path0123
00000
10000
20020
30330
40444
50455
60455

最后得到path,那么path[t-1][3]就是从[0, t-1]的范围内,选择3个数和最大时,最后一个数的脚标,用res[2]来表示,那么前一个的脚标一定是[0, res[2]-k]的范围内,选择2个数和最大时,最后一个数的脚标,依次类推,得到第一个脚标。

代码如下:
class Solution {
    public int[] maxSumOfThreeSubarrays(int[] nums, int k) {
        int len = nums.length;
        int t = len-k+1;
        int[] sums = new int[t];
        int[] res = new int[3];
        for(int i=0; i<k; i++){
            sums[0] += nums[i];
        }
        for(int i=1;i<t;i++){
            sums[i] = sums[i-1]-nums[i-1]+nums[i+k-1];
        }
        int[][] dp = new int[t][4];
        int[][] path = new int[t][4];
        dp[0][1] = sums[0];
        path[0][1] = 0;
        for(int i = 1;i<t;i++){
            dp[i][1] = Math.max(dp[i-1][1], sums[i]);
            path[i][1] = sums[i] > dp[i-1][1] ? i : path[i-1][1];
        }
        for(int i=0;i<t;i++){
            for(int j=2;j<4;j++){
                if (i >= (j-1)*k){
                    int select = dp[i-k][j-1] + sums[i];
                    int notSelect = dp[i-1][j];
                    dp[i][j] = Math.max(select, notSelect);
                    path[i][j] = path[i-1][j];
                    if(select > notSelect){
                        path[i][j] = i;
                    }
                }
            }
        }

        res[2] = path[t-1][3];
        res[1] = path[res[2]-k][2];
        res[0] = path[res[1]-k][1];
        return res;
    }

该题还可以引申一下,把找3个子数组,引申到找n个子数组:

class Solution {
    public int[] maxSumOfThreeSubarrays(int[] nums, int k) {
        return maxSumOfNSubarrays(nums,k,3);
    }

    public int[] maxSumOfNSubarrays(int[] nums, int k, int n){
        int len = nums.length;
        int t = len-k+1;
        int[] sums = new int[t];
        int[] res = new int[n];
        for(int i=0; i<k; i++){
            sums[0] += nums[i];
        }
        for(int i=1;i<t;i++){
            sums[i] = sums[i-1]-nums[i-1]+nums[i+k-1];
        }
        int[][] dp = new int[t][n+1];
        int[][] path = new int[t][n+1];
        dp[0][1] = sums[0];
        path[0][1] = 0;
        for(int i = 1;i<t;i++){
            dp[i][1] = Math.max(dp[i-1][1], sums[i]);
            path[i][1] = sums[i] > dp[i-1][1] ? i : path[i-1][1];
        }
        for(int i=0;i<t;i++){
            for(int j=2;j<n+1;j++){
                if (i >= (j-1)*k){
                    int select = dp[i-k][j-1] + sums[i];
                    int notSelect = dp[i-1][j];
                    dp[i][j] = Math.max(select, notSelect);
                    path[i][j] = path[i-1][j];
                    if(select > notSelect){
                        path[i][j] = i;
                    }
                }
            }
        }

        int row = t-1;
        int col = n;
        res[n-1] = path[row][col--];
        for(int i=n-2; i>=0; i--){
            res[i] = path[res[i+1]-k][col--];
        }
        return res;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值