Array operations for maximum sum

1.1k Views Asked by At

Given an array A consisting of N elements. Our task is to find the maximal subarray sum after applying the following operation exactly once:

. Select any subarray and set all the elements in it to zero.

Eg:- array is -1 4 -1 2 then answer is 6 because we can choose -1 at index 2 as a subarray and make it 0. So the resultatnt array will be after applying the operation is : -1 4 0 2. Max sum subarray is 4+0+2 = 6.

My approach was to find start and end indexes of minimum sum subarray and make all elements as 0 of that subarray and after that find maximum sum subarray. But this approach is wrong.

1

There are 1 best solutions below

1
Shamis On

Starting simple:

First, let us start with the part of the question: Finding the maximal subarray sum.
This can be done via dynamic programming:

a = [1, 2, 3, -2, 1, -6, 3, 2, -4, 1, 2, 3]
a = [-1, -1, 1, 2, 3, 4, -6, 1, 2, 3, 4]

def compute_max_sums(a):
    res = []
    currentSum = 0
    for x in a:
        if currentSum > 0:
            res.append(x + currentSum)
            currentSum += x
        else:
            res.append(x)
            currentSum = x
    return res

res = compute_max_sums(a)
print(res)
print(max(res))

Quick explanation: we iterate through the array. As long as the sum is non-negative, it is worth appending the whole block to the next number. If we dip below zero at any point, we discard whole "tail" sequence since it will not be profitable to keep it anymore and we start anew. At the end, we have an array, where j-th element is the maximal sum of a subarray i:j where 0 <= i <= j.
Rest is just the question of finding the maximal value in the array.

Back to the original question

Now that we solved the simplified version, it is time to look further. We can now select a subarray to be deleted to increase the maximal sum. The naive solution would be to try every possible subarray and to repeat the steps above. This would unfortunately take too long1. Fortunately, there is a way around this: we can think of the zeroes as a bridge between two maxima.
There is one more thing to address though - currently, when we have the j-th element, we only know that the tail is somewhere behind it so if we were to take maximum and 2nd biggest element from the array, it could happen that they would overlap which would be a problem since we would be counting some of the elements more than once.

Overlapping tails

How to mitigate this "overlapping tails" issue?
The solution is to compute everything once more, this time from the end to start. This gives us two arrays - one where j-th element has its tail i pointing towards the left end of the array(e.g. i <=j) and the other where the reverse is true. Now, if we take x from first array and y from second array we know that if index(x) < index(y) then their respective subarrays are non-overlapping.
We can now proceed to try every suitable x, y pair - there is O(n2) of them. However since we don't need any further computation as we already precomputed the values, this is the final complexity of the algorithm since the preparation cost us only O(n) and thus it doesn't impose any additional penalty.

Here be dragons

So far the stuff we did was rather straightforward. This following section is not that complex but there are going to be some moving parts. Time to brush up the max heaps:

  • Accessing the max is in constant time
  • Deleting any element is O(log(n)) if we have a reference to that element. (We can't find the element in O(log(n)). However if we know where it is, we can swap it with the last element of the heap, delete it, and bubble down the swapped element in O(log(n)).
  • Adding any element into the heap is O(log(n)) as well.
  • Building a heap can be done in O(n)

That being said, since we need to go from start to the end, we can build two heaps, one for each of our pre-computed arrays. We will also need a helper array that will give us quick index -> element-in-heap access to get the delete in log(n).

The first heap will start empty - we are at the start of the array, the second one will start full - we have the whole array ready.

Now we can iterate over whole array. In each step i we:

  • Compare the max(heap1) + max(heap2) with our current best result to get the current maximum. O(1)
  • Add the i-th element from the first array into the first heap - O(log(n))
  • Remove the i-th indexed element from the second heap(this is why we have to keep the references in a helper array) - O(log(n))

The resulting complexity is O(n * log(n)).

Update:

Just a quick illustration of the O(n2) solution since OP nicely and politely asked. Man oh man, I'm not your bro.
Note 1: Getting the solution won't help you as much as figuring out the solution on your own.
Note 2: The fact that the following code gives the correct answer is not a proof of its correctness. While I'm fairly certain that my solution should work it is definitely worth looking into why it works(if it works) than looking at one example of it working.

input = [100, -50, -500, 2, 8, 13, -160, 5, -7, 100]
reverse_input = [x for x in reversed(input)]
max_sums = compute_max_sums(input)
rev_max_sums = [x for x in reversed(compute_max_sums(reverse_input))]
print(max_sums)
print(rev_max_sums)
current_max = 0
for i in range(len(max_sums)):
    if i < len(max_sums) - 1:
        for j in range(i + 1, len(rev_max_sums)):
            if max_sums[i] + rev_max_sums[j] > current_max:
                current_max = max_sums[i] + rev_max_sums[j]
                
print(current_max)

1 There are n possible beginnings, n possible ends and the complexity of the code we have is O(n) resulting in a complexity of O(n3). Not the end of the world, however it's not nice either.