14

There's an array A containing (positive and negative) integers. Find a (contiguous) subarray whose elements' absolute sum is minimal, e.g.:

A = [2, -4, 6, -3, 9]
|(−4) + 6 + (−3)| = 1 <- minimal absolute sum

I've started by implementing a brute-force algorithm which was O(N^2) or O(N^3), though it produced correct results. But the task specifies:

complexity:
- expected worst-case time complexity is O(N*log(N))
- expected worst-case space complexity is O(N)

After some searching I thought that maybe Kadane's algorithm can be modified to fit this problem but I failed to do it.

My question is - is Kadane's algorithm the right way to go? If not, could you point me in the right direction (or name an algorithm that could help me here)? I don't want a ready-made code, I just need help in finding the right algorithm.

NPS
  • 6,003
  • 11
  • 53
  • 90

11 Answers11

20

If you compute the partial sums such as

2, 2 +(-4), 2 + (-4) + 6, 2 + (-4) + 6 + (-3)...

Then the sum of any contiguous subarray is the difference of two of the partial sums. So to find the contiguous subarray whose absolute value is minimal, I suggest that you sort the partial sums and then find the two values which are closest together, and use the positions of these two partial sums in the original sequence to find the start and end of the sub-array with smallest absolute value.

The expensive bit here is the sort, so I think this runs in time O(n * log(n)).

Patryk
  • 22,602
  • 44
  • 128
  • 244
mcdowella
  • 19,301
  • 2
  • 19
  • 25
  • 1
    I'm not following how you identify the subarray from the partial sums. for instance the array [-4,5,-1] has partial sums [-4,1,0], which you seem to imply means that the subarray should be [5,-1]=4, whereas the actual solution is [-4,5,-1]=0. – Benubird Jun 11 '15 at 12:55
  • I didn't consider the entire array, considered as a sub-array. You could either consider subarrays with small partial sums separately, or be sure to include the sub-array with zero elements in it when you sort everything - this has a sum of zero, so in your example you would have partial sums [-4,1,0,0] and spot the solution that comes from considering the span between the end of the terms summed by the two zero sums - the beginning and end of the entire array. The subarray identified from two partial sums is the set of items in the partial sum with most items summed but not in the other. – mcdowella Jun 11 '15 at 18:00
  • Consider 3,3,3,4,5 ? Maybe I'm confused. – Catalyst Dec 14 '15 at 18:41
  • Partial sums 0,3,6,9,13,18 (including partial sum of first 0 elements) which is already sorted and 3,6 is a pair of numbers closest together so one answer for the contiguous sub-array with smallest absolute partial sum is [3]. As far as I can tell this is the correct answer, unless you allow the zero length partial sum, which would be the correct answer for every input if you allowed it. – mcdowella Dec 15 '15 at 05:33
7

This is C++ implementation of Saksow's algorithm.

int solution(vector<int> &A) {
    vector<int> P;
    int min = 20000 ;
    int dif = 0 ;
    P.resize(A.size()+1);
    P[0] = 0;
    for(int i = 1 ; i < P.size(); i ++)
    {
        P[i] = P[i-1]+A[i-1];

    }
    sort(P.begin(),P.end());
    for(int i = 1 ; i < P.size(); i++)
    {
         dif = P[i]-P[i-1];
         if(dif<min)
         {
             min = dif;
         }
    }
    return min;
}
Louie Lu
  • 71
  • 1
  • 2
5

I was doing this test on Codility and I found mcdowella answer quite helpful, but not enough I have to say: so here is a 2015 answer guys!

We need to build the prefix sums of array A (called P here) like: P[0] = 0, P[1] = P[0] + A[0], P[2] = P[1] + A[1], ..., P[N] = P[N-1] + A[N-1]

The "min abs sum" of A will be the minimum absolute difference between 2 elements in P. So we just have to .sort() P and loop through it taking every time 2 successive elements. This way we have O(N + Nlog(N) + N) which equals to O(Nlog(N)).

That's it!

Seif
  • 1,058
  • 11
  • 19
  • I'm curious to see how you implemented that. – Maresh May 26 '15 at 12:14
  • I implemented it in Python but I don't have the code any more... What is the part that interests you the most ? I can explain more. – Seif Jun 01 '15 at 11:07
  • 2
    What about this array [-5,8,-1]? the P is [0,-5,3,2], so the min abs diff between P elements is 1 (2,3), but the min abs sum of A is 2 (-5,8,-1). Or this one: [14,-4,5] which gives P [0,12,10,15], so the min diff of P is 2 (10,12), but of A is 1 (-4,5) – Benubird Jun 11 '15 at 13:08
  • You are right in the first example. But for [14,-4,5] you built P wrongly it should be [0,14,10,15] which gives the min diff 1. Can you look for a fully working solution ? – Seif Jun 12 '15 at 13:56
  • @Salivan there is cases where it doesn't work but I think it's not too far from the correct solution – Seif Feb 01 '17 at 10:31
  • Thanks for the reply. However that fails for the 100%. I was curious for their own specific answer using DP, but it requires too much to understand it (as I do the tests for fun). So I just converted the solution into Java. – Soley Feb 01 '17 at 10:43
  • It sure worked for many cases, you can see my reply above for an example, good luck! – Seif Feb 01 '17 at 10:47
  • will it work for -1,-2,-3? when you compute the result, you should consider the partial sums, too, not only the differences between them. – Horia Toma Aug 17 '17 at 18:43
