4

I stumbled recently on a modified maximum sum subarray problem, here we know the sum (let's say it's S=2) but we need to find a longest slice of array which produces this exact sum (longest means must have biggest number of elements)

So for input array

A = [1, 0, -1, 1, 1, -1, -1]

We find 2 slices: A(0:4) because sum(1,0,-1,1,1) is 2 and A(3:4) because sum(1,1) is also 2

But the A(0:4) subarray is the longest thus it's length 5 is the answer here..

Most of the solution's I found where not O(n) because they used 2 loops instead of a one or some packages for collections. Is this variant of problem even possible to solve with O(n) complexity?

I'm mostly interested in a solution written in Java, but don't know which algorithm to model.

assert solution(new int[] { 1, 0, -1, 1, 1, -1, -1 }, 2) == 5;

Best Regards

oski86
  • 855
  • 1
  • 13
  • 35

2 Answers2

2

It can be done in O(n) as well:

First, create an auxilary array that sums each prefix of the array:

sums[i] = arr[0] + arr[1] + ... + arr[i]

The above can be computed in O(n) time.

Next, create a hash map Map<Integer,List<Integer>>, where the key is representing a prefix sum, and the value is a list of indices with this prefix sum. Pseudo code:

Map<Integer,List<Integer>> map = new HashMap<>();
for (int i = 0; i < sums.length; i++) {
   List<Integer> l = map.get(sums[i]);
   if (l == null) { 
       l = new ArrayList<>();
       map.put(sums[i],l);
   }
   l.add(i);
}

Now, iterate the sums array, and for each element x, check if the map contains a key k such that x-k == S.
This is done by checking if it has a key k = S-x, which is O(1) in a hash map.

If there is such a key, then get the last index in the values list, which is also done in O(1), and take it as a candidate match.

Pseudo code:

int currentMaxLength = Integer.MIN_VALUE;
for (int i = 0; i < sums.length; i++) { 
   int k = S-sums[i];
   List<Integer> l = map.get(k);
   if (l == null) continue;
   int width = Math.abs(l.getLast() -  i);
   if (width > currentMaxLength) currentMaxLength = width;
}
return currentMaxLength;

The idea is, if you have multiple matches for some x1,x2 such that x2-x1 = S, and where x1,x2 are prefix sums, the candidates for longest subarray are the first place where x1 appears, and the last place where x2 appears.
For x1, this is the element which i refers to in the main loop, and it is always regarded as a candidate.
For x2, you will always check the last occurance of x2, and thus it's correct.

Quicknote: The actual code also needs to regard sums[-1] = 0.


Java code:

public static int solution(int[] arr, int S) { 
    int[] sums = new int[arr.length+1];
    int sum = 0;
    //generate the sums array:
    sums[0] = 0;
    for (int i = 0; i < arr.length; i++) sums[i+1] = sum = sum+arr[i];
    //generate map:
    Map<Integer,List<Integer>> map = new HashMap<>();
    for (int i = 0; i < sums.length; i++) {
       List<Integer> l = map.get(sums[i]);
       if (l == null) { 
           l = new ArrayList<>();
           map.put(sums[i],l);
       }
       l.add(i);
    }
    //find longest:
    int currentMaxLength = Integer.MIN_VALUE;
    for (int i = 0; i < sums.length; i++) { 
       int k = S - sums[i];
       List<Integer> l = map.get(k);
       if (l == null) continue;
       int width = Math.abs(l.get(l.size()-1) -  i);
       if (width > currentMaxLength) currentMaxLength = width;
    }
    return currentMaxLength;
}
public static void main(String[] args) {
    System.out.println(solution(new int[] { 1, 0, -1, 1, 1, -1, -1 }, 2));
}
amit
  • 175,853
  • 27
  • 231
  • 333
  • HashMap's get and put operations are amortized O(1). The complexity here is O(2n * 1) in the best scenario, and in the worst is O(2 * (n^2)). Because we can expect linear time in most of the scenarios, I'd like to accept this answer. - reference http://stackoverflow.com/questions/4553624/hashmap-get-put-complexity – oski86 May 28 '15 at 10:35
  • @oski86 The complexity is `O(n)` average case, and indeed `O(n^2)` worst case. Note that if the array contains for example small values, , you could use a bitset instead, at the expanse of `O(S)` memory (where S is the difference between maximal prefix sum and minimal prefix sum). Another alternative is a `TreeMap`, that will make the algorithm run in O(nlogn) all cases. – amit May 28 '15 at 10:38
-1

Think of something like this:

If you have a array T[A:D] and the maximum sub array of T[A:D] is T[B:C], then you get a next element T[E], you therefore need the maximum sub array of [A:E], this sub array MUST BE either the old T[B:C], or the T[B:E].