sly s's answer is derived from this paper, but he didn't explain the algorithm, which means someone stumbling across this question has to read the whole paper, and his code isn't very sleek as well. I'll give the crux of the algorithm from the aforementioned paper, complete with complexity analysis, and also provide a Scala implementation, just because that's the language I chose while working on these problems.
Basically, we do two passes:
- Find the max, and keep track of which elements the max was compared to.
- Find the max among the elements the max was compared to; the result is the second largest element.
In the picture above, 12 is the largest number in the array, and was compared to 3, 1, 11, and 10 in the first pass. In the second pass, we find the largest among {3, 1, 11, 10}, which is 11, which is the second largest number in the original array.
Time Complexity:
- All elements must be looked at, therefore,
n - 1
comparisons for pass 1.
- Since we divide the problem into two halves each time, there are at most
log₂n
recursive calls, for each of which, the comparisons sequence grows by at most one; the size of the comparisons sequence is thus at most log₂n
, therefore, log₂n - 1
comparisons for pass 2.
Total number of comparisons <= (n - 1) + (log₂n - 1)
= n + log₂n - 2
def secondLargest(xs: IndexedSeq[Int]): Int = {
def max(lo: Int, hi: Int, ys: IndexedSeq[Int]): (Int, IndexedSeq[Int]) = {
if (lo >= hi) {
(ys(lo), IndexedSeq.empty[Int])
} else {
val mid = lo + (hi - lo) / 2
val (x, a) = max(lo, mid, ys)
val (y, b) = max(mid + 1, hi, ys)
if (x > y) {
(x, a :+ y)
} else {
(y, b :+ x)
}
}
}
val (_, comparisons) = max(0, xs.size - 1, xs)
max(0, comparisons.size - 1, comparisons)._1
}