Problem overview
The method secondLargest receives a vector of integers, nums, and returns its second largest value. Duplicates of the maximum do not count as a second place: in [2, 7, 11, 8, 11, 8, 3, 11] the maximum is 11, which appears three times, and the answer is 8. The array is guaranteed to contain at least two distinct values, so an answer always exists.
Approach: track two numbers while scanning
The obvious solution sorts the vector in descending order and then walks past the copies of the maximum until it finds a smaller value. That costs O(n log n). The follow-up asks for one pass without sorting, and it is possible because you only ever need to remember two numbers: the largest so far and the largest value below it.
Keep two variables, maxVal and second. For every number in the vector there are three cases:
- It is bigger than
maxVal. It becomes the new maximum, and the old maximum is now the runner-up. - It is smaller than
maxValbut bigger thansecond. It becomes the new runner-up. - Anything else, including a number equal to the maximum, changes nothing.
Here is the example traced. Both variables start at the smallest int, written -inf here.
num maxVal second
2 2 -inf
7 7 2
11 11 7
8 11 8
11 11 8 (equal to max: ignored)
8 11 8 (equal to second: ignored)
3 11 8
11 11 8
It helps to see why the middle case needs both conditions. A runner-up must be a different value from the maximum, so a number equal to maxVal can't qualify, and it must beat the current runner-up to be worth recording. Because the three cases cover every possible number, no element is left in doubt.
A different one-pass idea is to find the maximum first, then scan again for the largest value that is not equal to it. That is two passes and still O(n). Tracking both values together does the job in a single loop, which is what the follow-up asks for.
Step by step
- Declare
maxValandsecondasints, both set toINT_MIN. Any real element is at least that large, so the first element always wins the comparison. This avoids a special "first iteration" check. - Loop over
numswith a range-basedforand anintnamednum. - Test for a new maximum:
num > maxVal. - Demote, then replace. Inside that branch, copy
maxValintosecondfirst and only then assignnumtomaxVal. Reversing the two lines loses the old maximum. - Test for a new runner-up in an
else if:num < maxVal && num > second. The first condition keeps copies of the maximum out of second place, and the second keeps small numbers from replacing a better runner-up. - Update
secondtonumin that branch. - Return
secondafter the loop.
The solution in C++
class Solution {
public:
int secondLargest(vector<int>& nums) {
int maxVal = INT_MIN;
int second = INT_MIN;
for (int num : nums) {
if (num > maxVal) {
second = maxVal;
maxVal = num;
} else if (num < maxVal && num > second) {
second = num;
}
}
return second;
}
};
C++ details worth knowing:
INT_MINis a macro for the smallestint, usually -2,147,483,648. It comes from<climits>. It is a safe starting point because no element can be smaller. Because the lesson guarantees two distinct values,secondis always overwritten before the loop ends.- Loop by value.
for (int num : nums)copies eachint, which is as cheap as passing a reference. For bigger element types you would writeconst auto&. - The vector is taken by reference (
vector<int>& nums), so calling the method does not copy the data, and this code never modifies it. - Chain of
ifandelse if. At most one branch runs per element. Whennum > maxValis true, theelse ifis skipped, which is why the demotion is done inside the first branch.
Complexity
Time: O(n). One pass over the vector, with a constant amount of work per element.
Space: O(1). Two integers, no matter how long the vector is. Sorting would add O(n log n) time, which this approach avoids.
Edge cases and common mistakes
- Duplicates of the maximum. In
[5, 5, 4]the answer is 4, not 5. Thenum < maxValtest in the second branch is what stops the second 5 from becomingsecond. If you wrote justnum > second, the second 5 would be accepted when it arrives and the function would return 5. - Negative numbers. For
[-5, -1, -9]the answer is -5. Starting the variables at 0 instead ofINT_MINwould give 0, a value that is not in the vector. - Two elements.
[1, 2]returns 1. - Increasing order. When the vector is already sorted ascending, every number is a new maximum, and the demotion step is the only thing that fills
second. - The order of the two assignments in the first branch matters, as noted above.
- Fewer than two distinct values. This case is excluded by the lesson. If it happened, say for
[4, 4], the function would returnINT_MIN, which can't be told from a realINT_MINin the data. INT_MINas a real element. For[INT_MIN, 3]the function returnsINT_MINcorrectly, because 3 first becomes the maximum and demotes the old value.
Testing
[2, 7, 11, 8, 11, 8, 3, 11]gives 8.[5, 5, 4]gives 4.[-5, -1, -9]gives -5.[1, 2]gives 1.