LeetCode 3194 - Minimum Average of Smallest and Largest Elements
The problem gives us an even-length integer array nums. We repeatedly perform the following operation until the array becomes empty: 1. Remove the smallest element. 2. Remove the largest element. 3. Compute their average. 4. Store that average in another array called averages.
Difficulty: 🟢 Easy
Topics: Array, Two Pointers, Sorting
Solution
LeetCode 3194 - Minimum Average of Smallest and Largest Elements
Problem Understanding
The problem gives us an even-length integer array nums. We repeatedly perform the following operation until the array becomes empty:
- Remove the smallest element.
- Remove the largest element.
- Compute their average.
- Store that average in another array called
averages.
After all pairs have been processed, we return the minimum value that appeared in averages.
In simpler terms, after sorting the array, we always pair the smallest remaining number with the largest remaining number. For every pair, we compute:
$$\frac{\text{smallest} + \text{largest}}{2}$$
Among all these averages, we must return the smallest one.
The constraints are very small:
2 <= nums.length <= 50- The array length is always even.
- Each value is between
1and50.
These constraints tell us that performance is not a major concern. Even less efficient solutions would pass comfortably. However, the problem is designed to test understanding of sorting and two pointer techniques.
An important observation is that the problem guarantees an even number of elements, so every element can always be paired. We never need to handle leftover elements.
Several edge cases are worth considering:
- Arrays with only two elements.
- Arrays where all numbers are equal.
- Arrays with repeated minimum or maximum values.
- Arrays where the minimum average occurs late in the process instead of early.
A naive implementation could repeatedly search for minimum and maximum elements and physically remove them from the array, but there is a cleaner and more efficient way.
Approaches
Brute Force Approach
The brute force solution directly simulates the process exactly as described.
At every step:
- Find the smallest element.
- Find the largest element.
- Remove both from the array.
- Compute their average.
- Track the minimum average seen so far.
Since removing elements from a list or array is expensive, each operation may require shifting elements. Additionally, repeatedly searching for minimum and maximum values costs linear time.
Although this works correctly because it follows the problem statement exactly, it is inefficient compared to a sorting-based solution.
Optimal Approach
The key insight is that after sorting the array, the smallest remaining element is always at the left side, and the largest remaining element is always at the right side.
Instead of repeatedly searching and deleting elements, we can:
- Sort the array once.
- Use two pointers:
leftstarts at the beginning.rightstarts at the end.
- Pair
nums[left]withnums[right]. - Compute the average.
- Move both pointers inward.
This avoids repeated searches and deletions entirely.
Because the array is sorted, every iteration naturally gives us the current minimum and maximum remaining elements.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n²) | O(1) | Repeatedly searches and removes min/max elements |
| Optimal | O(n log n) | O(1) or O(log n) depending on sorting implementation | Sort once, then use two pointers |
Algorithm Walkthrough
Optimal Algorithm
- Sort the array in non-decreasing order.
Sorting ensures the smallest values are on the left and the largest values are on the right. 2. Initialize two pointers.
Set:
left = 0right = len(nums) - 1
These pointers represent the smallest and largest remaining elements. 3. Initialize the answer.
Start with a very large value such as positive infinity. This variable will store the minimum average encountered.
4. Process pairs while left < right.
At every iteration:
- Compute the average:
$$\frac{nums[left] + nums[right]}{2}$$
- Update the minimum answer if this average is smaller.
- Move
leftone step right. - Move
rightone step left.
- Return the minimum average.
After processing all pairs, the stored minimum value is the answer.
Why it works
After sorting, the smallest remaining element is always at the left pointer and the largest remaining element is always at the right pointer. Moving inward after each pairing exactly simulates the process described in the problem statement. Since every valid pair is processed once and every average is considered, the minimum recorded average must be correct.
Python Solution
from typing import List
class Solution:
def minimumAverage(self, nums: List[int]) -> float:
nums.sort()
left = 0
right = len(nums) - 1
minimum_average = float("inf")
while left < right:
current_average = (nums[left] + nums[right]) / 2
minimum_average = min(minimum_average, current_average)
left += 1
right -= 1
return minimum_average
The implementation begins by sorting the array so that the smallest and largest remaining elements can always be accessed directly.
Two pointers are then initialized. The left pointer starts at the beginning of the sorted array, while the right pointer starts at the end.
Inside the loop, the code computes the average of the current smallest and largest elements. The result is compared against the current minimum average.
After processing the pair, both pointers move inward to represent removing those elements from consideration.
The loop continues until all pairs have been processed, and the smallest average found is returned.
Go Solution
package main
import (
"math"
"sort"
)
func minimumAverage(nums []int) float64 {
sort.Ints(nums)
left := 0
right := len(nums) - 1
minimumAverage := math.Inf(1)
for left < right {
currentAverage := float64(nums[left]+nums[right]) / 2.0
if currentAverage < minimumAverage {
minimumAverage = currentAverage
}
left++
right--
}
return minimumAverage
}
The Go implementation follows the same logic as the Python version.
The array is sorted using sort.Ints. Since Go distinguishes between integers and floating point values, explicit conversion to float64 is necessary before division. Using 2.0 ensures floating point division instead of integer division.
Go slices are used directly, and no additional arrays are required.
Worked Examples
Example 1
Input:
nums = [7,8,3,4,15,13,4,1]
After sorting:
[1,3,4,4,7,8,13,15]
| Step | left value | right value | Average | Minimum So Far |
|---|---|---|---|---|
| 1 | 1 | 15 | 8.0 | 8.0 |
| 2 | 3 | 13 | 8.0 | 8.0 |
| 3 | 4 | 8 | 6.0 | 6.0 |
| 4 | 4 | 7 | 5.5 | 5.5 |
Final answer:
5.5
Example 2
Input:
nums = [1,9,8,3,10,5]
After sorting:
[1,3,5,8,9,10]
| Step | left value | right value | Average | Minimum So Far |
|---|---|---|---|---|
| 1 | 1 | 10 | 5.5 | 5.5 |
| 2 | 3 | 9 | 6.0 | 5.5 |
| 3 | 5 | 8 | 6.5 | 5.5 |
Final answer:
5.5
Example 3
Input:
nums = [1,2,3,7,8,9]
After sorting:
[1,2,3,7,8,9]
| Step | left value | right value | Average | Minimum So Far |
|---|---|---|---|---|
| 1 | 1 | 9 | 5.0 | 5.0 |
| 2 | 2 | 8 | 5.0 | 5.0 |
| 3 | 3 | 7 | 5.0 | 5.0 |
Final answer:
5.0
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n log n) | Sorting dominates the runtime |
| Space | O(1) or O(log n) | Depends on the sorting implementation |
The main cost comes from sorting the array. After sorting, the two pointer traversal processes each pair exactly once, which takes linear time.
The algorithm itself uses only a few extra variables. However, the underlying sorting implementation may use additional stack space internally.
Test Cases
from typing import List
class Solution:
def minimumAverage(self, nums: List[int]) -> float:
nums.sort()
left = 0
right = len(nums) - 1
minimum_average = float("inf")
while left < right:
current_average = (nums[left] + nums[right]) / 2
minimum_average = min(minimum_average, current_average)
left += 1
right -= 1
return minimum_average
solution = Solution()
assert solution.minimumAverage([7,8,3,4,15,13,4,1]) == 5.5 # Example 1
assert solution.minimumAverage([1,9,8,3,10,5]) == 5.5 # Example 2
assert solution.minimumAverage([1,2,3,7,8,9]) == 5.0 # Example 3
assert solution.minimumAverage([1,100]) == 50.5 # Smallest valid input
assert solution.minimumAverage([5,5,5,5]) == 5.0 # All elements equal
assert solution.minimumAverage([1,1,1,10,10,10]) == 5.5 # Repeated extremes
assert solution.minimumAverage([2,4,6,8]) == 5.0 # Evenly spaced values
assert solution.minimumAverage([1,2,50,51]) == 26.0 # Two pairs with same average
assert solution.minimumAverage([1,50,2,49,3,48]) == 25.5 # Multiple decreasing averages
Test Summary
| Test | Why |
|---|---|
[7,8,3,4,15,13,4,1] |
Verifies the first official example |
[1,9,8,3,10,5] |
Verifies the second official example |
[1,2,3,7,8,9] |
Verifies equal averages across all pairs |
[1,100] |
Smallest possible valid input |
[5,5,5,5] |
All values identical |
[1,1,1,10,10,10] |
Repeated minimum and maximum values |
[2,4,6,8] |
Standard balanced pairing |
[1,2,50,51] |
Multiple pairs producing equal averages |
[1,50,2,49,3,48] |
Ensures minimum may occur later in processing |
Edge Cases
Minimum Input Size
The smallest valid array contains exactly two elements. In this case, there is only one pair and therefore only one average. A buggy implementation might incorrectly assume multiple iterations are required. The two pointer solution handles this naturally because the loop executes exactly once.
All Elements Equal
If every element is identical, every computed average will also be identical. Some implementations may unnecessarily track multiple values or accidentally introduce floating point inconsistencies. Since this solution directly computes each average using floating point division, all results remain correct and equal.
Duplicate Minimum or Maximum Values
Arrays may contain repeated smallest or largest elements. A brute force implementation that removes elements incorrectly could accidentally remove too many duplicates. The sorted two pointer approach avoids this issue because each index is processed exactly once.
Minimum Average Appears Late
The smallest average is not guaranteed to appear during the first pairing. For example:
[1,50,2,49,3,48]
produces averages:
24.5, 25.5, 26.0
A flawed implementation that returns early would fail here. This solution processes every pair and tracks the minimum throughout the entire traversal.