Maximum Contiguous Subsequence Sum of At Least Length L

I believe that you can do this in O(n) time regardless of the choice of by using a modified version of Kadane’s algorithm.

To see how this works, let’s consider the case where L = 0. In that case, we want to find the maximum-sum subarray of the original sequence. This can be solved by Kadane’s algorithm, a clever dynamic programming solution that works as follows. The idea is to keep track of the weight of the maximum-weight subarray ending just before and just after each position in the array. Whichever of these arrays has the largest sum is the subarray with the maximum total sum. Let the original array be A and let the array of maximum sums ending at position k be array M. Then Kadane’s algorithm works like this:

  • Set M(0) = 0. Any subarray ending just before the first array entry can’t have anything in it, so it has sum zero.
  • For each array index k, in order, set M(k + 1) = max(0, M(k) + A(k)). The idea here is that the best subarray ending just before this position is either formed by extending the best array from the previous position by a single element, or by discarding that array entirely and just picking the empty subarray before this position.

Once you’ve filled in this table M, you can just scan it to find the maximum value overall, which gives you the weight of the maximum-weight subarray.

But how do we adapt this to the case where L ≠ 0? Fortunately, this isn’t too bad. Look at the recurrence for Kadane’s algorithm. The idea is that at each point we can either extend the array by one step, or we can reset back to the empty array. But if we have a lower bound on the size of our subarray, we can think of this differently: the maximum-weight subarray of length at least L ending just before position k + 1 is formed either by extending the best array of length at least L that ends just before position k by one element, or by discarding that array and taking the L element subarray that ends right before position k. This gives us a new version of Kadane’s algorithm that looks like this:

  • Set M(L) equal to the sum of the first L elements of the array.
  • For each array index k ≥ L, in order, set M(k + 1) to the maximum of M(k) + A(k) (the value we get by extending the array) and the sum of the L elements just before position k + 1 (the value we get by just taking the last k elements).

If we run this, we will fill in table M values from L to the length of the array. The maximum value in that range is then the maximum sum subarray value for subarrays of length at least L.

But this doesn’t run in linear time! In particular, it runs in O(nL), since each iteration of the computation has to look at the previous L elements of the array. However, by doing some extra pre computation, we can drop this to O(n). The idea is that we can build up a table containing the sums of the L element before each array index in O(n) time as follows. First, sum up the first L elements of the array and store that as S(L). This is the sum of the L elements just before position L. Now, if we want to get the sum of the L elements just before index L + 1, wr can do s by summing up the first L elements of the array, adding in the next array element, then subtracting out the very first array element. This can be done in O(1) time by computing S(L + 1) = S(L) + A(L) – A(0). We can then use a similar trick to compute S(L + 2) = S(L + 1) + A(L + 1) – A(1). More generally, we can fill in this table of partial sums in O(n) time using the recurrence

  • S(L) = A(0) + A(1) + … + A(L – 1).
  • S(L + k + 1) = S(L + k) + A(L + k) – A(k).

This runs in O(n) time. If we have this table precomputed, we can then find the maximum-weight subarray of length at least L by using this recurrence from above:

  • M(L) = S(L)
  • M(L + k + 1) = max(M(L + k) + A(L + k), S(L + k))

We can then just scan across the M array to find the maximum value. This whole process runs in O(n) time: we need O(n) time to compute the S array, O(n) time to compute M array, and O(L) = O(n) time to find the maximum value. It also takes O(L) space, since we need to store the M and S arrays.

But we can do better than this by reducing the memory usage to O(1)! The trick is to notice that at each point we don’t need the entire M and S arrays; just the last term. We can therefore just store the last value of M and S, which takes only O(1) memory. At each point, we will also keep track of the maximum value we’ve seen in the M array, so we don’t need to hold the M array after we’ve filled it in. This then gives the following O(n)-time, O(1)-space algorithm for solving the problem:

  • Set S to the sum of the first L array elements.
  • Set M = S.
  • Set Best = M
  • For k = L + 1 up to n, the length of the array:
    • Set S = S + A(k) – A(k – L)
    • Set M = max(M + A(k), S)
    • Set Best = max(Best, M)
  • Output Best

As an example, here’s a trace through the algorithm on your original array with L = 3:

        -5    -1    2      -3    0    -3    3
S                      -4     -2   -1    -6    0  
M                      -4     -2   -1    -4    0
Best                   -4     -2   -1    -1    0

So the output is 0.

Or, on a different array with L = 2:

        0   5    -3    -1    2   -4   -1    7   8
S              5     2    -4   1    -2   -5   6   15
M              5     2     1   3    -1   -2   6   15
Best           5     5     5   5     5    5   6   15

So the output is 15.

Hope this helps! This is a really cool problem!

EDIT: I have a C++ implementation of this algorithm available if you’re interested in looking at some actual code for the solution.

Leave a Comment