Maximum subarray sum modulo M
Asked Answered
M

15

40

Most of us are familiar with the maximum sum subarray problem. I came across a variant of this problem which asks the programmer to output the maximum of all subarray sums modulo some number M.

The naive approach to solve this variant would be to find all possible subarray sums (which would be of the order of N^2 where N is the size of the array). Of course, this is not good enough. The question is - how can we do better?

Example: Let us consider the following array:

6 6 11 15 12 1

Let M = 13. In this case, subarray 6 6 (or 12 or 6 6 11 15 or 11 15 12) will yield maximum sum ( = 12 ).

Maladminister answered 29/6, 2015 at 11:1 Comment(4)
Is there an upper limit on M?Persian
let us assume that the upper limit on number M is equal to maximum number in the array.Maladminister
O(n*M) is trivial, by finding existence subarrays that ends in i and sums (in modolus) to k, for each index i and for each k in [0,M) (done in DP)Virgulate
@Virgulate we would like our complexity to be independent of modulo M.Maladminister
O
32

We can do this as follow:

Maintaining an array sum which at index ith, it contains the modulus sum from 0 to ith.

For each index ith, we need to find the maximum sub sum that end at this index:

For each subarray (start + 1 , i ), we know that the mod sum of this sub array is

int a = (sum[i] - sum[start] + M) % M

So, we can only achieve a sub-sum larger than sum[i] if sum[start] is larger than sum[i] and as close to sum[i] as possible.

This can be done easily if you using a binary search tree.

Pseudo code:

int[] sum;
sum[0] = A[0];
Tree tree;
tree.add(sum[0]);
int result = sum[0];
for(int i = 1; i < n; i++){
    sum[i] = sum[i - 1] + A[i];
    sum[i] %= M;
    int a = tree.getMinimumValueLargerThan(sum[i]);
    result = max((sum[i] - a + M) % M, result);
    tree.add(sum[i]);
}
print result;

Time complexity :O(n log n)

