Start learning free

LESSON 324 OF 338 · GREEDY

Find Second Largest in Go: One Pass Without Sorting

Practice O(n) Read the full explanation

Given an array of integers, return the second largest value.

Example:

Input: nums = [2, 7, 11, 8, 11, 8, 3, 11]
Output: 8
Explanation: The maximum value is 11. The second largest value is 8.

Note:

You may assume the array contains at least two distinct values.


Follow up:

Could you solve this in one pass without sorting?


STEP 1 OF 7
    Ctrl + Enter to check

    GO · GREEDY

    Find Second Largest in Go, explained

    Problem overview

    The function secondLargest receives a slice nums of integers and returns its second largest distinct value. The slice is guaranteed to hold at least two different values.

    For [2, 7, 11, 8, 11, 8, 3, 11] the maximum is 11 and it appears three times. The second largest is 8, not 11: repeats of the maximum don't count as a runner-up. The answer is 8.

    Approach: track two numbers in a single pass

    The obvious solution sorts the slice and looks near the end, but then you must skip duplicates of the maximum, and sorting costs O(n log n). You can do better by walking through the slice once and remembering only two values: the largest seen so far and the largest value seen that is smaller than it.

    Each number you read falls into one of three cases:

    • It is bigger than the current maximum. The old maximum becomes the runner-up, and the number becomes the new maximum.
    • It is smaller than the maximum but bigger than the runner-up. It is a better runner-up.
    • Anything else, including a repeat of the maximum, changes nothing.

    Tracing the example from the statement, the pair (maxVal, second) changes like this:

    • After 2: (2, sentinel), because the sentinel is smaller than everything.
    • After 7: (7, 2). After 11: (11, 7).
    • After 8: (11, 8), since 8 is below the maximum and above the runner-up.
    • The second 11, the second 8 and the 3 change nothing. The 11 equals the maximum, the 8 equals the runner-up, and the 3 is below it.

    The final pair is (11, 8), so the function returns 8. Note that the answer is the second largest value, not the value at the second-to-last position of a sorted copy, which here would be 11.

    The sentinel start value is a shortcut. It works because the problem guarantees real values above it, and a stricter version could instead start from the first element and handle the second distinct value separately.

    Step by step

    1. Declare maxVal and second with :=, both set to -2147483648, the smallest 32-bit integer. Any real value in the slice is at least that, so no special case for the first element is needed.
    2. Range over nums, discarding the index with _ and naming the value num.
    3. Test num > maxVal. A new maximum pushes the old one down to second place.
    4. Copy first, overwrite second. Set second = maxVal and only then maxVal = num. With the lines swapped, the old maximum is lost and both variables end up equal.
    5. Add an else if for num < maxVal && num > second. The strict comparison with maxVal keeps duplicates of the maximum from taking second place.
    6. Update second to num in that branch.
    7. Return second after the loop.

    The solution in Go

    func secondLargest(nums []int) int {
        maxVal := -2147483648
        second := -2147483648
        for _, num := range nums {
            if num > maxVal {
                second = maxVal
                maxVal = num
            } else if num < maxVal && num > second {
                second = num
            }
        }
        return second
    }
    

    The Go details worth knowing:

    • for _, num := range nums yields an index and a value on each pass. Go refuses unused variables, so the unused index is replaced by the blank identifier _.
    • := infers int from the constant. In Go int is 64 bits, so the literal fits easily.
    • else if chains the two conditions: the second test only runs when num > maxVal was false. Go has no ternary operator, so branching like this uses if.
    • Parentheses are not needed around conditions, but the braces are mandatory.

    Complexity

    Time is O(n), one pass with constant work per element. Space is O(1): two integers, however long the slice is.

    Edge cases and common mistakes

    • Duplicates of the maximum. For [3, 3, 5] and [5, 3, 3] the answer is 3 in both. In [3, 5, 5] the answer is also 3, because the second 5 equals maxVal and fails both tests.
    • Ascending and descending input. [1, 2, 3, 4] and [4, 3, 2, 1] both give 3. The first order exercises the "new maximum" branch every time, the second the "better runner-up" branch.
    • Negative numbers. [-5, -3, -9] gives -5. The sentinel is far below any ordinary negative value, so these work.
    • All values equal. [7, 7, 7] never updates second, so the function returns the sentinel -2147483648. The guarantee of two distinct values rules this out.
    • Values below the sentinel. Since Go's int is 64 bits, a value such as -3000000000 is smaller than the starting value and is never picked up. The sentinel trick only suits inputs within 32-bit range.
    • Using >= for the maximum test. That would let a duplicate of the maximum overwrite second with the maximum itself.

    Testing

    When you test, try the ordering extremes as well as the repeats, since the two branches of the loop behave differently depending on the order of the input.

    • []int{1, 2} returns 1.
    • []int{2, 1} returns 1.
    • []int{2, 7, 11, 8, 11, 8, 3, 11} returns 8.
    • []int{-5, -3, -9} returns -5.
    Keep going with GoAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free