给你一个整数数组 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)
要计算三个无重叠子数组的最大和,我们可以枚举第三个子数组的位置,同时维护前两个无重叠子数组的最大和及其位置。
要计算两个无重叠子数组的最大和,我们可以枚举第二个子数组的位置,同时维护第一个子数组的最大和及其位置。
因此,我们首先来解决单个子数组的最大和问题,然后解决两个无重叠子数组的最大和问题,最后解决三个无重叠子数组的最大和问题。
前言一:单个子数组的最大和 我们用滑动窗口来解决这一问题。
滑动窗口是数组/字符串问题中常用的抽象概念。窗口 通常是指在数组/字符串中由开始和结束索引定义的一系列元素的集合,即闭区间 [i,j]。而滑动窗口 是指可以将两个边界向某一方向「滑动」的窗口。例如,我们将 [i,j] 向右滑动 1 个元素,它将变为 [i+1,j+1]。
设 sum}_1 为大小为 k 的窗口的元素和,当窗口从 [i-k+1,i] 向右滑动 1 个元素后,sum}_1 增加了 nums}[i+1],减少了 nums}[i-k+1]。据此我们可以 O(1) 地计算出向右滑动 1 个元素后的窗口的元素和。
我们从 [0,k-1] 开始,不断地向右滑动窗口,直至窗口右端点到达数组末尾时停止。统计这一过程中的 sum}_1 的最大值(记作 maxSum}_1)及其对应位置。
[sol1-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 class Solution : def maxSumOfOneSubarray (self, nums: List [int ], k: int ) -> List [int ]: ans = [] sum1, maxSum1 = 0 , 0 for i, num in enumerate (nums): sum1 += num if i >= k - 1 : if sum1 > maxSum1: maxSum1 = sum1 ans = [i - k + 1 ] sum1 -= nums[i - k + 1 ] return ans
[sol1-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution {public : vector<int > maxSumOfOneSubarray (vector<int > &nums, int k) { vector<int > ans; int sum1 = 0 , maxSum1 = 0 ; for (int i = 0 ; i < nums.size (); ++i) { sum1 += nums[i]; if (i >= k - 1 ) { if (sum1 > maxSum1) { maxSum1 = sum1; ans = {i - k + 1 }; } sum1 -= nums[i - k + 1 ]; } } return ans; } };
[sol1-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution { public int [] maxSumOfOneSubarray(int [] nums, int k) { int [] ans = new int [1 ]; int sum1 = 0 , maxSum1 = 0 ; for (int i = 0 ; i < nums.length; ++i) { sum1 += nums[i]; if (i >= k - 1 ) { if (sum1 > maxSum1) { maxSum1 = sum1; ans[0 ] = i - k + 1 ; } sum1 -= nums[i - k + 1 ]; } } return ans; } }
[sol1-C#] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Solution { public int [] MaxSumOfOneSubarray (int [] nums, int k ) { int [] ans = new int [1 ]; int sum1 = 0 , maxSum1 = 0 ; for (int i = 0 ; i < nums.Length; ++i) { sum1 += nums[i]; if (i >= k - 1 ) { if (sum1 > maxSum1) { maxSum1 = sum1; ans[0 ] = i - k + 1 ; } sum1 -= nums[i - k + 1 ]; } } return ans; } }
[sol1-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 func maxSumOfOneSubarray (nums []int , k int ) (ans []int ) { var sum1, maxSum1 int for i, num := range nums { sum1 += num if i >= k-1 { if sum1 > maxSum1 { maxSum1 = sum1 ans = []int {i - k + 1 } } sum1 -= nums[i-k+1 ] } } return }
[sol1-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 var maxSumOfOneSubarray = function (nums, k ) { let ans = []; let sum1 = 0 , maxSum1 = 0 ; for (let i = 0 ; i < nums.length ; ++i) { sum1 += nums[i]; if (i >= k - 1 ) { if (sum1 > maxSum1) { maxSum1 = sum1; ans = [i - k + 1 ]; } sum1 -= nums[i - k + 1 ]; } } return ans; };
前言二:两个无重叠子数组的最大和 我们使用两个大小为 k 的滑动窗口。设 sum}_1 为第一个滑动窗口的元素和,该滑动窗口从 [0,k-1] 开始;sum}_2 为第二个滑动窗口的元素和,该滑动窗口从 [k,2k-1] 开始。
我们同时向右滑动这两个窗口,并维护 sum}_1 的最大值 maxSum}_1 及其对应位置。每次滑动时,计算当前 maxSum}_1 与 sum}_2 之和。统计这一过程中的 maxSum}_1+\textit{sum}2 的最大值(记作 maxSum} {12)及其对应位置。
[sol2-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution : def maxSumOfTwoSubarrays (self, nums: List [int ], k: int ) -> List [int ]: ans = [] sum1, maxSum1, maxSum1Idx = 0 , 0 , 0 sum2, maxSum12 = 0 , 0 for i in range (k, len (nums)): sum1 += nums[i - k] sum2 += nums[i] if i >= k * 2 - 1 : if sum1 > maxSum1: maxSum1 = sum1 maxSum1Idx = i - k * 2 + 1 if maxSum1 + sum2 > maxSum12: maxSum12 = maxSum1 + sum2 ans = [maxSum1Idx, i - k + 1 ] sum1 -= nums[i - k * 2 + 1 ] sum2 -= nums[i - k + 1 ] return ans
[sol2-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution {public : vector<int > maxSumOfTwoSubarrays (vector<int > &nums, int k) { vector<int > ans; int sum1 = 0 , maxSum1 = 0 , maxSum1Idx = 0 ; int sum2 = 0 , maxSum12 = 0 ; for (int i = k; i < nums.size (); ++i) { sum1 += nums[i - k]; sum2 += nums[i]; if (i >= k * 2 - 1 ) { if (sum1 > maxSum1) { maxSum1 = sum1; maxSum1Idx = i - k * 2 + 1 ; } if (maxSum1 + sum2 > maxSum12) { maxSum12 = maxSum1 + sum2; ans = {maxSum1Idx, i - k + 1 }; } sum1 -= nums[i - k * 2 + 1 ]; sum2 -= nums[i - k + 1 ]; } } return ans; } };
[sol2-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Solution { public int [] maxSumOfTwoSubarrays(int [] nums, int k) { int [] ans = new int [2 ]; int sum1 = 0 , maxSum1 = 0 , maxSum1Idx = 0 ; int sum2 = 0 , maxSum12 = 0 ; for (int i = k; i < nums.length; ++i) { sum1 += nums[i - k]; sum2 += nums[i]; if (i >= k * 2 - 1 ) { if (sum1 > maxSum1) { maxSum1 = sum1; maxSum1Idx = i - k * 2 + 1 ; } if (maxSum1 + sum2 > maxSum12) { maxSum12 = maxSum1 + sum2; ans[0 ] = maxSum1Idx; ans[1 ] = i - k + 1 ; } sum1 -= nums[i - k * 2 + 1 ]; sum2 -= nums[i - k + 1 ]; } } return ans; } }
[sol2-C#] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class Solution { public int [] MaxSumOfTwoSubarrays (int [] nums, int k ) { int [] ans = new int [2 ]; int sum1 = 0 , maxSum1 = 0 , maxSum1Idx = 0 ; int sum2 = 0 , maxSum12 = 0 ; for (int i = k; i < nums.Length; ++i) { sum1 += nums[i - k]; sum2 += nums[i]; if (i >= k * 2 - 1 ) { if (sum1 > maxSum1) { maxSum1 = sum1; maxSum1Idx = i - k * 2 + 1 ; } if (maxSum1 + sum2 > maxSum12) { maxSum12 = maxSum1 + sum2; ans[0 ] = maxSum1Idx; ans[1 ] = i - k + 1 ; } sum1 -= nums[i - k * 2 + 1 ]; sum2 -= nums[i - k + 1 ]; } } return ans; } }
[sol2-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func maxSumOfTwoSubarrays (nums []int , k int ) (ans []int ) { var sum1, maxSum1, maxSum1Idx int var sum2, maxSum12 int for i := k; i < len (nums); i++ { sum1 += nums[i-k] sum2 += nums[i] if i >= k*2 -1 { if sum1 > maxSum1 { maxSum1 = sum1 maxSum1Idx = i - k*2 + 1 } if maxSum1+sum2 > maxSum12 { maxSum12 = maxSum1 + sum2 ans = []int {maxSum1Idx, i - k + 1 } } sum1 -= nums[i-k*2 +1 ] sum2 -= nums[i-k+1 ] } } return }
[sol2-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 const maxSumOfTwoSubarrays = (nums, k ) => { const ans = [0 , 0 ]; let sum1 = 0 , maxSum1 = 0 , maxSum1Idx = 0 ; let sum2 = 0 , maxSum12 = 0 ; for (let i = k; i < nums.length ; ++i) { sum1 += nums[i - k]; sum2 += nums[i]; if (i >= k * 2 - 1 ) { if (sum1 > maxSum1) { maxSum1 = sum1; maxSum1Idx = i - k * 2 + 1 ; } if (maxSum1 + sum2 > maxSum12) { maxSum12 = maxSum1 + sum2; ans[0 ] = maxSum1Idx; ans[1 ] = i - k + 1 ; } sum1 -= nums[i - k * 2 + 1 ]; sum2 -= nums[i - k + 1 ]; } } return ans; }
方法一:滑动窗口 回到本题,我们使用三个大小为 k 的滑动窗口。设 sum}_1 为第一个滑动窗口的元素和,该滑动窗口从 [0,k-1] 开始;sum}_2 为第二个滑动窗口的元素和,该滑动窗口从 [k,2k-1] 开始;sum}_3 为第三个滑动窗口的元素和,该滑动窗口从 [2k,3k-1] 开始。
我们同时向右滑动这三个窗口,按照前言二的方法并维护 maxSum}{12 及其对应位置。每次滑动时,计算当前 maxSum} {12 与 sum}3 之和。统计这一过程中的 maxSum} {12}+\textit{sum}_3 的最大值及其对应位置。
对于题目要求的最小字典序,由于我们是从左向右遍历的,并且仅当元素和超过最大元素和时才修改最大元素和,从而保证求出来的下标列表是字典序最小的。
[sol3-Python3] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution : def maxSumOfThreeSubarrays (self, nums: List [int ], k: int ) -> List [int ]: ans = [] sum1, maxSum1, maxSum1Idx = 0 , 0 , 0 sum2, maxSum12, maxSum12Idx = 0 , 0 , () sum3, maxTotal = 0 , 0 for i in range (k * 2 , len (nums)): sum1 += nums[i - k * 2 ] sum2 += nums[i - k] sum3 += nums[i] if i >= k * 3 - 1 : if sum1 > maxSum1: maxSum1 = sum1 maxSum1Idx = i - k * 3 + 1 if maxSum1 + sum2 > maxSum12: maxSum12 = maxSum1 + sum2 maxSum12Idx = (maxSum1Idx, i - k * 2 + 1 ) if maxSum12 + sum3 > maxTotal: maxTotal = maxSum12 + sum3 ans = [*maxSum12Idx, i - k + 1 ] sum1 -= nums[i - k * 3 + 1 ] sum2 -= nums[i - k * 2 + 1 ] sum3 -= nums[i - k + 1 ] return ans
[sol3-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 class Solution {public : vector<int > maxSumOfThreeSubarrays (vector<int > &nums, int k) { vector<int > ans; int sum1 = 0 , maxSum1 = 0 , maxSum1Idx = 0 ; int sum2 = 0 , maxSum12 = 0 , maxSum12Idx1 = 0 , maxSum12Idx2 = 0 ; int sum3 = 0 , maxTotal = 0 ; for (int i = k * 2 ; i < nums.size (); ++i) { sum1 += nums[i - k * 2 ]; sum2 += nums[i - k]; sum3 += nums[i]; if (i >= k * 3 - 1 ) { if (sum1 > maxSum1) { maxSum1 = sum1; maxSum1Idx = i - k * 3 + 1 ; } if (maxSum1 + sum2 > maxSum12) { maxSum12 = maxSum1 + sum2; maxSum12Idx1 = maxSum1Idx; maxSum12Idx2 = i - k * 2 + 1 ; } if (maxSum12 + sum3 > maxTotal) { maxTotal = maxSum12 + sum3; ans = {maxSum12Idx1, maxSum12Idx2, i - k + 1 }; } sum1 -= nums[i - k * 3 + 1 ]; sum2 -= nums[i - k * 2 + 1 ]; sum3 -= nums[i - k + 1 ]; } } return ans; } };
[sol3-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 class Solution { public int [] maxSumOfThreeSubarrays(int [] nums, int k) { int [] ans = new int [3 ]; int sum1 = 0 , maxSum1 = 0 , maxSum1Idx = 0 ; int sum2 = 0 , maxSum12 = 0 , maxSum12Idx1 = 0 , maxSum12Idx2 = 0 ; int sum3 = 0 , maxTotal = 0 ; for (int i = k * 2 ; i < nums.length; ++i) { sum1 += nums[i - k * 2 ]; sum2 += nums[i - k]; sum3 += nums[i]; if (i >= k * 3 - 1 ) { if (sum1 > maxSum1) { maxSum1 = sum1; maxSum1Idx = i - k * 3 + 1 ; } if (maxSum1 + sum2 > maxSum12) { maxSum12 = maxSum1 + sum2; maxSum12Idx1 = maxSum1Idx; maxSum12Idx2 = i - k * 2 + 1 ; } if (maxSum12 + sum3 > maxTotal) { maxTotal = maxSum12 + sum3; ans[0 ] = maxSum12Idx1; ans[1 ] = maxSum12Idx2; ans[2 ] = i - k + 1 ; } sum1 -= nums[i - k * 3 + 1 ]; sum2 -= nums[i - k * 2 + 1 ]; sum3 -= nums[i - k + 1 ]; } } return ans; } }
[sol3-C#] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public class Solution { public int [] MaxSumOfThreeSubarrays (int [] nums, int k ) { int [] ans = new int [3 ]; int sum1 = 0 , maxSum1 = 0 , maxSum1Idx = 0 ; int sum2 = 0 , maxSum12 = 0 , maxSum12Idx1 = 0 , maxSum12Idx2 = 0 ; int sum3 = 0 , maxTotal = 0 ; for (int i = k * 2 ; i < nums.Length; ++i) { sum1 += nums[i - k * 2 ]; sum2 += nums[i - k]; sum3 += nums[i]; if (i >= k * 3 - 1 ) { if (sum1 > maxSum1) { maxSum1 = sum1; maxSum1Idx = i - k * 3 + 1 ; } if (maxSum1 + sum2 > maxSum12) { maxSum12 = maxSum1 + sum2; maxSum12Idx1 = maxSum1Idx; maxSum12Idx2 = i - k * 2 + 1 ; } if (maxSum12 + sum3 > maxTotal) { maxTotal = maxSum12 + sum3; ans[0 ] = maxSum12Idx1; ans[1 ] = maxSum12Idx2; ans[2 ] = i - k + 1 ; } sum1 -= nums[i - k * 3 + 1 ]; sum2 -= nums[i - k * 2 + 1 ]; sum3 -= nums[i - k + 1 ]; } } return ans; } }
[sol3-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 func maxSumOfThreeSubarrays (nums []int , k int ) (ans []int ) { var sum1, maxSum1, maxSum1Idx int var sum2, maxSum12, maxSum12Idx1, maxSum12Idx2 int var sum3, maxTotal int for i := k * 2 ; i < len (nums); i++ { sum1 += nums[i-k*2 ] sum2 += nums[i-k] sum3 += nums[i] if i >= k*3 -1 { if sum1 > maxSum1 { maxSum1 = sum1 maxSum1Idx = i - k*3 + 1 } if maxSum1+sum2 > maxSum12 { maxSum12 = maxSum1 + sum2 maxSum12Idx1, maxSum12Idx2 = maxSum1Idx, i-k*2 +1 } if maxSum12+sum3 > maxTotal { maxTotal = maxSum12 + sum3 ans = []int {maxSum12Idx1, maxSum12Idx2, i - k + 1 } } sum1 -= nums[i-k*3 +1 ] sum2 -= nums[i-k*2 +1 ] sum3 -= nums[i-k+1 ] } } return }
[sol3-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 const maxSumOfThreeSubarrays = (nums, k ) => { const ans = [0 , 0 , 0 ]; let sum1 = 0 , maxSum1 = 0 , maxSum1Idx = 0 ; let sum2 = 0 , maxSum12 = 0 , maxSum12Idx1 = 0 , maxSum12Idx2 = 0 ; let sum3 = 0 , maxTotal = 0 ; for (let i = k * 2 ; i < nums.length ; ++i) { sum1 += nums[i - k * 2 ]; sum2 += nums[i - k]; sum3 += nums[i]; if (i >= k * 3 - 1 ) { if (sum1 > maxSum1) { maxSum1 = sum1; maxSum1Idx = i - k * 3 + 1 ; } if (maxSum1 + sum2 > maxSum12) { maxSum12 = maxSum1 + sum2; maxSum12Idx1 = maxSum1Idx; maxSum12Idx2 = i - k * 2 + 1 ; } if (maxSum12 + sum3 > maxTotal) { maxTotal = maxSum12 + sum3; ans[0 ] = maxSum12Idx1; ans[1 ] = maxSum12Idx2; ans[2 ] = i - k + 1 ; } sum1 -= nums[i - k * 3 + 1 ]; sum2 -= nums[i - k * 2 + 1 ]; sum3 -= nums[i - k + 1 ]; } } return ans; }
复杂度分析
结语 此题也可以用动态规划求解,总体思路是我们用 dp}[i][j] 表示到数组第 j 个元素为止,前 i 个互不重叠的子数组的最大值。对于当前第 j 个元素所对应的值,我们有不取和取两种选择,选择不取该元素,则值为到 j-1 个元素时前 i 个互不重叠的子数组的最大值,即 dp}[i][j-1] ,选择取该元素,则值为到 j-k 个元素时前 i-1 个互不重叠的子数组的最大值 dp}[i-1][j-k] 加上最后一段子数组的和,我们选择这两种情况下较大值即可,可以得到如下状态转移方程:
\textit{dp}[i][j] = \max(\textit{dp}[i][j-1],\textit{dp}[i-1][j-k]+\textit{sum}[j]-\textit{sum}[j-k])
其中,sum 为前缀和数组。
感兴趣的读者可以自行尝试。