Overmodest answered 29/6, 2015 at 11:50 Comment(12)
Nice. Also you can make it O(n log min(n, M)) by only inserting distinct sums into the tree.Elnaelnar
in line 5 result should be sum[0]%m, not sum[0]Marni
looking at this, to me it doesn't seem to be possible that this is a solution since it doesn't even refer to any elements of A apart from A[0]. There's something missingDemisec
@Demisec oh, there is a bug in my code, fixed it, thanks :)Overmodest
Why is getMinimumValueLargerThan guaranteed to return something? Suppose, M = 10000000, sequence = [1,1,...]. On the first step when sum[0]=1, tree=[1], sum[1]=2 -- there is no minimum larger than 2 in the tree. Am i mising something?Spoonerism
@Spoonerism it is ok if it not return anything, that case is trivial, so I expect you can handle that.Overmodest
I don't understand why you need a full array for the sums if you are only using the last oneElectrometallurgy
Why we have +M in (sum[i] - sum[start] + M) % M. Can't figure out.Aghast
Because sum[i] - sum[start] can be negative, hence we add M and take modulo of M to get positive remainder. Also adding any multiples of M would not change the remainder value. 1%7 == (1 + 7)%7 == (1+2*7)%7 etc.Munoz
@Aghast Because we want a modulo operation, which is not present in C/C++ standard library. C / C++ has %, which is a reminder operator. They are quite similar, but not the same; the differences show up on negative numbers. So we implement modulo by hand, and probably the simplest implementation is to use reminder with the + M trick (assuming the addition doesn't overflow). You can read more about modulo/reminder in this answer.Synsepalous
@PhamTrung Just to be sure; Tree has to be a balanced tree for this algorithm to be O(n log n) for arbitrary data, right?Synsepalous
@Synsepalous yes. a normal binary tree would be $O(n^2)$ if the tree is skew. I was getting time limit exceeded error using normal BST. Using a red-black tree solved this.Paterfamilias
V
8

Let A be our input array with zero-based indexing. We can reduce A modulo M without changing the result.

First of all, let's reduce the problem to a slightly easier one by computing an array P representing the prefix sums of A, modulo M:

A = 6 6 11 2 12 1
P = 6 12 10 12 11 12

Now let's process the possible left borders of our solution subarrays in decreasing order. This means that we will first determine the optimal solution that starts at index n - 1, then the one that starts at index n - 2 etc.

In our example, if we chose i = 3 as our left border, the possible subarray sums are represented by the suffix P[3..n-1] plus a constant a = A[i] - P[i]:

a = A[3] - P[3] = 2 - 12 = 3 (mod 13)
P + a = * * * 2 1 2

The global maximum will occur at one point too. Since we can insert the suffix values from right to left, we have now reduced the problem to the following:

Given a set of values S and integers x and M, find the maximum of S + x modulo M

This one is easy: Just use a balanced binary search tree to manage the elements of S. Given a query x, we want to find the largest value in S that is smaller than M - x (that is the case where no overflow occurs when adding x). If there is no such value, just use the largest value of S. Both can be done in O(log |S|) time.

Total runtime of this solution: O(n log n)

Here's some C++ code to compute the maximum sum. It would need some minor adaptions to also return the borders of the optimal subarray:

#include <bits/stdc++.h>
using namespace std;

int max_mod_sum(const vector<int>& A, int M) {
    vector<int> P(A.size());
    for (int i = 0; i < A.size(); ++i)
        P[i] = (A[i] + (i > 0 ? P[i-1] : 0)) % M;
    set<int> S;
    int res = 0;
    for (int i = A.size() - 1; i >= 0; --i) {
        S.insert(P[i]);
        int a = (A[i] - P[i] + M) % M;
        auto it = S.lower_bound(M - a);
        if (it != begin(S))
            res = max(res, *prev(it) + a);
        res = max(res, (*prev(end(S)) + a) % M);
    }
    return res;
}

int main() {
    // random testing to the rescue
    for (int i = 0; i < 1000; ++i) {
        int M = rand() % 1000 + 1, n = rand() % 1000 + 1;
        vector<int> A(n);
        for (int i = 0; i< n; ++i)
            A[i] = rand() % M;
        int should_be = 0;
        for (int i = 0; i < n; ++i) {
            int sum = 0;
            for (int j = i; j < n; ++j) {
                sum = (sum + A[j]) % M;
                should_be = max(should_be, sum);
            }
        }
        assert(should_be == max_mod_sum(A, M));
    }
}
Visualize answered 29/6, 2015 at 11:30 Comment(2)
I feel like there is a non-explicit assumption in your explanation regarding S + x mod M reaches its maximum at S = M - 1 - x. If S and x can be any value then S = M - 1 - x + y * M are also valid solutions. In your tree you only store one of them. I think this works out because the x and S are both in [0,M[.Sexagenarian
Yes, we're only considering the canonical representatives mod M. Hence the sum of two representatives is in (0, 2M(Visualize
B
3

For me, all explanations here were awful, since I didn't get the searching/sorting part. How do we search/sort, was unclear.

We all know that we need to build prefixSum, meaning sum of all elems from 0 to i with modulo m

I guess, what we are looking for is clear. Knowing that subarray[i][j] = (prefix[i] - prefix[j] + m) % m (indicating the modulo sum from index i to j), our maxima when given prefix[i] is always that prefix[j] which is as close as possible to prefix[i], but slightly bigger.

E.g. for m = 8, prefix[i] being 5, we are looking for the next value after 5, which is in our prefixArray.

For efficient search (binary search) we sort the prefixes.

What we can not do is, build the prefixSum first, then iterate again from 0 to n and look for index in the sorted prefix array, because we can find and endIndex which is smaller than our startIndex, which is no good.

Therefore, what we do is we iterate from 0 to n indicating the endIndex of our potential max subarray sum and then look in our sorted prefix array, (which is empty at the beginning) which contains sorted prefixes between 0 and endIndex.

def maximumSum(coll, m):
    n = len(coll)
    maxSum, prefixSum = 0, 0
    sortedPrefixes = []

    for endIndex in range(n):
        prefixSum = (prefixSum + coll[endIndex]) % m
        maxSum = max(maxSum, prefixSum)

        startIndex = bisect.bisect_right(sortedPrefixes, prefixSum)
        if startIndex < len(sortedPrefixes): 
            maxSum = max(maxSum, prefixSum - sortedPrefixes[startIndex] + m)

        bisect.insort(sortedPrefixes, prefixSum)

    return maxSum
Braley answered 14/9, 2018 at 18:39 Comment(2)
"I guess, what we are looking for is clear. Knowing that subarray[i][j] = (prefix[i] - prefix[j] + m) % m (indicating the modulo sum from index i to j),". Where did this equation come from, it's not clear to me?Bumptious
@Bumptious basically we just subtract two prefix sums getting the prefix sum of the segment between i and j. Since prefix(i) can be any value between 0 and m, by subtracting the prefix(j) we can get a negative number (if prefix(i) < prefix(j)), this is why we add m, however, the end result will be greater than m if (prefix(i) is > prefix(j)), this is why we perform the % m operation. Nothing fancy, just modulo arithmeticBraley
M
3

From your question, it seems that you have created an array to store the cumulative sums (Prefix Sum Array), and are calculating the sum of the sub-array arr[i:j] as (sum[j] - sum[i] + M) % M. (arr and sum denote the given array and the prefix sum array respectively)

Calculating the sum of every sub-array results in a O(n*n) algorithm.

The question that arises is -

Do we really need to consider the sum of every sub-array to reach the desired maximum?

No!

For a value of j the value (sum[j] - sum[i] + M) % M will be maximum when sum[i] is just greater than sum[j] or the difference is M - 1.

This would reduce the algorithm to O(nlogn).

You can take a look at this explanation! https://www.youtube.com/watch?v=u_ft5jCDZXk

Myology answered 20/6, 2020 at 7:9 Comment(0)
M
3

There are already a bunch of great solutions listed here, but I wanted to add one that has O(nlogn) runtime without using a balanced binary tree, which isn't in the Python standard library. This solution isn't my idea, but I had to think a bit as to why it worked. Here's the code, explanation below:

def maximumSum(a, m):
    prefixSums = [(0, -1)]
    for idx, el in enumerate(a):
        prefixSums.append(((prefixSums[-1][0] + el) % m, idx))
    
    prefixSums = sorted(prefixSums)
    maxSeen = prefixSums[-1][0]
    
    for (a, a_idx), (b, b_idx) in zip(prefixSums[:-1], prefixSums[1:]):
        if a_idx > b_idx and b > a:
            maxSeen = max((a-b) % m, maxSeen)
            
    return maxSeen

As with the other solutions, we first calculate the prefix sums, but this time we also keep track of the index of the prefix sum. We then sort the prefix sums, as we want to find the smallest difference between prefix sums modulo m - sorting lets us just look at adjacent elements as they have the smallest difference.

At this point you might think we're neglecting an essential part of the problem - we want the smallest difference between prefix sums, but the larger prefix sum needs to appear before the smaller prefix sum (meaning it has a smaller index). In the solutions using trees, we ensure that by adding prefix sums one by one and recalculating the best solution.

However, it turns out that we can look at adjacent elements and just ignore ones that don't satisfy our index requirement. This confused me for some time, but the key realization is that the optimal solution will always come from two adjacent elements. I'll prove this via a contradiction. Let's say that the optimal solution comes from two non-adjacent prefix sums x and z with indices i and k, where z > x (it's sorted!) and k > i:

x ... z
k ... i

Let's consider one of the numbers between x and z, and let's call it y with index j. Since the list is sorted, x < y < z.

x ... y ... z
k ... j ... i

The prefix sum y must have index j < i, otherwise it would be part of a better solution with z. But if j < i, then j < k and y and x form a better solution than z and x! So any elements between x and z must form a better solution with one of the two, which contradicts our original assumption. Therefore the optimal solution must come from adjacent prefix sums in the sorted list.

Meemeece answered 29/1, 2021 at 21:7 Comment(0)
G
2

Here is Java code for maximum sub array sum modulo. We handle the case we can not find least element in the tree strictly greater than s[i]

public static long maxModulo(long[] a, final long k) {
    long[] s = new long[a.length];
    TreeSet<Long> tree = new TreeSet<>();

    s[0] = a[0] % k;
    tree.add(s[0]);
    long result = s[0];

    for (int i = 1; i < a.length; i++) {

        s[i] = (s[i - 1] + a[i]) % k;

        // find least element in the tree strictly greater than s[i]
        Long v = tree.higher(s[i]);

        if (v == null) {
            // can't find v, then compare v and s[i]
            result = Math.max(s[i], result);
        } else {
            result = Math.max((s[i] - v + k) % k, result);
        }
        tree.add(s[i]);
    }
    return result;
 }
Gideon answered 15/3, 2017 at 13:6 Comment(0)
N
2

Few points from my side that might hopefully help someone understand the problem better.

  1. You do not need to add +M to the modulo calculation, as mentioned, % operator handles negative numbers well, so a % M = (a + M) % M

  2. As mentioned, the trick is to build the proxy sum table such that

proxy[n] = (a[1] + ... a[n]) % M

This then allows one to represent the maxSubarraySum[i, j] as

maxSubarraySum[i, j] = (proxy[j] - proxy[j]) % M

The implementation trick is to build the proxy table as we iterate through the elements, instead of first pre-building it and then using. This is because for each new element in the array a[i] we want to compute proxy[i] and find proxy[j] that is bigger than but as close as possible to proxy[i] (ideally bigger by 1 because this results in a reminder of M - 1). For this we need to use a clever data structure for building proxy table while keeping it sorted and being able to quickly find a closest bigger element to proxy[i]. bisect.bisect_right is a good choice in Python.

See my Python implementation below (hope this helps but I am aware this might not necessarily be as concise as others' solutions):

def maximumSum(a, m):
    prefix_sum = [a[0] % m]
    prefix_sum_sorted = [a[0] % m]
    current_max = prefix_sum_sorted[0]
    for elem in a[1:]:
        prefix_sum_next = (prefix_sum[-1] + elem) % m
        prefix_sum.append(prefix_sum_next)
        idx_closest_bigger = bisect.bisect_right(prefix_sum_sorted, prefix_sum_next)
        if idx_closest_bigger >= len(prefix_sum_sorted):
            current_max = max(current_max, prefix_sum_next)
            bisect.insort_right(prefix_sum_sorted, prefix_sum_next)
            continue
        if prefix_sum_sorted[idx_closest_bigger] > prefix_sum_next:
            current_max = max(current_max, (prefix_sum_next - prefix_sum_sorted[idx_closest_bigger]) % m)
            bisect.insort_right(prefix_sum_sorted, prefix_sum_next)
    return current_max
Neighbor answered 20/1, 2020 at 13:6 Comment(0)
P
1

Total java implementation with O(n*log(n))

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.TreeSet;
import java.util.stream.Stream;

public class MaximizeSumMod {

    public static void main(String[] args) throws Exception{

        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        Long times = Long.valueOf(in.readLine());

        while(times --> 0){
            long[] pair = Stream.of(in.readLine().split(" ")).mapToLong(Long::parseLong).toArray();
            long mod = pair[1];            
            long[] numbers = Stream.of(in.readLine().split(" ")).mapToLong(Long::parseLong).toArray();
            printMaxMod(numbers,mod);
        }
    }

    private static void printMaxMod(long[] numbers, Long mod) {

        Long maxSoFar = (numbers[numbers.length-1] + numbers[numbers.length-2])%mod;
        maxSoFar = (maxSoFar > (numbers[0]%mod)) ? maxSoFar : numbers[0]%mod;
        numbers[0] %=mod;
        for (Long i = 1L; i < numbers.length; i++) {
            long currentNumber = numbers[i.intValue()]%mod;            
            maxSoFar = maxSoFar > currentNumber ? maxSoFar : currentNumber;
            numbers[i.intValue()] = (currentNumber + numbers[i.intValue()-1])%mod;
            maxSoFar = maxSoFar > numbers[i.intValue()] ? maxSoFar : numbers[i.intValue()];
        }

        if(mod.equals(maxSoFar+1) || numbers.length == 2){
            System.out.println(maxSoFar);
            return;
        }

        long previousNumber = numbers[0];
        TreeSet<Long> set = new TreeSet<>();
        set.add(previousNumber);

        for (Long i = 2L; i < numbers.length; i++) {
            Long currentNumber = numbers[i.intValue()];
            Long ceiling = set.ceiling(currentNumber);
            if(ceiling == null){
                set.add(numbers[i.intValue()-1]);            
                continue;
            }

            if(ceiling.equals(currentNumber)){
                set.remove(ceiling);
                Long greaterCeiling = set.ceiling(currentNumber);
                if(greaterCeiling == null){
                    set.add(ceiling);
                    set.add(numbers[i.intValue()-1]);            
                    continue;
                }
                set.add(ceiling);                    
                ceiling = greaterCeiling;
            }
            Long newMax = (currentNumber - ceiling + mod);
            maxSoFar = maxSoFar > newMax ? maxSoFar :newMax;
            set.add(numbers[i.intValue()-1]);            
        }

        System.out.println(maxSoFar);

    }

}
Paregmenon answered 19/7, 2016 at 19:30 Comment(0)
U
1

Adding STL C++11 code based on the solution suggested by @Pham Trung. Might be handy.

#include <iostream>
#include <set>

int main() {
    int N;
    std::cin>>N;
    for (int nn=0;nn<N;nn++){
        long long n,m;
        std::set<long long> mSet;
        long long maxVal = 0; //positive input values
        long long sumVal = 0;

        std::cin>>n>>m;
        mSet.insert(m);
        for (long long q=0;q<n;q++){
            long long tmp;

            std::cin>>tmp;
            sumVal = (sumVal + tmp)%m;
            auto itSub = mSet.upper_bound(sumVal);
            maxVal = std::max(maxVal,(m + sumVal - *itSub)%m);
            mSet.insert(sumVal);                
        }
        std::cout<<maxVal<<"\n";
    }
}
Unplaced answered 24/8, 2017 at 15:57 Comment(1)
Challenge can be found here: hackerrank.com/challenges/maximum-subarray-sumUnplaced
D
1

As you can read in Wikipedia exists a solution called Kadane's algorithm, which compute the maximum subarray sum watching ate the maximum subarray ending at position i for all positions i by iterating once over the array. Then this solve the problem with with runtime complexity O(n).

Unfortunately, I think that Kadane's algorithm isn't able to find all possible solution when more than one solution exists.

An implementation in Java, I didn't tested it:

public int[] kadanesAlgorithm (int[] array) {
        int start_old = 0;
        int start = 0;
        int end = 0;
        int found_max = 0;

        int max = array[0];

        for(int i = 0; i<array.length; i++) {
            max = Math.max(array[i], max + array[i]);
            found_max = Math.max(found_max, max);
            if(max < 0)
                start = i+1;
            else if(max == found_max) {
                start_old=start;
                end = i;
                }
        }

        return Arrays.copyOfRange(array, start_old, end+1);
    }
Dihybrid answered 21/5, 2019 at 18:24 Comment(0)
F
0

I feel my thoughts are aligned with what have been posted already, but just in case - Kotlin O(NlogN) solution:

val seen = sortedSetOf(0L)
var prev = 0L

return max(a.map { x ->
    val z = (prev + x) % m
    prev = z
    seen.add(z)
    seen.higher(z)?.let{ y ->
        (z - y + m) % m
    } ?: z
})
Fleck answered 29/2, 2020 at 15:42 Comment(0)
S
0

Implementation in java using treeset...

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.TreeSet;

public class Main {

public static void main(String[] args) throws IOException {
    BufferedReader read = new BufferedReader(new InputStreamReader(System.in)) ;
    String[] str = read.readLine().trim().split(" ") ;
    int n = Integer.parseInt(str[0]) ;
    long m = Long.parseLong(str[1]) ;
    str = read.readLine().trim().split(" ") ;
    long[] arr = new long[n] ;
    for(int i=0; i<n; i++) {
        arr[i] = Long.parseLong(str[i]) ;
    }

    long maxCount = 0L ;
    TreeSet<Long> tree = new TreeSet<>() ;
    tree.add(0L) ;
    long prefix = 0L ;
    for(int i=0; i<n; i++) {
        prefix = (prefix + arr[i]) % m ;
        maxCount = Math.max(prefix, maxCount) ;

        Long temp = tree.higher(prefix) ;
        System.out.println(temp);
        if(temp != null) {
            maxCount = Math.max((prefix-temp+m)%m, maxCount) ;
        } 
        
        //System.out.println(maxCount);
        tree.add(prefix) ;
    }

    System.out.println(maxCount);
}

}

Styrene answered 18/5, 2021 at 20:49 Comment(0)
I
0

Here is one implementation of solution in java for this problem which works using TreeSet in java for optimized solution !

public static long maximumSum2(long[] arr, long n, long m)
{
    long x = 0;
    long prefix = 0;
    long maxim = 0;
    TreeSet<Long> S = new TreeSet<Long>();
    S.add((long)0);

    // Traversing the array.
    for (int i = 0; i < n; i++)
    {

    // Finding prefix sum.
    prefix = (prefix + arr[i]) % m;

    // Finding maximum of prefix sum.
    maxim = Math.max(maxim, prefix);

    // Finding iterator poing to the first
    // element that is not less than value
    // "prefix + 1", i.e., greater than or
    // equal to this value.
    long it = S.higher(prefix)!=null?S.higher(prefix):0;
    // boolean isFound = false;
    // for (long j : S)
    // {
    //     if (j >= prefix + 1)
    //     if(isFound == false) {
    //         it = j;
    //         isFound = true;
    //     }
    //     else {
    //         if(j < it) {
    //             it = j;
    //         }
    //     }
    // }
    if (it != 0)
    {
        maxim = Math.max(maxim, prefix - it + m);
    }

    // adding prefix in the set.
    S.add(prefix);
    }
    return maxim;
}
Interclavicle answered 23/5, 2021 at 12:57 Comment(0)
C
0
public static int MaxSequence(int[] arr)
    {
        int maxSum = 0;
        int partialSum = 0;
        int negative = 0;
        for (int i = 0; i < arr.Length; i++)
        {
            if (arr[i] < 0)
            {
                negative++;
            }
        }

        if (negative == arr.Length)
        {
            return 0;
        }

        foreach (int item in arr)
        {
            partialSum += item;
            maxSum = Math.Max(maxSum, partialSum);
            if (partialSum < 0)
            {
                partialSum = 0;
            }
        }


        return maxSum;
    }
Culprit answered 8/11, 2022 at 16:58 Comment(0)
A
-2

Modify Kadane algorithm to keep track of #occurrence. Below is the code.

#python3
#source: https://github.com/harishvc/challenges/blob/master/dp-largest-sum-sublist-modulo.py  
#Time complexity: O(n)
#Space complexity: O(n)
def maxContiguousSum(a,K):
    sum_so_far =0
    max_sum = 0
    count = {} #keep track of occurrence
    for i in range(0,len(a)):
            sum_so_far += a[i]
            sum_so_far = sum_so_far%K
            if sum_so_far > 0:
                    max_sum = max(max_sum,sum_so_far)
                    if sum_so_far in count.keys():
                            count[sum_so_far] += 1
                    else:
                            count[sum_so_far] = 1
            else:
                    assert sum_so_far < 0 , "Logic error"
                    #IMPORTANT: reset sum_so_far
                    sum_so_far = 0
    return max_sum,count[max_sum]

  a = [6, 6, 11, 15, 12, 1]
  K = 13
  max_sum,count = maxContiguousSum(a,K)
  print("input >>> %s max sum=%d #occurrence=%d" % (a,max_sum,count))
Arsenide answered 1/10, 2016 at 2:9 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.