Problem overview
TrimmedTotal gets a List<int> named nums and returns their sum without one smallest and one largest value. This is how some contests score: each judge gives a number, and the lowest and the highest are dropped so no single judge decides alone.
Only one copy of each extreme is dropped. In [5, 1, 5, 9, 1] the smallest value, 1, appears twice, but only one 1 is dropped, together with the 9. The rest, 5 + 5 + 1, is 11. For [7, 9, 6, 8, 10] the answer is 24, and for [-4, 3, -2] it is -2. The list has 3 to 100 numbers, so something always remains.
Approach
You do not need to find and remove the extremes from the list. Subtract them instead: the total of everything, minus the smallest value, minus the largest value. Subtracting the smallest value once takes away exactly one copy of it, whether it appears once or five times. The same goes for the largest.
LINQ provides all three numbers: Sum(), Min() and Max(). Each one makes its own pass over the list.
Step by step
totalisnums.Sum(), the sum of all the numbers.smallestisnums.Min().largestisnums.Max().- Return
total - smallest - largest.
The solution in C#
class Solution {
public int TrimmedTotal(List<int> nums) {
int total = nums.Sum();
int smallest = nums.Min();
int largest = nums.Max();
return total - smallest - largest;
}
}
Three small variables make each step readable, but return nums.Sum() - nums.Min() - nums.Max(); would do the same.
Complexity
Three passes over n numbers are still O(n) time, with O(1) extra space.
One loop instead of three calls
You can collect all three numbers in a single pass:
int total = 0;
int smallest = nums[0];
int largest = nums[0];
foreach (int num in nums) {
total += num;
if (num < smallest) smallest = num;
if (num > largest) largest = num;
}
return total - smallest - largest;
It returns 11 for [5, 1, 5, 9, 1] and 24 for [7, 9, 6, 8, 10]. This does one pass instead of three, but both versions are O(n), and the built-in calls are shorter and harder to get wrong. Use the loop when you need the three values and more in the same pass.
Two details of the built-ins are worth knowing. Min() and Max() throw an InvalidOperationException on an empty list, which cannot happen here because the list has at least 3 numbers. And Sum() on ints checks for overflow: a list such as [2000000000, 2000000000] throws an OverflowException instead of wrapping around. With numbers up to 1000, neither matters.
Another way
You can sort, then skip the first and the last item: nums.OrderBy(n => n).Skip(1).SkipLast(1).Sum(). It gives 24 for [7, 9, 6, 8, 10] and 11 for [5, 1, 5, 9, 1], like the lesson's code, but sorting costs O(n log n) for no benefit.
Testing
Outputs from running the solution:
| nums | Result |
|---|---|
[7, 9, 6, 8, 10] | 24 |
[5, 1, 5, 9, 1] | 11 |
[-4, 3, -2] | -2 |
[4, 4, 4] | 4 |
In the last row the smallest and the largest are the same value, 4. They are still two different items, and each is dropped once, which leaves one 4.
Common mistakes
- Removing every copy of the extremes. A
RemoveAllor aWhere(n => n != smallest)removes both 1s in the second example, so only5 + 5is left and the result is 10, not 11. - Subtracting before adding. Compute the total of all the numbers first, then take the extremes off.
- Assuming the list has 3 numbers. The list can have up to 100. The same three calls work for any length of at least 3.
