Here\'s an interview questions that a colleague asked for a programming position. I thought this was great for watching the interviewee think it through. I\'d love to get re
We can just use the easiest 2 lines of code algo, it's simple and handles all negative as well :)
curr_max = max(a[i], curr_max+a[i]);
max_so_far = max(max_so_far, curr_max;
eg. C++
int maxSubArraySum(int a[], int size)
{
int max_so_far = a[0];
int curr_max = a[0];
for (int i = 1; i < size; i++)
{
curr_max = max(a[i], curr_max+a[i]);
max_so_far = max(max_so_far, curr_max;
}
return max_so_far;
}
The complexity is just O(n) for Kadane's algorithm:
The algorithm keeps track of the tentative maximum subsequence in
(maxSum, maxStartIndex, maxEndIndex)
. It accumulates a partial sum incurrentMaxSum
and updates the optimal range when this partial sum becomes larger thanmaxSum
.
Try this code .. it would work fine for at least one +ve number in the array.. O(n) just one for loop used..
public static void main(String[] args) {
int length ;
int a[]={-12, 14, 0, -4, 61, -39};
length=a.length;
int absoluteMax=0, localMax=0, startIndex=0, lastIndex=0, tempStartIndex=0;
for (int index=0;index<length;index++) {
localMax= localMax + a[index];
if(localMax < 0){ localMax=0; tempStartIndex = index + 1;}
if(absoluteMax < localMax) {
absoluteMax = localMax;
lastIndex =index;
startIndex=tempStartIndex;
}
}
System.out.println("startIndex "+startIndex+" lastIndex "+ lastIndex);
while (startIndex <= lastIndex) {
System.out.print(" "+a[startIndex++]);
}
}
This might be wrong because it's suspiciously simple.
This looks like O(n).
I'm intruding on this ancient thread to give a detailed explanation of why Kadane's algorithm works. The algorithm was presented in a class I'm currently taking, but with only a vague explanation. Here's an implementation of the algorithm in Haskell:
maxCont l = maxCont' 0 0 l
maxCont' maxSum _ [] = maxSum
maxCont' maxSum thisSum (x:xs)
| newSum > maxSum = maxCont' newSum newSum xs
| newSum < 0 = maxCont' maxSum 0 xs
| otherwise = maxCont' maxSum newsum xs
where
newSum = thisSum + x
Now since we're just trying to understand the algorithm, let's undo the minor optimization of naming newSum
:
maxCont l = maxCont' 0 0 l
maxCont' maxSum _ [] = maxSum
maxCont' maxSum thisSum (x:xs)
| thisSum + x > maxSum = maxCont' (thisSum + x) (thisSum+x) xs
| thisSum + x < 0 = maxCont' maxSum 0 xs
| otherwise = maxCont' maxSum (thisSum+x) xs
What is this crazy function maxCont'
? Let's come up with a simple specification of what it's supposed to be doing. We want the following to hold, with the precondition that 0 ≤ thisSum ≤ maxSum
:
maxCont' maxSum thisSum [] = maxSum
maxCont' maxSum thisSum l = maximum [maxSum, thisSum + maxInit l, maxCont l]
where maxInit l
is the greatest sum of an initial segment of l
and maxCont
is the maximum contiguous sum of l
.
Trivial but important fact: for all l
, maxInit l ≤ maxCont l
. It should be obvious that the above specification guarantees maxCont l = maxCont' 0 0 l
, which is what we want. Instead of trying to explain directly why the final version of maxCont' implements the specification above (which I don't really know how to do), I will show how it can be derived from it, transforming the specification one step at a time until it becomes the code, which will then certainly be correct. As written, this specification doesn't give an implementation: if maxCont
is defined in terms of maxCont'
as described above, it will loop forever as maxCont'
calls maxCont
calls maxCont'
with the same list. So let's expand it out just a bit to expose the pieces we will need:
maxCont' maxSum thisSum (x:xs) =
maximum [maxSum, thisSum + maxInit (x:xs), maxCont (x:xs)]
This didn't fix anything yet, but it exposed things. Let's use that. thisSum + maxInit (x:xs)
is either thisSum
or thisSum + x + maxInit xs
. But thisSum ≤ maxSum
by the precondition, so we can ignore this possibility when calculating the maximum. maxCont (x:xs)
is a sum that either includes x
or doesn't. But if it includes x
, then it's the same as maxInit (x:xs)
, which is covered by the preceding, so we can ignore that possibility, and only consider the case where maxCont (x:xs) = maxCont xs
. So we arrive at the next version:
maxCont' maxSum thisSum (x:xs) = maximum [maxSum, thisSum+x+maxInit xs, maxCont xs]
This one, finally, is properly recursive, but we have a ways to go to get to efficient code, especially because that mythical maxInit would be too slow. Let's break it down into the three cases considered in the Java code (abusing Haskell notation a bit):
maxCont' maxSum thisSum (x:xs)
| maxSum < thisSum + x = maximum [maxSum, thisSum+x+maxInit xs, maxCont xs]
| thisSum + x < 0 = maximum [maxSum, thisSum+x+maxInit xs, maxCont xs]
| 0 ≤ thisSum + x ≤ maxSum = maximum [maxSum, thisSum+x+maxInit xs, maxCont xs]
In the first case, we know that maxSum
can't be the maximum: thisSum+x
is greater and maxInit xs
is always positive. In the second case, we know that thisSum+x+maxInit xs
can't be the maximum: maxCont xs
is always at least as large as maxInit xs
, and thisSum+x
is negative. So we can eliminate those possibilities:
maxCont' maxSum thisSum (x:xs)
| maxSum < thisSum + x = maximum [ thisSum+x+maxInit xs, maxCont xs]
| thisSum + x < 0 = maximum [maxSum, maxCont xs]
| 0 ≤ thisSum + x ≤ maxSum = maximum [maxSum, thisSum+x+maxInit xs, maxCont xs]
Now we have just barely enough of an edge to twist things around. Now that we've eliminated impossible cases, we're going to add some duplicate cases, which will put these three cases back into the same form so we can substitute in the original specification of maxCont'
. In the first case, we don't have a first term, so we need to use something that we know won't exceed the other terms. To maintain the invariant that thisSum ≤ maxSum
, we will need to use thisSum+x
. In the second case, we don't have a second term that looks like something+maxInit xs
, but we know that maxInit xs ≤ maxCont xs
, so we can safely stick in 0+maxInit xs
. Adding these extra terms for regularity yields the following:
maxCont' maxSum thisSum (x:xs)
| maxSum < thisSum + x = maximum [(thisSum+x), (thisSum+x)+maxInit xs, maxCont xs]
| thisSum + x < 0 = maximum [maxSum, 0+maxInit xs, maxCont xs]
| 0 ≤ thisSum + x ≤ maxSum = maximum [maxSum, thisSum+x+maxInit xs, maxCont xs]
Finally, having checked the precondition, we substitute in the specification,
maxCont' maxSum thisSum l = maximum [maxSum, thisSum + maxInit l, maxCont l]
to get
maxCont' maxSum thisSum (x:xs)
| maxSum < thisSum + x = maxCont' (thisSum+x) (thisSum+x) xs
| thisSum + x < 0 = maxCont' maxSum 0 xs
| 0 ≤ thisSum + x ≤ maxSum = maxCont' maxSum (thisSum+x) xs
Fixing this up into real syntax and tacking on the omitted base case yields the actual algorithm, which we've now proven satisfies the spec as long as it terminates. But each successive recursive step operates on a shorter list, so it does indeed terminate.
There's just one last thing to do, for my sake, which is to write the final code more idiomatically and flexibly:
maxCont :: (Num a, Ord a) => [a] -> a
maxCont = fst . foldl maxCont' (0,0)
where
maxCont' (maxSum, thisSum) x
| maxSum < newSum = (newSum, newSum)
| newSum < 0 = (maxSum, 0)
| otherwise = (maxSum, newSum)
where newSum = thisSum + x
I would add an answer that contains 2 approaches, that handle array with or without positive elements differently, written in Java
.
Java
MaxSubSum.java:
public class MaxSubSum {
/**
* Find max sub array, only include sub array with positive sum.
* <p>For array that only contains non-positive elements, will choose empty sub array start from 0.
* <p>For empty input array, will choose empty sub array start from 0.
*
* @param arr input array,
* @return array of length 3, with elements as: {maxSum, startIdx, len};
* <p>tips: should use 'len' when loop the returned max sub array, so that it could also work for empty sub array,
*/
public static int[] find(int[] arr) {
if (arr.length == 0) return new int[]{0, 0, 0}; // empty array, no sub array,
int maxSum = 0; // max sum, found so far,
int maxStart = 0; // start of max sum,
int maxLen = 0; // length of max subarray,
int sum = 0; // current sum,
int start = 0; // current start,
for (int i = 0; i < arr.length; i++) {
if (arr[i] > 0) { // get a positive,
if (sum <= 0) { // should restart,
start = i;
sum = arr[i];
} else sum += arr[i];
if (sum > maxSum) { // get a larger sum,
maxSum = sum;
maxStart = start;
maxLen = i - start + 1;
}
} else sum += arr[i]; // 0 or negative number,
}
return new int[]{maxSum, maxStart, maxLen};
}
/**
* Find max sub array, also include sub array with non-positive sum.
* <p>For array that only contains non-positive elements, will choose first smallest element.
* <p>For empty input array, will choose empty sub array start from 0.
*
* @param arr input array,
* @return array of length 3, with elements as: {maxSum, startIdx, len};
* <p>tips: should use 'len' when loop the returned max sub array, so that it could also work for empty sub array,
*/
public static int[] findIncludeNonPositive(int[] arr) {
if (arr.length == 0) return new int[]{0, 0, 0}; // empty array, no sub array,
int maxSum = arr[0]; // max sum, found so far,
int maxStart = 0; // start of max sum,
int maxLen = 1; // length of max subarray,
int sum = arr[0]; // current sum,
int start = 0; // current start,
for (int i = 1; i < arr.length; i++) {
if (sum <= 0) { // should restart,
start = i;
sum = arr[i];
} else sum += arr[i];
if (sum > maxSum) { // get a larger sum,
maxSum = sum;
maxStart = start;
maxLen = i - start + 1;
}
}
return new int[]{maxSum, maxStart, maxLen};
}
}
MaxSubSumTest.java:
(Test case, via TestNG
)
import org.testng.Assert;
import org.testng.annotations.Test;
import java.util.Arrays;
public class MaxSubSumTest {
@Test
public void test_find() {
Assert.assertTrue(Arrays.equals(MaxSubSum.find(new int[]{-2, -3, 4, -1, -2, 1, 5, -3}), new int[]{7, 2, 5})); // max sub array: {4, -1, -2, 1, 5}
Assert.assertTrue(Arrays.equals(MaxSubSum.find(new int[]{12, 14, 0, -4, 61, -39}), new int[]{83, 0, 5})); // max sub array: {12, 14, 0, -4, 61}
// corner
Assert.assertTrue(Arrays.equals(MaxSubSum.find(new int[]{}), new int[]{0, 0, 0})); // empty array,
Assert.assertTrue(Arrays.equals(MaxSubSum.find(new int[]{-5, -4, 0, 0, -7, 0, -2}), new int[]{0, 0, 0})); // array with all elements <= 0,
Assert.assertTrue(Arrays.equals(MaxSubSum.find(new int[]{-5, -4, -2, -7, -2, -9}), new int[]{0, 0, 0})); // array with all elements < 0,
}
@Test
public void test_findIncludeNonPositive() {
Assert.assertTrue(Arrays.equals(MaxSubSum.findIncludeNonPositive(new int[]{-2, -3, 4, -1, -2, 1, 5, -3}), new int[]{7, 2, 5})); // max sub array: {4, -1, -2, 1, 5}
Assert.assertTrue(Arrays.equals(MaxSubSum.findIncludeNonPositive(new int[]{12, 14, 0, -4, 61, -39}), new int[]{83, 0, 5})); // max sub array: {12, 14, 0, -4, 61}
// corner
Assert.assertTrue(Arrays.equals(MaxSubSum.findIncludeNonPositive(new int[]{}), new int[]{0, 0, 0})); // empty array,
Assert.assertTrue(Arrays.equals(MaxSubSum.findIncludeNonPositive(new int[]{-5, -4, 0, 0, -7, 0, -2}), new int[]{0, 2, 1})); // array with all elements <= 0,
Assert.assertTrue(Arrays.equals(MaxSubSum.findIncludeNonPositive(new int[]{-5, -4, -2, -7, -2, -9}), new int[]{-2, 2, 1})); // array with all elements < 0,
}
}
Explanation:
find()
Find max sub array, only include sub array with positive sum.
Behavior:
BTW:
findIncludeNonPositive()
Find max sub array, also include sub array with non-positive sum.
Behavior:
BTW:
0
at end of sub array, which doesn't change the sum.Complexity:
O(n)
O(1)