Problem overview
The function second_largest receives a vector of integers, nums, and returns the second largest value. Duplicates of the maximum don't count as the runner-up. You may assume the vector holds at least two distinct values.
For nums = [2, 7, 11, 8, 11, 8, 3, 11] the maximum is 11, which appears three times. The largest value that is different from 11 is 8, so the answer is 8.
Approach: two trackers in a single pass
The simple way is to sort the vector and walk back from the end until a value differs from the last one. That costs O(n log n). You can also scan twice: once to find the maximum, and once to find the largest value below it.
In Rust the sorting version is short. dedup removes consecutive repeats, which after a sort means all repeats:
fn second_by_sort(nums: Vec<i32>) -> i32 {
let mut v = nums;
v.sort_unstable();
v.dedup();
v[v.len() - 2]
}
For [2, 7, 11, 8, 11, 8, 3, 11] it also returns 8. It allocates nothing extra, but it reorders the data and does more work than the question needs, since only the top two values matter.
A single scan is enough if you keep two variables, the best value so far (max_val) and the best value strictly below it (second). For each number there are three cases:
- It is larger than
max_val. The old maximum becomes the runner-up and the number becomes the new maximum. - It is smaller than
max_valbut larger thansecond. It is a better runner-up. - Anything else, including a repeat of the current maximum, changes nothing.
On the example, the trackers move like this: after 2 they are (2, MIN), after 7 (7, 2), after 11 (11, 7), then 8 becomes the runner-up (11, 8). The later 11s, 8s and 3 change nothing, so the result is 8.
The "old maximum becomes the runner-up" move is correct because the old maximum was larger than everything seen before it, and the new number is larger than the old maximum. So the old maximum is the largest value below the new maximum among everything seen so far.
Step by step
- Declare
max_valandsecondwithlet mut, both set toi32::MIN. Every real value is at least that large, so the first elements always take over without a special "first iteration" check. - Loop over the vector with
for num in nums, so every element gets a chance to update the trackers. - Test for a new maximum with
if num > max_val. - Demote the old maximum before overwriting it. Copy
max_valintosecondfirst, then storenuminmax_val. Swapping those two lines would lose the old maximum. - Add an
else iffor a number that is strictly less thanmax_valand strictly greater thansecond. The strict comparison withmax_valis what keeps duplicates of the maximum from becoming the runner-up. - Update
secondin that branch. - Return
second.
The solution in Rust
impl Solution {
fn second_largest(nums: Vec<i32>) -> i32 {
let mut max_val = i32::MIN;
let mut second = i32::MIN;
for num in nums {
if num > max_val {
second = max_val;
max_val = num;
} else if num < max_val && num > second {
second = num;
}
}
return second;
}
}
The Rust details worth a closer look:
i32::MINis the smallest 32-bit signed integer, -2147483648, an associated constant of the type.- Iterating by value.
for num in numsconsumes the vector and gives each element as a plaini32, so comparisons need no dereferencing. The vector can't be used after the loop, which is fine here because the function only returns a number. return second;is an explicit return. Writingsecondas the last expression, without a semicolon and withoutreturn, does the same thing.- Mutability. Both trackers are reassigned inside the loop, so both need
mut.
Complexity
Time: O(n). One pass, with a constant number of comparisons per element.
Space: O(1). Two integers, whatever the length of the vector.
The sorting version takes O(n log n) time, so the single pass is the better answer to the follow-up question. Both are correct on every valid input, and the one-pass loop never needs to look at an element twice.
Edge cases and common mistakes
- Duplicates of the maximum. In
[7, 7, 3]the answer is 3. If the comparison werenum <= max_val, the second 7 would become the runner-up. - Increasing and decreasing input.
[1, 2, 3, 4, 5]and[5, 4, 3, 2, 1]both return 4. The first order exercises the demotion branch on every element, the second theelse ifbranch. - Two elements.
[5, 9]and[9, 5]both return 5. - Negative numbers.
[-5, -5, -7]returns -7. Starting the trackers at 0 instead ofi32::MINwould wrongly return 0. - Wrong order of the two assignments. Setting
max_val = numfirst and thensecond = max_valmakes both trackers equal tonum. - Fewer than two distinct values. For
[4, 4, 4]the function returnsi32::MIN, becausesecondnever changes. The lesson rules this input out.
Testing
[2, 7, 11, 8, 11, 8, 3, 11]gives 8.[7, 7, 3]gives 3.[-5, -5, -7]gives -7.[5, 9]gives 5.