2

The answer is yes, Kadane's algorithm is definitely the way to go for solving your problem.

http://en.wikipedia.org/wiki/Maximum_subarray_problem

Source - I've closely worked with a PhD student who's entire PhD thesis was devoted to the maximum subarray problem.

wookie919
  • 3,054
  • 24
  • 32
0
def min_abs_subarray(a):
    s = [a[0]]
    for e in a[1:]:
        s.append(s[-1] + e)
    s = sorted(s)
    min = abs(s[0])
    t = s[0]
    for x in s[1:]:
        cur = abs(x)
        min = cur if cur < min else min
        cur = abs(t-x)
        min = cur if cur < min else min
        t = x
    return min
Oleg
  • 961
  • 8
  • 16
0

You can run Kadane's algorithmtwice(or do it in one go) to find minimum and maximum sum where finding minimum works in same way as maximum with reversed signs and then calculate new maximum by comparing their absolute value.

Source-Someone's(dont remember who) comment in this site.

monster
  • 808
  • 10
  • 23
0

Short Sweet and work like a charm. JavaScript / NodeJs solution

 function solution(A, i=0, sum =0 ) {

    //Edge case if Array is empty
    if(A.length == 0) return 0;

    // Base case. For last Array element , add and substart from sum
    //  and find min of their absolute value
    if(A.length -1 === i){
        return Math.min( Math.abs(sum + A[i]), Math.abs(sum - A[i])) ;
    }

    // Absolute value by adding the elem with the sum.
    // And recusrively move to next elem
    let plus = Math.abs(solution(A, i+1, sum+A[i]));

    // Absolute value by substracting the elem from the sum
    let minus = Math.abs(solution(A, i+1, sum-A[i]));
    return Math.min(plus, minus);
}

console.log(solution([-100, 3, 2, 4]))
klutt
  • 30,332
  • 17
  • 55
  • 95
sapy
  • 8,952
  • 7
  • 49
  • 60
0

Here is an Iterative solution in python. It's 100% correct. enter image description here

 def solution(A):
    memo = []
    if not len(A):
        return 0

    for ind, val in enumerate(A):
        if ind == 0:
            memo.append([val, -1*val])
        else:
            newElem = []
            for i in memo[ind - 1]:
                newElem.append(i+val)
                newElem.append(i-val)
            memo.append(newElem)
    return min(abs(n) for n in memo.pop())
sapy
  • 8,952
  • 7
  • 49
  • 60
-1

Here is a C solution based on Kadane's algorithm. Hopefully its helpful.

#include <stdio.h>
int min(int a, int b)
{
  return (a >= b)? b: a;
}

int min_slice(int A[], int N) {
if (N==0 || N>1000000) 
return 0;

int minTillHere = A[0];
int minSoFar = A[0];
int i;
for(i = 1; i < N; i++){
    minTillHere = min(A[i], minTillHere + A[i]);
    minSoFar = min(minSoFar, minTillHere);
    }
return minSoFar;
}


int main(){
int A[]={3, 2, -6, 4, 0}, N = 5;
//int A[]={3, 2, 6, 4, 0}, N = 5;
//int A[]={-4, -8, -3, -2, -4, -10}, N = 6;
printf("Minimum slice = %d \n", min_slice(A,N));
return 0;
}
-1
public static int solution(int[] A) {
    int minTillHere = A[0];
    int absMinTillHere = A[0];
    int minSoFar = A[0];
    int i;
    for(i = 1; i < A.length; i++){
        absMinTillHere = Math.min(Math.abs(A[i]),Math.abs(minTillHere + A[i]));
        minTillHere = Math.min(A[i], minTillHere + A[i]);
        minSoFar = Math.min(Math.abs(minSoFar), absMinTillHere);
        }
    return minSoFar;
}
Kovalan V
  • 39
  • 6
-1
int main()
{
int n; cin >> n;

vector<int>a(n);

for(int i = 0; i < n; i++) cin >> a[i];

long long local_min = 0, global_min = LLONG_MAX;
for(int i = 0; i < n; i++)
{
    if(abs(local_min + a[i]) > abs(a[i]))
    {
        local_min = a[i];
    }
    else local_min += a[i];

    global_min = min(global_min, abs(local_min));
}
cout << global_min << endl;

}