1058. Minimize Rounding Error to Meet Target π
Problem Description
You are given an array of prices (as strings) and a target sum. Each price needs to be rounded to either its floor or ceiling value, and the sum of all rounded prices must equal the target.
The goal is to minimize the total rounding error, which is calculated as the sum of absolute differences between each rounded value and its original value: Ξ£ |Round(pi) - pi|
.
Key constraints:
- Each price
pi
can only be rounded toFloor(pi)
orCeil(pi)
- The sum of all rounded prices must exactly equal the target
- If it's impossible to achieve the target sum through any combination of floor/ceiling operations, return
"-1"
- If a valid solution exists, return the minimum rounding error as a string with exactly 3 decimal places
Example breakdown:
- If you have prices
[1.5, 2.3, 3.7]
and target7
:- Possible rounding:
[1, 2, 4]
β sum = 7 β - Rounding error:
|1-1.5| + |2-2.3| + |4-3.7| = 0.5 + 0.3 + 0.3 = 1.1
- Return:
"1.100"
- Possible rounding:
The challenge is to determine which prices to round down (floor) and which to round up (ceiling) to achieve the exact target sum while minimizing the total rounding error.
Intuition
To find the minimum rounding error, let's first understand the bounds of what's possible. If we round all prices down (floor), we get the minimum possible sum. If we round all prices up (ceiling), we get the maximum possible sum. The target must fall within this range for a solution to exist.
The key insight is that rounding a number with a decimal part causes different errors depending on the direction:
- Rounding down from
x.d
tox
creates an error ofd
- Rounding up from
x.d
tox+1
creates an error of1-d
For integers (no decimal part), there's no choice - we must keep them as is with zero error.
Now, if we start with all prices floored, we need to selectively round some up to reach the target. Each time we change a floor to a ceiling, we:
- Increase the sum by 1
- Change the error from
d
to1-d
The net change in error is (1-d) - d = 1-2d
. To minimize total error, we should prioritize rounding up the numbers with the largest decimal parts (largest d
values), as this gives us the most negative change in error (1-2d)
.
This leads to a greedy strategy:
- Start with all numbers floored and calculate how many need to be ceiled to reach the target
- Sort the decimal parts in descending order
- Round up the numbers with the largest decimal parts first
- The final error is the sum of
(1-d)
for rounded up numbers plus the sum ofd
for rounded down numbers
Solution Approach
Let's walk through the implementation step by step:
Step 1: Extract decimal parts and calculate bounds
mi = 0
arr = []
for p in prices:
p = float(p)
mi += int(p) # Sum of all floors
if d := p - int(p): # Extract decimal part
arr.append(d)
- Convert each price string to float
mi
accumulates the sum of all floored values (minimum possible sum)arr
collects all non-zero decimal parts (these are the prices we can choose to round up)
Step 2: Check feasibility
if not mi <= target <= mi + len(arr):
return "-1"
- The minimum sum is
mi
(all floored) - The maximum sum is
mi + len(arr)
(all non-integers rounded up) - If target is outside this range, it's impossible to achieve
Step 3: Determine how many to round up
d = target - mi
- Since we start with all floored (sum =
mi
), we need to round up exactlyd
numbers to reach the target - Each rounding up increases the sum by 1
Step 4: Greedy selection
arr.sort(reverse=True)
ans = d - sum(arr[:d]) + sum(arr[d:])
- Sort decimal parts in descending order
- Round up the
d
largest decimal parts (firstd
elements) - Calculate total error:
- For rounded up numbers (
arr[:d]
): error =1 - decimal_part
for each - Sum of errors =
d - sum(arr[:d])
- For rounded down numbers (
arr[d:]
): error =decimal_part
for each - Sum of errors =
sum(arr[d:])
- Total error =
d - sum(arr[:d]) + sum(arr[d:])
- For rounded up numbers (
Step 5: Format result
return f'{ans:.3f}'
- Return the minimum error formatted to 3 decimal places
The algorithm runs in O(n log n)
time due to sorting, with O(n)
space for storing decimal parts.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with prices = ["2.700", "3.400", "5.900"] and target = 11.
Step 1: Extract decimal parts and calculate bounds
- Convert to floats: [2.7, 3.4, 5.9]
- Extract floors and decimal parts:
- 2.7 β floor = 2, decimal = 0.7
- 3.4 β floor = 3, decimal = 0.4
- 5.9 β floor = 5, decimal = 0.9
mi
(sum of floors) = 2 + 3 + 5 = 10arr
(decimal parts) = [0.7, 0.4, 0.9]
Step 2: Check feasibility
- Minimum possible sum = 10 (all floored)
- Maximum possible sum = 10 + 3 = 13 (all ceiled)
- Target = 11 is within [10, 13] β
Step 3: Determine how many to round up
- We need: target - mi = 11 - 10 = 1
- So we must round up exactly 1 number
Step 4: Greedy selection
- Sort decimal parts descending: [0.9, 0.7, 0.4]
- Round up the 1 largest decimal (0.9):
- 5.9 β 6 (round up), error = |6 - 5.9| = 0.1
- Keep others floored:
- 2.7 β 2 (round down), error = |2 - 2.7| = 0.7
- 3.4 β 3 (round down), error = |3 - 3.4| = 0.4
Calculate total error:
- Using the formula:
d - sum(arr[:d]) + sum(arr[d:])
- = 1 - sum([0.9]) + sum([0.7, 0.4])
- = 1 - 0.9 + 1.1
- = 1.2
Step 5: Format result
- Return "1.200"
Verification:
- Rounded values: [2, 3, 6]
- Sum: 2 + 3 + 6 = 11 β (matches target)
- Total error: 0.7 + 0.4 + 0.1 = 1.2 β
Solution Implementation
1class Solution:
2 def minimizeError(self, prices: List[str], target: int) -> str:
3 # Calculate the minimum possible sum (all prices floored)
4 min_sum = 0
5 # Store decimal parts for prices that can be ceiled
6 decimal_parts = []
7
8 for price_str in prices:
9 price_float = float(price_str)
10 floor_value = int(price_float)
11 min_sum += floor_value
12
13 # Calculate decimal part (difference between float and floor)
14 decimal_part = price_float - floor_value
15 if decimal_part > 0: # Only add non-zero decimal parts
16 decimal_parts.append(decimal_part)
17
18 # Check if target is achievable
19 # min_sum: all prices floored
20 # min_sum + len(decimal_parts): all prices with decimals are ceiled
21 max_sum = min_sum + len(decimal_parts)
22 if not (min_sum <= target <= max_sum):
23 return "-1"
24
25 # Calculate how many prices need to be ceiled
26 num_to_ceil = target - min_sum
27
28 # Sort decimal parts in descending order to minimize error
29 # Ceiling prices with larger decimals minimizes the total error
30 decimal_parts.sort(reverse=True)
31
32 # Calculate total error:
33 # - For ceiled prices: error = (1 - decimal_part)
34 # - For floored prices: error = decimal_part
35 error_from_ceiled = num_to_ceil - sum(decimal_parts[:num_to_ceil])
36 error_from_floored = sum(decimal_parts[num_to_ceil:])
37 total_error = error_from_ceiled + error_from_floored
38
39 # Format result to 3 decimal places
40 return f'{total_error:.3f}'
41
1class Solution {
2 public String minimizeError(String[] prices, int target) {
3 // Calculate the minimum possible sum (all prices floored)
4 int minimumSum = 0;
5 // Store fractional parts of all prices that have decimals
6 List<Double> fractionalParts = new ArrayList<>();
7
8 // Process each price string
9 for (String priceStr : prices) {
10 double price = Double.valueOf(priceStr);
11 int floorPrice = (int) price;
12 minimumSum += floorPrice;
13
14 // Calculate fractional part (decimal portion)
15 double fractionalPart = price - floorPrice;
16 // Only add non-zero fractional parts to the list
17 if (fractionalPart > 0) {
18 fractionalParts.add(fractionalPart);
19 }
20 }
21
22 // Check if target is achievable
23 // Minimum sum: all prices floored
24 // Maximum sum: all prices with fractional parts ceiled (minimumSum + fractionalParts.size())
25 if (target < minimumSum || target > minimumSum + fractionalParts.size()) {
26 return "-1";
27 }
28
29 // Calculate how many prices need to be ceiled
30 int numberToCeil = target - minimumSum;
31
32 // Sort fractional parts in descending order
33 // We want to ceil the prices with larger fractional parts first to minimize error
34 fractionalParts.sort(Collections.reverseOrder());
35
36 // Calculate total error
37 double totalError = numberToCeil; // Initial error from ceiled prices
38
39 // Subtract fractional parts of prices that will be ceiled
40 // (ceiling adds 1 - fractionalPart to the error)
41 for (int i = 0; i < numberToCeil; ++i) {
42 totalError -= fractionalParts.get(i);
43 }
44
45 // Add fractional parts of prices that will be floored
46 // (flooring adds fractionalPart to the error)
47 for (int i = numberToCeil; i < fractionalParts.size(); ++i) {
48 totalError += fractionalParts.get(i);
49 }
50
51 // Format the result to 3 decimal places
52 DecimalFormat decimalFormatter = new DecimalFormat("#0.000");
53 return decimalFormatter.format(totalError);
54 }
55}
56
1class Solution {
2public:
3 string minimizeError(vector<string>& prices, int target) {
4 // Calculate the minimum possible sum (all prices floored)
5 int minSum = 0;
6 vector<double> fractionalParts;
7
8 // Process each price string
9 for (auto& priceStr : prices) {
10 double price = stod(priceStr);
11 int floorPrice = (int)price;
12 minSum += floorPrice;
13
14 // Collect fractional parts for prices that can be rounded up
15 double fractionalPart = price - floorPrice;
16 if (fractionalPart > 0) {
17 fractionalParts.push_back(fractionalPart);
18 }
19 }
20
21 // Check if target is achievable
22 // minSum: all prices floored
23 // maxSum: minSum + number of prices that can be rounded up
24 if (target < minSum || target > minSum + fractionalParts.size()) {
25 return "-1";
26 }
27
28 // Calculate how many prices need to be rounded up
29 int numToRoundUp = target - minSum;
30
31 // Sort fractional parts in descending order
32 // We want to round up the largest fractional parts to minimize error
33 sort(fractionalParts.rbegin(), fractionalParts.rend());
34
35 // Calculate total rounding error
36 double totalError = numToRoundUp; // Initial error from rounding up numToRoundUp prices
37
38 // Subtract fractional parts of prices we round up (reduces error)
39 for (int i = 0; i < numToRoundUp; ++i) {
40 totalError -= fractionalParts[i];
41 }
42
43 // Add fractional parts of prices we round down (increases error)
44 for (int i = numToRoundUp; i < fractionalParts.size(); ++i) {
45 totalError += fractionalParts[i];
46 }
47
48 // Format result to 3 decimal places
49 string result = to_string(totalError);
50 return result.substr(0, result.find('.') + 4);
51 }
52};
53
1function minimizeError(prices: string[], target: number): string {
2 // Calculate the minimum possible sum (all prices floored)
3 let minSum = 0;
4 const fractionalParts: number[] = [];
5
6 // Process each price string
7 for (const priceStr of prices) {
8 const price = parseFloat(priceStr);
9 const floorPrice = Math.floor(price);
10 minSum += floorPrice;
11
12 // Collect fractional parts for prices that can be rounded up
13 const fractionalPart = price - floorPrice;
14 if (fractionalPart > 0) {
15 fractionalParts.push(fractionalPart);
16 }
17 }
18
19 // Check if target is achievable
20 // minSum: all prices floored
21 // maxSum: minSum + number of prices that can be rounded up
22 if (target < minSum || target > minSum + fractionalParts.length) {
23 return "-1";
24 }
25
26 // Calculate how many prices need to be rounded up
27 const numToRoundUp = target - minSum;
28
29 // Sort fractional parts in descending order
30 // We want to round up the largest fractional parts to minimize error
31 fractionalParts.sort((a, b) => b - a);
32
33 // Calculate total rounding error
34 let totalError = numToRoundUp; // Initial error from rounding up numToRoundUp prices
35
36 // Subtract fractional parts of prices we round up (reduces error)
37 for (let i = 0; i < numToRoundUp; i++) {
38 totalError -= fractionalParts[i];
39 }
40
41 // Add fractional parts of prices we round down (increases error)
42 for (let i = numToRoundUp; i < fractionalParts.length; i++) {
43 totalError += fractionalParts[i];
44 }
45
46 // Format result to 3 decimal places
47 return totalError.toFixed(3);
48}
49
Time and Space Complexity
Time Complexity: O(n log n)
where n
is the length of the prices array.
- Converting each price string to float and calculating the floor value takes
O(n)
time - Building the array of decimal parts takes
O(n)
time - Sorting the decimal parts array takes
O(k log k)
time wherek β€ n
is the number of non-integer prices - Computing the sum of subarrays takes
O(k)
time - Since
k β€ n
, the dominant operation is sorting, giving usO(n log n)
overall
Space Complexity: O(n)
- The
arr
list stores at mostn
decimal parts, requiringO(n)
space - Other variables (
mi
,d
,ans
) use constant spaceO(1)
- The sorting operation may use
O(log n)
space for the recursive call stack (depending on the implementation) - Overall space complexity is
O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Floating Point Precision Issues
One of the most common pitfalls in this problem is dealing with floating-point arithmetic inaccuracies. When converting strings to floats and performing calculations, small precision errors can accumulate.
Problem Example:
price = "0.1"
p = float(price) # 0.1
floor_val = int(p) # 0
decimal = p - floor_val # Might be 0.09999999999999998 instead of 0.1
Solution:
Use the decimal
module for precise arithmetic or round intermediate calculations appropriately:
from decimal import Decimal, ROUND_HALF_UP
price = Decimal("0.1")
floor_val = int(price)
decimal_part = price - floor_val # Exactly 0.1
2. Integer Prices Handling
The code might incorrectly handle prices that are already integers (like "5.000" or "3").
Problem Example:
price = "5.000"
p = float(price) # 5.0
decimal = p - int(p) # 0.0
if decimal: # This condition fails, but "5.000" could still be in prices array
arr.append(decimal)
Solution: Be explicit about checking for near-zero decimals:
decimal_part = price_float - floor_value if decimal_part > 1e-9: # Use epsilon comparison decimal_parts.append(decimal_part)
3. Incorrect Target Feasibility Check
A subtle bug can occur when all prices are integers. The maximum sum calculation assumes we can only increase the sum by the count of non-integer prices.
Problem Example:
prices = ["1", "2", "3"] # All integers target = 7 # decimal_parts = [] (empty) # min_sum = 6, max_sum = 6 + 0 = 6 # Returns "-1" even though target 7 might seem unreachable
This is actually correct behavior, but developers might misunderstand that integer prices cannot be "rounded up" since ceil(integer) = integer.
4. String Formatting Edge Cases
The final formatting might produce unexpected results for very small errors.
Problem Example:
total_error = 0.0005
return f'{total_error:.3f}' # Returns "0.001" due to rounding
Solution: Consider whether you want banker's rounding or always round down for consistency:
import math
# To always round down to 3 decimals:
truncated = math.floor(total_error * 1000) / 1000
return f'{truncated:.3f}'
5. Empty Decimal Parts Array
When all prices are integers, sorting an empty array and slicing it could cause issues in some implementations.
Problem Example:
decimal_parts = []
decimal_parts.sort(reverse=True) # OK, but empty
sum(decimal_parts[:0]) # Returns 0, which is correct
sum(decimal_parts[0:]) # Returns 0, which is correct
While Python handles this gracefully, it's worth adding an early check:
if not decimal_parts and target != min_sum: return "-1" if not decimal_parts: return "0.000"
Which algorithm should you use to find a node that is close to the root of the tree?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Want a Structured Path to Master System Design Too? Donβt Miss This!