leetcode
leetcode 601 ~ 650
三个无重叠子数组的最大和

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

难度:

标签:

题目描述

代码结果

运行时间: 48 ms, 内存: 18.0 MB


/*
 * 思路:
 * 1. 计算每个长度为 k 的子数组的和,使用 Streams 存储在 sums 列表中。
 * 2. 使用 left 和 right 数组记录可以得到最大和的左边和右边的子数组起始位置。
 * 3. 遍历 middle 子数组的起始位置,寻找使得总和最大的三个子数组组合。
 */
 
import java.util.*;
import java.util.stream.*;
 
public class Solution {
    public int[] maxSumOfThreeSubarrays(int[] nums, int k) {
        int n = nums.length;
        List<Integer> sums = IntStream.range(0, n - k + 1)
                                      .map(i -> IntStream.range(i, i + k).map(j -> nums[j]).sum())
                                      .boxed()
                                      .collect(Collectors.toList());
 
        int[] left = new int[n - k + 1];
        int bestLeft = 0;
        for (int i = 0; i <= n - k; i++) {
            if (sums.get(i) > sums.get(bestLeft)) bestLeft = i;
            left[i] = bestLeft;
        }
 
        int[] right = new int[n - k + 1];
        int bestRight = n - k;
        for (int i = n - k; i >= 0; i--) {
            if (sums.get(i) >= sums.get(bestRight)) bestRight = i;
            right[i] = bestRight;
        }
 
        int[] result = new int[3];
        int maxSum = 0;
        for (int mid = k; mid <= n - 2 * k; mid++) {
            int l = left[mid - k];
            int r = right[mid + k];
            int total = sums.get(l) + sums.get(mid) + sums.get(r);
            if (total > maxSum) {
                maxSum = total;
                result[0] = l;
                result[1] = mid;
                result[2] = r;
            }
        }
        return result;
    }
}

解释

方法:

本题解采用滑动窗口和动态规划的思想。首先,使用三个变量s1, s2, s3分别维护三个长度为k的子数组的和。然后,从第k * 2个元素开始遍历数组,每次向右移动一个元素,更新三个子数组的和。同时,使用mx1和mx12分别记录遍历过程中第一个子数组和前两个子数组的最大和,以及对应的起始下标idx1和idx12。最后,每次遍历时比较当前三个子数组的总和是否大于之前记录的最大值s,如果大于,则更新s和结果ans。

时间复杂度:

O(n)

空间复杂度:

O(1)

代码细节讲解

🦆
如何保证三个子数组互不重叠?在代码中是如何实现的?
在代码中,通过固定每个子数组的长度为k,并且按照严格的位置偏移来更新每个子数组的和来保证子数组互不重叠。具体来说,第一个子数组从索引0开始,到k-1结束;第二个子数组从索引k开始,到2k-1结束;第三个子数组从索引2k开始,到3k-1结束。随着数组的遍历,每个子数组都严格按照k个元素的长度向右滑动,通过这种固定长度和起始位置的偏移量控制,保证了子数组之间不会重叠。
🦆
在更新子数组和的同时,如何保证每次更新都是基于最新的子数组起始位置?
在代码中,每次更新子数组和时,都会加上新进入窗口的元素,并移除窗口最左侧的元素。这种操作确保了每次窗口(或子数组)的和都是基于最新的k个连续元素。例如,s1更新时加上nums[i - k * 2]并减去nums[i - k * 3 + 1],这样s1始终保持为最新的从i - 2k + 1到i - k * 2的子数组的和。
🦆
在题解中提到,如果当前三个子数组的总和大于之前记录的最大值时才更新结果,那么是否有可能出现多个总和相同但起始位置不同的情况?如果有,如何确保返回字典序最小的结果?
确实可能出现多个总和相同但起始位置不同的情况。在实现中,通过只在找到更大的总和时更新结果来确保只保存最大值。如果遇到相同的总和,由于我们不更新结果,所以自然保留了最先找到的(即字典序最小的)结果。这是因为数组是按索引顺序遍历的,第一次遇到的相同最大总和的起始位置自然是最小的。
🦆
题解中提到使用了滑动窗口和动态规划的思想,请问这里的动态规划是如何应用的?具体是在哪些部分体现的?
动态规划在本题中主要体现在逐步构建最优解,即不断更新记录最大子数组和的过程中。使用mx1来记录到当前位置为止,最大的第一个子数组的和。进一步,使用mx12来记录到当前位置为止,最大的前两个子数组的累加和。这种从前向后逐步优化求解的方法,利用以往的计算结果来简化未来的计算,是动态规划的典型应用。每步更新mx1和mx12都是基于之前的状态进行的,确保了每步都是基于最优子结构的决策。

相关问题

买卖股票的最佳时机 III

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

 

示例 1:

输入:prices = [3,3,5,0,0,3,1,4]
输出:6
解释:在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
     随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。

示例 2:

输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。   
     注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。   
     因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3:

输入:prices = [7,6,4,3,1] 
输出:0 
解释:在这个情况下, 没有交易完成, 所以最大利润为 0。

示例 4:

输入:prices = [1]
输出:0

 

提示:

  • 1 <= prices.length <= 105
  • 0 <= prices[i] <= 105