【1031. 两个非重叠子数组的最大和】

来源:力扣(LeetCode)

描述:

给你一个整数数组 nums 和两个整数 firstLensecondLen,请你找出并返回两个非重叠 子数组 中元素的最大和,长度分别为 firstLensecondLen

长度为 firstLen 的子数组可以出现在长为 secondLen 的子数组之前或之后,但二者必须是不重叠的。

子数组是数组的一个 连续 部分。

示例 1:

输入:nums = [0,6,5,2,2,5,1,9,4], firstLen = 1, secondLen = 2
输出:20
解释:子数组的一种选择中,[9] 长度为 1[6,5] 长度为 2

示例 2:

输入:nums = [3,8,1,3,2,1,8,9,0], firstLen = 3, secondLen = 2
输出:29
解释:子数组的一种选择中,[3,8,1] 长度为 3[8,9] 长度为 2

示例 3:

输入:nums = [2,1,5,6,0,9,5,0,3,8], firstLen = 4, secondLen = 3
输出:31
解释:子数组的一种选择中,[5,6,0,9] 长度为 4[0,3,8] 长度为 3

提示:

  • 1 <= firstLen, secondLen <= 1000
  • 2 <= firstLen + secondLen <= 1000
  • firstLen + secondLen <= nums.length <= 1000
  • 0 <= nums[i] <= 1000

方法:动态规划 + 滑动窗口

思路与算法

首先题目给出一个长度为 n 的数组 nums。现在我们需要返回两个长度分别为 firstLen 和 secondLen 的非重叠的子数组的最大和,firstLen + secondLen ≤ n,其中这两段子数组的前后顺序没有要求。

由于两段子数组的前后顺序没有区别,所以现在不妨设长度为 firstLen 的子数组在长度为 secondLen 的子数组前来计算此时的两段子数组的最大和。首先我们用 nums[i, j] 来表示 nums[i], nums[i + 1], …, nums[j − 1] 这一段子数组,并记 sum(nums[l, r]) 表示子数组 nums[l, r] 的和,dp[i] 为 nums[0, i + 1] 中长度为 firstLen 的最大子数组和,若不存在长度为 firstLen 的子数组则为 0。那么对于某一段长度为 secondLen 的子数组 nums[j, j + secondLen],0 ≤ j < j + secondLen ≤ n,所以此时的两个数组的最大和为

dp[j − 1] + sum(nums[j, j + secondLen])

又因为

dp[i] = max{ dp[i − 1], sum(nums[i + 1 − firstLen, i + 1]) }

由于现在长度为 secondLen 在长度为 firstLen 的后面,所以用两个大小为 firstLen 和 secondLen 的滑动窗口分别从位置 0 和 firstLen 同时开始从左往右滑动,并在过程中维护窗口中的和。因为对于 ∀i < firstLen − 1,有 dp[i] = 0,并当 i = firstLen − 1 时为初始第一个窗口的和。那么在两个窗口从左到右移动的过程中,通过移动第一个窗口来更新 dp 值,通过第二个窗口来计算此时的最大和,并记录移动过程中的最大值即可。

同理我们可以得到当 secondLen 的子数组在长度为 firstLen 的子数组前时,两段子数组的最大和,两种情况取较大值即为最终的答案。由于 dp[i] 的求解只与 dp[i − 1] 有关,所以在实现的过程中我们可以通过「滚动数组」来进行空间优化。

代码:

class Solution {
public:
    int help(vector<int>& nums, int firstLen, int secondLen) {
        int suml = accumulate(nums.begin(), nums.begin() + firstLen, 0);
        int maxSumL = suml;
        int sumr = accumulate(nums.begin() + firstLen, nums.begin() + firstLen + secondLen, 0);
        int res = maxSumL + sumr;
        for (int i = firstLen + secondLen, j = firstLen; i < nums.size(); ++i, ++j) {
            suml += nums[j] - nums[j - firstLen];
            maxSumL = max(maxSumL, suml);
            sumr += nums[i] - nums[i - secondLen];
            res = max(res, maxSumL + sumr);
        }
        return res;
    }

    int maxSumTwoNoOverlap(vector<int>& nums, int firstLen, int secondLen) {
        return max(help(nums, firstLen, secondLen), help(nums, secondLen, firstLen));
    }
};

执行用时:0 ms, 在所有 C++ 提交中击败了100.00%的用户
内存消耗:8.1 MB, 在所有 C++ 提交中击败了92.58%的用户
复杂度分析
时间复杂度:O(n),其中 n 为数组 nums 的长度。
空间复杂度:O(1),仅使用常量空间。
author:LeetCode-Solution

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千北@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值