Problem overview
The function secondLargest receives a vector of integers, nums, and returns the second largest value. Duplicates don't count as separate ranks. In [2, 7, 11, 8, 11, 8, 3, 11] the largest value is 11, which appears three times, and the second largest value is 8, not 11. The problem guarantees at least two distinct values, so an answer always exists.
Approach: sort, then skip the maximum
If the numbers are sorted from largest to smallest, the maximum is at the front, and equal values are next to each other. The second largest value is the first element that differs from the maximum. Scanning from the front and returning the first different value handles any number of copies of the maximum.
For the example, descending order is [11, 11, 11, 8, 8, 7, 3, 2]. The scan skips the three 11s and returns the first 8.
Sorting costs O(n log n), which is more than necessary. The statement's follow-up asks for a single pass without sorting: keep the largest and second largest values seen so far and update them as you go. This lesson takes the simpler route, which is easier to get right, and the idea of "skip the duplicates of the maximum" is the same.
Step by step
- Sort in descending order. Call
sortwith the rangenums.begin()tonums.end()and the comparatorgreater<int>(). The default order is ascending, andgreater<int>reverses it. The sort works in place. - Loop over the values with a range-based
for, using anintvariablenum. - Compare with the maximum. After sorting,
nums[0]is the largest value. Test whethernumdiffers from it. - Return the first different value. Because the vector is sorted, that value is the next one down, the second largest.
- Add a fallback return of
-1. If every element equals the maximum, the loop would end without returning. The problem rules that out, so the line never runs. It is there because a function with a return type must return on every path, and a missing return is undefined behaviour in C++.
The solution in C++
class Solution {
public:
int secondLargest(vector<int>& nums) {
sort(nums.begin(), nums.end(), greater<int>());
for (int num : nums) {
if (num != nums[0]) {
return num;
}
}
return -1;
}
};
The C++ details worth knowing:
sorttakes an iterator range.nums.begin()points at the first element andnums.end()points one past the last. The third argument is an optional comparison object.greater<int>()is a function object. It lives in the<functional>header and answers "is the first argument greater than the second?". With it,sortputs larger values first. An alternative is to sort ascending and read from the back, or to usenums.rbegin()andnums.rend().- The input is modified. The parameter is
vector<int>&, a reference, so the caller's vector ends up sorted. That avoids a copy but has a side effect. If the caller needs the original order, copy the vector first or take it by value. - Range-based
forcopies each value.int numis a copy of the element, which is cheap for anint. - Reading
nums[0]repeatedly. This is fine, since the vector is not empty when the loop body runs. The vector is never changed during the loop.
Complexity
Time: O(n log n). The sort dominates. The scan afterwards is O(n) in the worst case, when the second largest value is at the very end.
Space: O(log n), typically. The sort works in place on the vector. Common implementations use a small amount of stack space for the recursion, but the standard doesn't promise an exact bound, so it is safest to say that no extra array of size n is created.
Edge cases and common mistakes
- Returning
nums[1]after sorting. That is the second largest element, not value. For[11, 11, 8]it would return 11, but the answer is 8. - Two elements.
[1, 2]becomes[2, 1]and the function returns 1. - Negative numbers.
[-1, -5, -3]becomes[-1, -3, -5]and the function returns -3. Nothing in the approach depends on the sign. A one-pass version that starts its maximum at 0 would get this wrong. - Several copies of the maximum at the front.
[5, 5, 4]gives 4. This is why the comparison is againstnums[0]and not against the previous element. - Sorting ascending and returning the second element from the end. That has the same duplicate trap as
nums[1]. Scan for the first value that differs from the end. - Dropping the fallback return. The code may still compile, with a warning, but control reaching the end of a non-void function is undefined behaviour.
Testing
[2, 7, 11, 8, 11, 8, 3, 11]gives8.[1, 2]gives1.[5, 5, 4]gives4.[-1, -5, -3]gives-3.