2

this is the question, and yes it is homework, so I don't necessarily want anyone to "do it" for me; I just need suggestions: Maximum sum: Design a linear algorithm that finds a contiguous subsequence of at most M in a sequence of N long integers that has the highest sum among all such subsequences. Implement your algorithm, and confirm that the order of growth of its running time is linear.

I think that the best way to design this program would be to use nested for loops, but because the algorithm must be linear, I cannot do that. So, I decided to approach the problem by making separate for loops (instead of nested ones).

However, I'm really not sure where to start. The values will range from -99 to 99 (as per the range of my random number generating program).

This is what I have so far (not much):

public class MaxSum {

public static void main(String[] args){
    int M = Integer.parseInt(args[0]);
    int N = StdIn.readInt(); 
    long[] a = new long[N]; 
    for (int i = 0; i < N; i++) {
          a[i] = StdIn.readLong();}}}

if M were a constant, this wouldn't be so difficult. For example, if M==3:

public class MaxSum2 {

    public static void main(String[] args){
        int N = StdIn.readInt(); //read size for array
        long[] a = new long[N]; //create array of size N
        for (int i = 0; i < N; i++) { //go through values of array
              a[i] = StdIn.readLong();} //read in values and assign them to             
                                                //array indices

        long p = a[0] + a[1] + a[2]; //start off with first 3 indices

        for (int i =0; i<N-4; i++)
        {if ((a[i]+a[i+1]+a[1+2])>=p) {p=(a[i]+a[i+1]+a[1+2]);}}
                 //if sum of values is greater than p, p becomes that sum

        for (int i =0; i<N-4; i++) //prints the subsequence that equals p
        {if ((a[i]+a[i+1]+a[1+2])==p) {StdOut.println((a[i]+a[i+1]+a[1+2]));}}}}

