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
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