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

给你一个整数数组 nums 和一个整数 k ,找出三个长度为 k 、互不重叠、且 3 k 项的和最大的子数组,并返回这三个子数组。
以下标的数组形式返回结果,数组中的每一项分别指示每个子数组的起始位置(下标从 *0
开始)。如果有多个结果,返回字典序最小的一个。

示例 1:
输入:nums = [1,2,1,2,6,7,5,1], k = 2 输出:[0,3,5] 解释:子数组 [1, 2], [2, 6], [7, 5] 对应的起始下标为 [0, 3, 5]。 也可以取 [2, 1], 但是结果 [1, 3, 5] 在字典序上更大。
示例 2:
输入:nums = [1,2,1,2,1,2,1,2,1], k = 2 输出:[0,2,4]

提示:

  • 1 <= nums.length <= 2 * 104
  • 1 <= nums[i] < 216
  • 1 <= k <= floor(nums.length / 3)

思路:
方法1:线性求解
求数组中最大值元素 -> 最大和次大值元素 -> …
求数组中定长区间和的最大值 -> 求数组中k个定长区间的最大值
都是一样的套路
方法2:DP + 前缀和
状态表示:f[i][j]表示考虑前i个元素中共有j个定长为k的区间的最大和
状态转移:考虑第i个元素,选或不选两种情况。
若选择第i个元素,f[i][j] = f[i - k][j - 1]
若不选第i个元素,f[i][j] = f[i - 1][j]
综上:f[i][j] = max(f[i - 1][j], f[i - k][j - 1])
考虑到本题需要输出选择的结果而不是最大值,并且是字典序最小的一个。
如果按照正常顺序从前往后DP,倒推的时候只能保证结果是最大的字典序,因为f[n - 1][3]只能由两个情况转移过来,f[n - 2][3]或者f[n - k - 1][2]。但该做法只能确保回溯出字典序最大的方案是正确(当两个可能的前驱节点都能转移到f[i][j] 时优先采纳靠后的位置)
所以需要换一下DP的方向,从后往前DP,这样从前往后寻找的方案就是字典序最小的。

  1. //方法1:线性求解
  2. class Solution {
  3. public int[] maxSumOfThreeSubarrays(int[] nums, int len) {
  4. int max1 = 0, idx1 = -1;
  5. int max2 = 0, idx21 = -1, idx22 = -1;
  6. int max3 = 0;
  7. int[] ans = new int[3];
  8. int s1 = 0, s2 = 0, s3 = 0;
  9. for (int i = 0, j = len, k = len * 2; k < nums.length; i++, j++, k++) {
  10. s1 += nums[i];
  11. s2 += nums[j];
  12. s3 += nums[k];
  13. if (s1 > max1) {
  14. max1 = s1;
  15. idx1 = i - len + 1;
  16. }
  17. if (max1 + s2 > max2) {
  18. max2 = max1 + s2;
  19. idx21 = idx1;
  20. idx22 = j - len + 1;
  21. }
  22. if (max2 + s3 > max3) {
  23. max3 = max2 + s3;
  24. ans[0] = idx21;
  25. ans[1] = idx22;
  26. ans[2] = k - len + 1;
  27. }
  28. if (i >= len - 1) {
  29. s1 -= nums[i - len + 1];
  30. s2 -= nums[j - len + 1];
  31. s3 -= nums[k - len + 1];
  32. }
  33. }
  34. return ans;
  35. }
  36. }
  1. //方法2:DP
  2. class Solution {
  3. public int[] maxSumOfThreeSubarrays(int[] nums, int k) {
  4. int n = nums.length;
  5. int[] pre = new int[n + 1];
  6. for (int i = 1; i <= n; i++)
  7. pre[i] = pre[i - 1] + nums[i - 1];
  8. int[][] f = new int[n + 1][4];
  9. for (int i = n - k; i >= 0; i--) {
  10. for (int j = 1; j <= 3; j++) {
  11. f[i][j] = Math.max(f[i + 1][j], f[i + k][j - 1] + pre[i + k] - pre[i]);
  12. }
  13. }
  14. int y = 3;
  15. int idx = 0;
  16. int[] ans = new int[3];
  17. while (y > 0) {
  18. while (f[idx][y] != f[idx + k][y - 1] + pre[idx + k] - pre[idx])
  19. idx++;
  20. ans[3 - y] = idx;
  21. y--;
  22. idx += k;
  23. }
  24. return ans;
  25. }
  26. }