Sum of K Subarrays With Length at Least M
MedPrompt
You are given an integer array nums and two integers, k and m.
Return the maximum sum of k non-overlapping subarrays of nums, where each subarray has a length of at least m.
Example 1:
Input: nums = [1,2,-1,3,3,4], k = 2, m = 2
Output: 13
Explanation:
The optimal choice is:
- Subarray
nums[3..5]with sum3 + 3 + 4 = 10(length is3 >= m). - Subarray
nums[0..1]with sum1 + 2 = 3(length is2 >= m).
The total sum is 10 + 3 = 13.
Example 2:
Input: nums = [-10,3,-1,-2], k = 4, m = 1
Output: -10
Explanation:
The optimal choice is choosing each element as a subarray. The output is (-10) + 3 + (-1) + (-2) = -10.
Constraints:
1 <= nums.length <= 2000-104 <= nums[i] <= 1041 <= k <= floor(nums.length / m)1 <= m <= 3
Approaches
3 approaches with complexity analysis and trade-offs.
This approach uses a top-down dynamic programming technique with recursion and memoization. We define a recursive function that explores all possible ways to select k valid subarrays. The state of our recursion is (i, j), representing the problem of finding the maximum sum of j subarrays from the portion of the array starting at index i.
Algorithm
- Define a recursive function
findMaxSum(i, j)which computes the maximum sum ofjsubarrays from the suffix of the arraynums[i:]. - Use a 2D array
memo[i][j]for memoization to store the results offindMaxSum(i, j). - The base cases for the recursion are:
- If
j == 0, we need 0 subarrays, so the sum is 0. - If we cannot form
jsubarrays of lengthmfrom the remaining elements (i.e.,i >= norn - i < j * m), return a very small number to signify an invalid path.
- If
- In the recursive step for
findMaxSum(i, j), consider two choices:- Skip
nums[i]: The result isfindMaxSum(i + 1, j). - Start a new subarray at
i: Iterate through all possible end pointspfor a subarray starting ati(wherep >= i + m - 1). For eachp, the sum issum(nums[i...p]) + findMaxSum(p + 1, j - 1). We take the maximum over all validp.
- Skip
- The final result for
findMaxSum(i, j)is the maximum of these two choices. - Store the result in
memo[i][j]before returning. - Pre-calculate prefix sums to compute
sum(nums[i...p])in O(1) time. - The initial call is
findMaxSum(0, k).
Walkthrough
The core idea is to build the solution by making a decision at each index i of the array. For a given state (i, j), we have two main options:
- Don't start a subarray at index
i: We move to the next indexi+1and try to findjsubarrays from there. This corresponds to the recursive callfindMaxSum(i + 1, j). - Start the next subarray at index
i: This subarray must have a length of at leastm. We can try every possible end indexpfromi + m - 1ton - 1. For each choice ofp, we form a subarraynums[i...p], calculate its sum, and recursively call the function to find the remainingj-1subarrays from indexp+1onwards (findMaxSum(p + 1, j - 1)). We take the maximum value among all possible end pointsp.
The final answer for (i, j) is the maximum of these two options. To avoid recomputing results for the same state, we store them in a 2D memoization table. Prefix sums are used to quickly calculate the sum of any subarray.
class Solution { long[] prefix; Long[][] memo; int n; int m; long small_val = Long.MIN_VALUE / 2; public long maxSum(int[] nums, int k, int m) { this.n = nums.length; this.m = m; this.prefix = new long[n + 1]; for (int i = 0; i < n; i++) { prefix[i + 1] = prefix[i] + nums[i]; } this.memo = new Long[n][k + 1]; return findMaxSum(0, k); } private long findMaxSum(int i, int j) { if (j == 0) { return 0; } if (i >= n || n - i < j * m) { return small_val; } if (memo[i][j] != null) { return memo[i][j]; } // Option 1: Skip nums[i] long res = findMaxSum(i + 1, j); // Option 2: Start a subarray at i for (int p = i + m - 1; p < n; p++) { long currentSum = prefix[p + 1] - prefix[i]; long nextSum = findMaxSum(p + 1, j - 1); if (nextSum > small_val) { res = Math.max(res, currentSum + nextSum); } } return memo[i][j] = res; }}Complexity
Time
O(n² * k). There are O(n * k) states, and each state computation involves a loop that can run up to O(n) times.
Space
O(n * k) for the memoization table and recursion stack depth.
Trade-offs
Pros
Relatively straightforward to formulate from the problem's recursive nature.
Correctly solves the problem for smaller inputs.
Cons
The time complexity of O(n² * k) is too slow for the given constraints and will likely result in a 'Time Limit Exceeded' error.
The space complexity of O(n * k) can be large, potentially causing memory issues for large
nandk.
This approach uses bottom-up dynamic programming to solve the problem more efficiently. We build a 2D table, dp[i][j], to store the maximum sum of j subarrays considering the first i elements of the input array. By optimizing the transition, we can calculate each DP state in constant time, leading to a much better time complexity than the naive recursive solution.
Algorithm
- Pre-calculate the prefix sums of the
numsarray to find subarray sums in O(1). - Create a 2D DP table
dp[n+1][k+1], wheredp[i][j]stores the maximum sum ofjsubarrays within the prefixnums[0...i-1]. - Initialize
dp[i][0] = 0for alli, and all otherdpentries to a very small number. - Iterate
jfrom 1 tok(number of subarrays). - Inside this loop, iterate
ifrom 1 ton(length of the prefix). - For each
dp[i][j], the value is determined by two cases:- Case 1: The
jsubarrays are all withinnums[0...i-2]. The value isdp[i-1][j]. - Case 2: The
j-th subarray ends atnums[i-1]. Its sum issum(nums[p...i-1])and the otherj-1subarrays are innums[0...p-1]. We need to findmax_{0 <= p <= i-m} (dp[p][j-1] + sum(nums[p...i-1])).
- Case 1: The
- The recurrence is
dp[i][j] = max(dp[i-1][j], prefix[i] + max_{0 <= p <= i-m} (dp[p][j-1] - prefix[p])). - To avoid the inner O(n) loop for
p, maintain a variablemax_prev_dp_termthat tracksmax(dp[p][j-1] - prefix[p])asiincreases. - The final answer is
dp[n][k].
Walkthrough
Let dp[i][j] be the maximum sum of j non-overlapping subarrays, each of length at least m, using the prefix nums[0...i-1].
The transition for dp[i][j] is based on two possibilities for the element nums[i-1]:
nums[i-1]is not part of the last subarray: The optimal solution forjsubarrays innums[0...i-1]is the same as fornums[0...i-2]. So,dp[i][j] = dp[i-1][j].nums[i-1]is the end of thej-th subarray: This subarray, saynums[p...i-1], must have length at leastm. The otherj-1subarrays must be chosen fromnums[0...p-1]. The total sum isdp[p][j-1] + sum(nums[p...i-1]). We need to maximize this over all valid start positionsp.
This leads to the recurrence: dp[i][j] = max(dp[i-1][j], max_{0 <= p <= i-m} (dp[p][j-1] + prefix[i] - prefix[p])).
The term max_{0 <= p <= i-m} (dp[p][j-1] - prefix[p]) can be computed efficiently. As we iterate i from 1 to n, we can maintain a running maximum of dp[q][j-1] - prefix[q] for q up to i-m. This optimization reduces the calculation of each DP state to O(1).
class Solution { public long maxSum(int[] nums, int k, int m) { int n = nums.length; long[] prefix = new long[n + 1]; for (int i = 0; i < n; i++) { prefix[i + 1] = prefix[i] + nums[i]; } long[][] dp = new long[n + 1][k + 1]; long small_val = Long.MIN_VALUE / 2; for (int i = 0; i <= n; i++) { for (int j = 1; j <= k; j++) { dp[i][j] = small_val; } } for (int j = 1; j <= k; j++) { long maxPrevDpTerm = small_val; for (int i = 1; i <= n; i++) { // Case 1: Don't use nums[i-1] in the last subarray dp[i][j] = dp[i - 1][j]; // Case 2: nums[i-1] is the end of the j-th subarray if (i >= m) { // Update the max term needed for the recurrence maxPrevDpTerm = Math.max(maxPrevDpTerm, dp[i - m][j - 1] - prefix[i - m]); if (maxPrevDpTerm > small_val) { dp[i][j] = Math.max(dp[i][j], maxPrevDpTerm + prefix[i]); } } } } return dp[n][k]; }}Complexity
Time
O(n * k). The two nested loops for `j` and `i` dominate, and the inner operations are O(1).
Space
O(n * k) to store the 2D DP table.
Trade-offs
Pros
The O(n * k) time complexity is efficient enough to pass the given constraints.
It's a systematic, bottom-up approach that is often easier to debug than recursion.
Cons
The O(n * k) space complexity can be substantial for the maximum constraints, potentially leading to memory limit issues.
This is the most optimized approach. It improves upon the previous DP solution by reducing the space complexity. We observe that when calculating the DP values for j subarrays, we only need the results from j-1 subarrays. This dependency allows us to discard older DP states and use only two 1D arrays to store the results for the previous and current number of subarrays, reducing the space from O(n*k) to O(n).
Algorithm
- The overall logic is identical to the previous iterative DP approach.
- Instead of a 2D
dptable, use two 1D arrays:dp(for results ofj-1subarrays) andnewDp(for results ofjsubarrays). - Initialize
dparray of sizen+1with all zeros, representing the base case forj=0. - Loop
jfrom 1 tok. - Inside the loop, initialize
newDpwith a very small value. - Also initialize
max_prev_dp_termto a very small value. This variable will trackmax(dp[p] - prefix[p]). - Loop
ifrom 1 ton:- Set
newDp[i] = newDp[i-1](Case 1). - If
i >= m, updatemax_prev_dp_term = max(max_prev_dp_term, dp[i-m] - prefix[i-m]). - If
max_prev_dp_termis valid, updatenewDp[i] = max(newDp[i], max_prev_dp_term + prefix[i])(Case 2).
- Set
- After the inner loop over
ifinishes, replacedpwithnewDpfor the next iteration ofj. - The final answer is
dp[n]after the outer loop completes.
Walkthrough
The recurrence relation and logic are the same as the standard bottom-up DP. The key difference is in the data structures used. Instead of a full dp[n+1][k+1] table, we maintain only the DP results for the current number of subarrays (j) and the previous one (j-1).
Let dp be the array storing results for j-1 subarrays and newDp be the array where we compute results for j subarrays. The outer loop iterates j from 1 to k. In each iteration, we compute the entire newDp array using values from the dp array. Once newDp is fully computed, it contains the optimal sums for j subarrays. We then assign newDp to dp and proceed to the next iteration for j+1.
This optimization is crucial for problems where n and k are large, as it prevents potential memory limit errors while keeping the time complexity the same.
class Solution { public long maxSum(int[] nums, int k, int m) { int n = nums.length; long[] prefix = new long[n + 1]; for (int i = 0; i < n; i++) { prefix[i + 1] = prefix[i] + nums[i]; } long[] dp = new long[n + 1]; // Corresponds to dp for j-1 subarrays long small_val = Long.MIN_VALUE / 2; for (int j = 1; j <= k; j++) { long[] newDp = new long[n + 1]; for(int i = 0; i <= n; i++) { newDp[i] = small_val; } long maxPrevDpTerm = small_val; for (int i = 1; i <= n; i++) { // Case 1: Don't use nums[i-1] in the last subarray newDp[i] = newDp[i - 1]; // Case 2: nums[i-1] is the end of the j-th subarray if (i >= m) { // dp[i-m] holds the result from the (j-1) iteration maxPrevDpTerm = Math.max(maxPrevDpTerm, dp[i - m] - prefix[i - m]); if (maxPrevDpTerm > small_val) { newDp[i] = Math.max(newDp[i], maxPrevDpTerm + prefix[i]); } } } dp = newDp; } return dp[n]; }}Complexity
Time
O(n * k). The time complexity is identical to the unoptimized space version.
Space
O(n). We use two arrays of size O(n) for the DP states and one O(n) array for prefix sums.
Trade-offs
Pros
Most efficient solution for the given constraints.
Optimal space complexity of O(n), which is memory-friendly.
Maintains the efficient O(n * k) time complexity.
Cons
The implementation can be slightly more complex due to the need to manage and swap two arrays.
Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.