Problem overview
The function second_largest receives three i32 values, a, b and c, and returns the middle one when they are ordered by size. For 7, 3, 9 the sorted order is 3, 7, 9, so the answer is 7. For 12, 8, 3 the answer is 8. The lesson asks for O(1) time and space, so the function can't sort a collection or loop.
Approach
The answer is always one of the three inputs, so test them one at a time. A number is the second largest, the middle one, when it is at least the smaller of the other two and at most the larger of the other two. For a that reads b.min(c) <= a && a <= b.max(c).
Check a, then b. If neither is in the middle, c must be, so there is no need to test it.
Take a = 7, b = 3, c = 9. The smaller of b and c is 3 and the larger is 9. Since 3 <= 7 <= 9, the function returns 7 immediately. With a = 12, b = 8, c = 3, the test for a compares against 3 and 8: 12 is above 8, so it fails. The test for b compares against 3 and 12: 3 <= 8 <= 12 holds, so it returns 8.
Step by step
- Test
a. The condition has two parts joined with&&:b.min(c) <= aanda <= b.max(c). If both hold, returna. The parameters are the lowercasea,bandc. - Test
b. The same idea with the roles changed:a.min(c) <= bandb <= a.max(c). If both hold, returnb. - Return
c. At this point neitheranorbis the middle value, socis. Write it as the function's final expression, with no semicolon.
The solution in Rust
impl Solution {
fn second_largest(a: i32, b: i32, c: i32) -> i32 {
if b.min(c) <= a && a <= b.max(c) {
return a;
}
if a.min(c) <= b && b <= a.max(c) {
return b;
}
c
}
}
Rust details worth knowing:
minandmaxare methods. Rust has no globalmin(x, y)function in the prelude. You call the method on one value and pass the other:b.min(c)is the smaller ofbandc. Both values must have the same type, herei32.- Final expression. The last line is
cwith no semicolon and noreturn. A block's last expression, when not followed by a semicolon, is its value, which is how Rust functions normally return. Writingc;instead turns it into a statement, the function then returns()and the compiler reports mismatched types. - Early
return. The tworeturnlines leave the function at once, so later lines run only when the earlier tests failed. This is why noelseis needed. - Precedence. Comparisons bind tighter than
&&, so the parts of each condition need no extra parentheses.
Complexity
Time: O(1). At most four comparisons and four min or max calls run, whatever the values are.
Space: O(1). Only the three parameters are used.
Edge cases and common mistakes
- Duplicates. With
5, 5, 1the sorted order is 1, 5, 5, so the second largest by position is 5. The function returns 5 from the first test, since1 <= 5 <= 5. With5, 1, 5,ais 5,b.min(c)is 1 andb.max(c)is 5, soaagain passes. When all three are equal, the function returnsa, which equals the others. - Negative numbers. The comparisons work the same: for
-1, -5, 0the function returns-1. - Using strict comparisons. With
<instead of<=, duplicates would fail every test, and the function would fall through toceven whencisn't the middle value. For5, 5, 1it would return 1. - Testing the wrong pair. The test for
amust look atbandc, not ataitself. Mixing up the names yields a condition that is true for the wrong number. - Confusing the middle with the median of a larger set. This approach works because there are exactly three values. For more values, sorting or a selection method is needed.
Testing
a = 7, b = 3, c = 9gives7.a = 12, b = 8, c = 3gives8.a = 1, b = 2, c = 3gives2.a = 3, b = 2, c = 1gives2.a = 5, b = 5, c = 1gives5.