If I must, I think MaxSum2 will be acceptable for my lab report (sadly, they don't expect much). However, I'd really like to make a general program, one that takes into consideration the possibility that, say, there could be only one positive value for the array, meaning that adding the others to it would only reduce it's value; Or if M were to equal 5, but the highest sum is a subsequence of the length 3, then I would want it to print that smaller subsequence that has the actual maximum sum.

I also think as a novice programmer, this is something I Should learn to do. Oh and although it will probably be acceptable, I don't think I'm supposed to use stacks or queues because we haven't actually covered that in class yet.

  • Look into this: http://en.wikipedia.org/wiki/Maximum_subarray_problem – Kuba Spatny Apr 06 '14 at 08:06
  • 2
    I would argue that the nested loops solution is in fact linear in `N`. – Dawood ibn Kareem Apr 06 '14 at 08:06
  • possible duplicate of [Find Portion in a sequence with max sum](http://stackoverflow.com/questions/15392205/find-portion-in-a-sequence-with-max-sum) – Lokesh Apr 06 '14 at 08:07
  • @Lokesh I don't think it's a duplicate. That question doesn't have `M`. – Dawood ibn Kareem Apr 06 '14 at 08:11
  • @DavidWallace, you're absolutely right. I knew I was overlooking something simple/silly. Thanks! –  Apr 06 '14 at 08:14
  • I wondered whether you meant it had to be `O(N+M)`, which the nested loops solution isn't. – Dawood ibn Kareem Apr 06 '14 at 08:15
  • Wait, actually, wouldn't it still need to go over values of the array? Could you explain what you mean because I thought I understood, but I am second guessing you/my intuition now. –  Apr 06 '14 at 08:15
  • @amaleemuer - I will post soon a `O(N)` solution. – Petar Minchev Apr 06 '14 at 08:17
  • I mean, you just iterate through the array (`O(N)`), and for each entry, find each sum that starts at that point - and there are `M` sums to consider. If any such sum is not the biggest you've encountered since the very beginning, then just discard it and carry on. You only have to store one value. I think it's `O(N*M^2)`, which is not very good, but it _is_ linear in `N`. – Dawood ibn Kareem Apr 06 '14 at 08:18
  • I'm still not following you; also, I think you may misunderstand the question because if N = 9, for example, and M = 3, there are not M Sums to consider, but 7 or so sums to consider (a[0]+a[1]+a[2]), (a[1]+a[2]+a[3]), ... (a[6]+a[7]+a[8]. –  Apr 06 '14 at 08:25
  • maybe that isn't what you meant by M sums. I'm not sure. –  Apr 06 '14 at 08:25
  • Yeah, I meant for each of the `N` initial values. Obviously, for the last few, there are fewer than `M` sums. But it's near enough `M`, for the sake of what `O()` it is. – Dawood ibn Kareem Apr 06 '14 at 08:27
  • @amaleemur - Are you convinced with my algorithm? It is linear O(N), but the idea is harder to understand than the slower nested loops. As `David Wallace` has said, if you cannot understand it, then go with your slower solution. – Petar Minchev Apr 06 '14 at 09:39
  • 1
    I am convinced that I need to rest my brain and come back to this, haha. It's a bit late where I am. But @PetarMinchev, you're code looks convincing and I've read the comments. However, I am going to try to sort of rewrite it "in my own words," so to speak. And I will update as soon as I do so (probably about 8-12 hours from now). –  Apr 06 '14 at 09:58

3 Answers3

4

Here is my version, adapted from Petar Minchev's code and with an important addition that allows this program to work for an array of numbers with all negative values.

public class MaxSum4 {

public static void main(String[] args)
{Stopwatch banana = new Stopwatch(); //stopwatch object for runtime data.

    long sum = 0;
    int currentStart = 0;
    long bestSum = 0;
    int bestStart = 0;
    int bestEnd = 0;

    int M = Integer.parseInt(args[0]); // read in highest possible length of         
                                        //subsequence from command line argument. 
    int N = StdIn.readInt(); //read in length of array
    long[] a = new long[N]; 
    for (int i = 0; i < N; i++) {//read in values from standard input
          a[i] = StdIn.readLong();}//and assign those values to array
    long negBuff = a[0];

    for (int i = 0; i < N; i++) { //go through values of array to find 
                                 //largest sum (bestSum)
    sum += a[i];                  //and updates values. note bestSum, bestStart,
                                  // and bestEnd updated
    if (sum > bestSum) {          //only when sum>bestSum
        bestSum = sum; 
        bestStart = currentStart; 
        bestEnd = i; }

    if (sum < 0) { //in case sum<0, skip to next iteration, reseting sum=0
        sum = 0;   //and update currentStart
        currentStart = i + 1;
        continue; }

    if (i - currentStart + 1 == M) { //checks if sequence length becomes equal 
                                         //to M.
        do {                          //updates sum and currentStart
           sum -= a[currentStart]; 
           currentStart++;  
        } while ((sum < 0 || a[currentStart] < 0) && (currentStart <= i)); 
                        //if sum or a[currentStart]
    }                   //is less than 0 and currentStart<=i,
}                       //update sum and currentStart again

    if(bestSum==0){ //checks to see if bestSum==0, which is the case if 
                            //all values are negative
        for (int i=0;i<N;i++){ //goes through values of array 
                                          //to find largest value
            if (a[i] >= negBuff) {negBuff=a[i]; 
                                  bestSum=negBuff; bestStart=i; bestEnd=i;}}}
                              //updates bestSum, bestStart, and bestEnd

    StdOut.print("best subsequence is from 
                 a[" + bestStart + "] to a[" + bestEnd + "]: ");
    for (int i = bestStart; i<=bestEnd; i++)
    {
        StdOut.print(a[i]+ " "); //prints sequence
    }
    StdOut.println();
    StdOut.println(banana.elapsedTime());}}//prints elapsed time

also, did this little trace for Petar's code:

trace for a small array

M=2

array: length 5

index value
0      -2
1       2
2       3
3       10
4       1

for the for-loop central to program:

i = 0   sum = 0 + -2 = -2
    sum>bestSum? no
    sum<0? yes so sum=0, currentStart = 0(i)+1 = 1,
         and continue loop with next value of i

i = 1   sum = 0 + 2 = 2
    sum>bestSum? yes so bestSum=2 and bestStart=currentStart=1 and bestEnd=1=1
    sum<0? no
    1(i)-1(currentStart)+1==M? 1-1+1=1 so no

i = 2   sum = 2+3 = 5
    sum>bestSum? yes so bestSum=5, bestStart=currentStart=1, and bestEnd=2
    sum<0? no
    2(i)-1(currentStart)+1=M? 2-1+1=2 so yes: 
    sum = sum-a[1(curentstart)] =5-2=3. currentStart++=2.
    (sum<0 || a[currentStart]<0)? no 

i = 3   sum=3+10=13
    sum>bestSum? yes so bestSum=13 and bestStart=currentStart=2 and bestEnd=3
    sum<0? no
    3(i)-2(currentStart)+1=M? 3-2+1=2 so yes: 
        sum = sum-a[1(curentstart)] =13-3=10. currentStart++=3.
        (sum<0 || a[currentStart]<0)? no 

i = 4   sum=10+1=11
    sum>bestSum? no
    sum<0? no
    4(i)-3(currentStart)+1==M? yes but changes to sum and currentStart now are 
        irrelevent as loop terminates

Thanks again! Just wanted to post a final answer and I was slightly proud for catching the all negative thing.

  • 1
    oh, and if anyone cares: stopwatch values confirm that this is definitely linear! –  Apr 07 '14 at 05:18
  • Great :) But I think you have a small bug - `//checks to see if bestSum==0, which is the case if all values are negative`. This sentence is not entirely true. I think if the sequence is `-4,-3,0`, the answer is `0` not `-3`. – Petar Minchev Apr 07 '14 at 06:57
  • Also could you post here when your teacher gives the feedback? – Petar Minchev Apr 07 '14 at 06:57
  • 1
    @PetarMinchev, my code returns 0 for that array. bestSum==0 when all values <= 0, so then it just finds the maximum for the entire array. because 0>=-3, the values are updated and (in your example) a[2] is printed. I'm not sure why you think it would give -3 as the max value unless you are thinking in terms of absolute value. And of course, bestSum will in fact be 0 for your example, but the desired output is the location of that zero. –  Apr 07 '14 at 07:54
  • 1
    And yes, I will get my report back next Monday, the 14th. When I get it, I will post any relevant feedback. Or, if they give no feedback, I will assume everything worked out. –  Apr 07 '14 at 07:57
  • @DavidWallace does my trace help convince you? –  Apr 07 '14 at 07:59
  • Ah, you've done well. And I have just upvoted both you and @PetarMinchev. No, your trace doesn't help convince me, but then, I am just an old cynic. My opinion is unimportant - what matters is whether you can convince the professor or TA who determines your grade for this assignment. – Dawood ibn Kareem Apr 07 '14 at 08:41
  • Alright so I got my assignment back, and I got full credit. There was no feedback, so I am assuming it was just fine. Sorry they didn't give more feedback. –  Apr 15 '14 at 02:44
1

Each element is looked at most twice (one time in the outer loop, and one time in the while loop).

O(2N) = O(N)

Explanation: each element is added to the current sum. When the sum goes below zero, it is reset to zero. When we hit M length sequence, we try to remove elements from the beginning, until the sum is > 0 and there are no negative elements in the beginning of it.

By the way, when all elements are < 0 inside the array, you should take only the largest negative number. This is a special edge case which I haven't written below.

Beware of bugs in the below code - it only illustrates the idea. I haven't run it.

int sum = 0;
int currentStart = 0;

int bestSum = 0;
int bestStart = 0;
int bestEnd = 0;

for (int i = 0; i < N; i++) {
    sum += a[i];
    if (sum > bestSum) {
        bestSum = sum; 
        bestStart = currentStart; 
        bestEnd = i;
    }

    if (sum < 0) {
        sum = 0; 
        currentStart = i + 1;
        continue;
    }

    //Our sequence length has become equal to M
    if (i - currentStart + 1 == M) { 
        do {
           sum -= a[currentStart]; 
           currentStart++;  
        } while ((sum < 0 || a[currentStart] < 0) && (currentStart <= i)); 
    }
}
Petar Minchev
  • 46,889
  • 11
  • 103
  • 119
  • But why does it work? I can't follow your explanation. – Dawood ibn Kareem Apr 06 '14 at 08:30
  • http://en.wikipedia.org/wiki/Maximum_subarray_problem. It is a modification of this algorithm. Basically when we hit the upper length limit `M`, we try removing the first element, so it fits the limit. But if the sum goes below zero (or there are still negative elements in the beginning), we still have to keep removing elements. – Petar Minchev Apr 06 '14 at 08:31
  • I understand the algorithm on that page. But I'm uneasy about whether you've done the right thing to consider the limit on the length of the subsequence. I'm not claiming that you haven't, but I don't think that your argument proves that you have. – Dawood ibn Kareem Apr 06 '14 at 08:34
  • OK, I will try explaining it again. The only hard case is when we have a current positive sum and we have hit the upper length limit M. To fit again inside the limit, we have to shift the start index to the right. We have to shift it right till the sum becomes positive, and there are no negative elements in the beginning. If there are negative elements in the beginning, we can just remove them to make better sum. I still know this is not a math proof. – Petar Minchev Apr 06 '14 at 08:42
  • Basically imagine it as a sliding window. The sliding window increases to length M to the right, and then it decreases from the left side. Then it increases to the right again. – Petar Minchev Apr 06 '14 at 08:52
  • 1
    So while it's sliding in that way, how do you know that you won't miss a case where there's a big number in the middle of the range, and negative numbers on each side of it? The correct answer might be of length one, but your window always includes one of the negatives, and never gets narrow enough to just have the single number. – Dawood ibn Kareem Apr 06 '14 at 08:58
  • It will work correct. Let's take `-4, -3, -2, -1, 1000, -1, -2, 3, -4.`. The window resets at each negative element, because sum is `< 0`. `if (sum < 0) { sum = 0;` – Petar Minchev Apr 06 '14 at 09:01
  • I'm thinking more like `5, 5, 5, -100, 1000, -100, 5, 5, 5.` Don't the positive elements stop the iteration, so it never gets to correct for the -100s? – Dawood ibn Kareem Apr 06 '14 at 09:02
  • Well the window will reset at first `-100`, because `5 + 5 + 5 - 100 < 0` and it will work correctly. – Petar Minchev Apr 06 '14 at 09:04
  • Positive elements stop the iteration, when the sum is greater than zero. – Petar Minchev Apr 06 '14 at 09:06
  • OK, what if it shouldn't reset? Let's say it's `0, 0, 90, 90, 90, -100, 1000, -100, 90, 90, 90, 0, 0` and again `M = 7'. – Dawood ibn Kareem Apr 06 '14 at 09:06
  • 1
    I think I need to play with this more to be truly convinced. As I said, your argument just hasn't 100% convinced me yet. – Dawood ibn Kareem Apr 06 '14 at 09:07
  • For the last example, the window goes like this: `0, 0, 90, 90, 90, -100, 1000` ,,, `0, 90, 90, 90, -100, 1000, -100` ,,, `90, 90, 90, -100, 1000, -100, 90` and so on... – Petar Minchev Apr 06 '14 at 09:08
  • 1
    It's OK. I might just cut and paste your solution into my IDE and try a few sequences myself. I'm just very glad that I'm not amaleemur's teacher, having to decide whether the algorithm is "correct" or not, in order to give her the right grade. – Dawood ibn Kareem Apr 06 '14 at 09:09
  • OK :) As I said I haven't written the special case of all negative elements in the array, but it is trivial - just take the largest negative number as the answer. – Petar Minchev Apr 06 '14 at 09:10
  • 1
    I've had a bit of a play. I think it's probably right, but I'm not sure. My best advice to amaleemur though would be to hand in a solution that she _knows_ is correct, in case her teacher asks her to justify it. So unless you can come up with an argument that convinces her that this algorithm is correct, and which she can easily relay to her teacher, I would advise her to do it the "nested loops" way, even though that algorithm is very probably inferior to this one. – Dawood ibn Kareem Apr 06 '14 at 09:36
0

I think what you are looking for is discussed in detail here

Find the subsequence with largest sum of elements in an array

I have explained 2 different solutions to resolve this problem with O(N) - linear time.

Community
  • 1
  • 1