Problem overview
The method secondLargest receives an array of integers, nums, and returns the second largest value. Duplicates of the maximum don't count as second place. In [2, 7, 11, 8, 11, 8, 3, 11] the maximum is 11, which appears three times, so the answer is 8, not 11.
The array always holds at least two distinct values. The follow-up asks for a solution that works in one pass without sorting.
Approach: track the top two values
Sorting the array and taking the second element from the end works, but it costs O(n log n) and still has to skip duplicates of the maximum. A single pass needs only two variables: the largest value seen so far and the largest value seen that is smaller than it.
Each number 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 becomes the new runner-up.
- Anything else, including a repeat of the maximum, changes nothing.
Both variables start at Int.min, the smallest Int. Any real element beats that starting value, so the first iteration needs no special case.
There are other ways. Sorting a copy in descending order and scanning for the first value different from the first element works, but it costs O(n log n). Two separate passes also work: one to find the maximum and one to find the largest value below it, which is O(n) but reads the array twice. The single pass below visits each element once, keeps the input untouched, and needs no extra array.
The order of the two tests inside the loop is also part of the design. A value that beats the maximum is checked first, so the demotion happens before anything else can look at second. The second test only needs to handle values that are not new maximums.
A worked trace
Follow [2, 7, 11, 8, 11, 8, 3, 11], writing the pair as (maxVal, second) after each element and using -inf for Int.min:
- 2 beats
-inf: the pair becomes (2, -inf). - 7 beats 2: (7, 2).
- 11 beats 7: (11, 7).
- 8 is below 11 and above 7: (11, 8).
- 11 equals the maximum, so neither test passes: (11, 8).
- 8 is not above the runner-up 8: (11, 8).
- 3 is below the runner-up: (11, 8).
- The last 11 equals the maximum again: (11, 8).
The function returns 8. Notice that the repeated 11 values never disturb the runner-up. That is the whole purpose of the strict comparison against maxVal.
Step by step
- Declare
maxValandsecondasvars, both set toInt.min. - Loop over
numswith the loop variablenum. - Test whether
numbeatsmaxVal. A new maximum means the old one is demoted. - Demote before replacing. Copy
maxValintosecondfirst, then storenuminmaxVal. Doing it the other way round would lose the old maximum. - Add an
else iffornum < maxVal && num > second. The strict comparison withmaxValkeeps duplicates of the maximum out of second place. - Update
secondtonumin that branch. - Return
secondafter the loop.
The solution in Swift
class Solution {
func secondLargest(_ nums: [Int]) -> Int {
var maxVal = Int.min
var second = Int.min
for num in nums {
if num > maxVal {
second = maxVal
maxVal = num
} else if num < maxVal && num > second {
second = num
}
}
return second
}
}
The Swift details worth a closer look:
Int.minis -9223372036854775808, sinceIntis 64 bits. Using it as a starting value is safe here because comparing never overflows.else ifchains. Only one branch runs per element. Whennumis a new maximum, the second condition isn't even tested.- Parameter labels. The underscore in
_ numsmeans callers writesecondLargest([1, 2])without a label. - No parentheses needed. Swift conditions don't take parentheses, but braces are required around every body.
Complexity
Time: O(n). One pass over the array, with a few comparisons per element.
Space: O(1). Only two extra variables.
Edge cases and common mistakes
- Duplicated maximum.
[5, 5, 4]returns 4. Without the strictnum < maxValtest, the second 5 would land insecond. - Negative numbers.
[-3, -1, -2]returns -2. Starting both variables at 0 instead ofInt.minwould wrongly return 0. - Two elements.
[1, 2]returns 1. - Ascending order.
[1, 2, 3, 4]returns 3: each new number pushes the old maximum down to second place. - Fewer than two distinct values.
[5, 5]and[]both returnInt.min, becausesecondnever changes. The lesson promises two distinct values, so this isn't tested. - Elements equal to
Int.min. They look the same as the starting value. For[Int.min, 3]the function returnsInt.min, which is correct, but it can't tell a real runner-up from "none found". AnInt?variable avoids that ambiguity if you need it.
Testing
[2, 7, 11, 8, 11, 8, 3, 11]gives8.[1, 2]gives1.[5, 5, 4]gives4.[-3, -1, -2]gives-2.[1, 2, 3, 4]gives3.
