Problem overview
The method secondLargest receives an array of integers and returns its second largest value. Duplicates of the maximum don't count as the second place. For [2, 7, 11, 8, 11, 8, 3, 11] the maximum is 11, which appears three times, and the second largest value is 8. You may assume the array has at least two distinct values.
Approach: sort, then look from the back
Once the array is sorted ascending, the maximum is the last element. The second largest value is the first element, scanning backwards, that is different from the maximum. Skipping equal values is what handles duplicates: in [2, 3, 7, 8, 8, 11, 11, 11] the scan passes over the three 11s and stops at 8.
This costs O(n log n) for the sort and at most O(n) for the scan. The statement's follow-up asks whether one pass without sorting is possible. It is: keep the largest and the second largest seen so far and update both as you go. This lesson takes the simpler route, and its logic is easier to check.
Step by step
- Make a sorted copy.
val sorted = nums.sortedgives a new ascending array and leaves the input unchanged. - Scan from the second-to-last index down to 0. The last index holds the maximum, so the scan starts one position before it and moves left.
- Compare with the maximum. If
sorted(i)differs fromsorted(sorted.length - 1), it isn't a copy of the maximum. - Return it. Because the array is sorted, the first different value from the back is the second largest.
- Add a fallback. If the loop ends without returning, every element equals the maximum, and the method returns -1. With the guaranteed two distinct values that line never runs, but the method needs a value on every path.
The solution in Scala
class Solution {
def secondLargest(nums: Array[Int]): Int = {
val sorted = nums.sorted
for (i <- sorted.length - 2 to 0 by -1) {
if (sorted(i) != sorted(sorted.length - 1)) {
return sorted(i)
}
}
return -1
}
}
The Scala details worth knowing:
- Counting down with
by -1.a to b by -1is a range that starts ataand steps down tob, withbincluded. Here it visitssorted.length - 2, then one less each time, down to 0. Becausetoincludes its end, index 0 is checked.sorted.length - 2 until 0 by -1would stop before 0. - Operator precedence in the range. Writing
sorted.length - 2 to 0 by -1parses as((sorted.length - 2) to 0) by -1, because arithmetic binds tighter than the range methods. returninside afor. Aforloop over a range is translated to a call offoreachwith a function, soreturninside it leaves the whole method from inside that function. This works, but Scala 3.5 reports a warning that non-local returns are no longer supported and suggestsscala.util.boundary. Awhileloop avoids the warning, becausereturnthere is an ordinary one.- The local name
sortedis an ordinaryvaland has nothing to do with thesortedmethod called innums.sorted. - Parentheses index arrays.
sorted(i)reads an element.
Complexity
Time: O(n log n). Sorting dominates. The backward scan is O(n) in the worst case, when the maximum repeats many times.
Space: O(n) for the sorted copy.
Edge cases and common mistakes
- Repeated maximum.
[11, 11, 3]gives 3. Simply returningsorted(sorted.length - 2)would give 11 here, which is wrong. - Two elements.
[1, 2]gives 1. - Negative numbers.
[-5, -5, -9]gives -9. - All values equal. This breaks the guarantee. The code returns -1, which could also be a real answer, so input of this kind would need a different design.
- Starting the scan at the last index. The element would be compared with itself and never differ, so it is just wasted work.
- Stopping the range before 0. With
untilinstead ofto, the smallest element is never examined, and[1, 2]would return -1.
Testing
[2, 7, 11, 8, 11, 8, 3, 11]gives8.[1, 2]gives1.[11, 11, 3]gives3.[-5, -5, -9]gives-9.
