1058. Minimize Rounding Error to Meet Target
Problem Description
In this problem, we are given an array of prices and a target value. We are asked to round each price either up (ceil) or down (floor) to an integer such that the sum of the rounded prices equals the target. Our objective is to minimize the rounding error, which is defined as the sum of the absolute differences between the original prices and their rounded values.
To clarify with an example, if we have prices prices = [0.70, 2.80, 4.90]
and a target of 8
, our goal is to round these prices to whole numbers such as 1, 3, and 4
, respectively, so that their sum is 8
. The rounding error in this case is |1-0.70| + |3-2.80| + |4-4.90| = 0.30 + 0.20 + 0.10 = 0.60
.
Yet, there might be cases where it is impossible to round the prices to reach the exact target sum. For instance, if we had the target 10
in the previous example, there’s no way to round 0.70, 2.80, 4.90
to sum up to 10
.
Intuition
To arrive at the solution, a good starting point is to consider the minimum and maximum sums we can get by rounding all prices down (using floor) or up (using ceil), respectively. We calculate the total sum of the floored values and count the items that have a fraction (i.e., numbers that are not already integers). If the target is outside the range of the minimum sum and the maximum sum (minimum sum <= target <= maximum sum
), we return "-1" as it is impossible to achieve the target by rounding.
Given that the target is within reach, we want to minimize the error by carefully choosing which numbers to round up and which to round down. Since this rounding must add up to the target, we need to round up a specific number of prices, corresponding to the difference between the target and the minimum sum we calculated earlier. To minimize the error, we should prioritize rounding up numbers with the smallest fractional parts, as doing so will lead to the smallest possible error.
The solution approach is as follows:
- Calculate the total sum of floor values of all the prices.
- Calculate how much we need to add to the floored sum to reach the target.
- Sort the fractional parts of each number in descending order. By doing this, we can ensure that when rounding up, we always add the smallest necessary amount to our total sum.
- Compute the error contributed by the numbers we need to round up (first
d
numbers after sorting) and the error contributed by the numbers we leave as floor values. - Return the sum of these errors as a formatted string with three decimal places.
Solution Approach
The solution uses simple data manipulation techniques and sorting algorithm to achieve the desired minimum error value. Following is the detailed explanation of how the Python code implements the solution:
-
Initial Summation and Difference Collection The first step in the code iterates through the given
prices
and calculates the sum of their floor values. This is done by iterating each pricep
in theprices
array, converting it to a float, and then adding its floor value tomi
. Simultaneously, the code checks if the given price has a non-zero fractional part (i.e.,p
is not an integer) and, if so, appends the fractional part to thearr
list by usingd := p - int(p)
.This approach employs basic looping and arithmetic operations, and utilizes a list to collect all fractional parts for later processing.
-
Checking the Feasibility After initial summation and difference collection, we verify whether it's possible to hit the
target
by rounding. This is checked by the conditionif not mi <= target <= mi + len(arr): return "-1"
. Here,mi
denotes the sum of the floor values, andmi + len(arr)
denotes the sum if we were to round all prices up. If thetarget
is not within this range, the code determines it's not feasible to reach thetarget
and immediately returns"−1"
. -
Sorting and Error Calculation If the target is achievable, then the
arr
list, which contains all the differences between each price and its floor value, is sorted in descending order usingarr.sort(reverse=True)
. This allows us to select the smallest fractional parts first when we round up, which minimizes the error. -
Computing the Total Rounding Error The total rounding error is computed by taking into account the numbers that we have to round up to reach the target. The difference between the target and initial sum
mi
gives usd
, the count of prices we must round up. The sum of rounding errors for prices rounded up issum(arr[:d])
, and for those rounded down issum(arr[d:])
. We subtract the first fromd
and add the second to find the total error. -
Formatting the Result Finally, the code computes the answer and formats it to 3 decimal places using the formatted string
f'{ans:.3f}'
, which completes our rounding error calculation.
This solution is efficient as it makes use of the properties of numbers and their rounding behaviors, along with sorting, which runs in O(n log n)
time complexity for Python's Timsort algorithm, where n
is the number of prices given.
The primary data structure utilized here is the list, for collecting the fractional parts and later sorting them. The use of list slicing and aggregation functions like sum()
makes the code concise and efficient.
The given solution elegantly combines simple mathematical observation with a well-known sorting algorithm to solve what initially appears to be a complex problem. This implementation underlines the effectiveness of combining fundamental programming constructs and algorithms to solve practical computational problems.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's step through the solution approach using the example where prices = [1.50, 2.50, 3.40]
and the target sum is 7
.
-
Initial Summation and Difference Collection
Considering the
prices
array:- For
1.50
, its floor value is1
and the fractional part is1.50 - 1 = 0.50
. - For
2.50
, its floor value is2
and the fractional part is2.50 - 2 = 0.50
. - For
3.40
, its floor value is3
and the fractional part is3.40 - 3 = 0.40
.
The sum of floor values,
mi
, is1 + 2 + 3 = 6
, and the fractional partsarr
are[0.50, 0.50, 0.40]
. - For
-
Checking the Feasibility
The minimum sum
mi
is6
and if we were to round all prices up, the maximum sum would be6 + 3 = 9
. The target7
is within this range(6 <= 7 <= 9)
, so proceeding is possible. -
Sorting and Error Calculation
We sort
arr
in descending order, resulting in[0.50, 0.50, 0.40]
. It remains intact as all elements are in the correct order. -
Computing the Total Rounding Error
We need to increase our sum from
6
to7
, henced = 7 - 6 = 1
. This means we need to round up one price. To minimize error, we round up the price with the smallest fractional part, which is0.40
.The rounding error for rounding up:
- We have one value to round up, which is
0.40
, so error from it is1 - 0.40 = 0.60
.
The rounding error for numbers we don't round up:
- The two values we keep as floor values are the two
0.50
s, and their errors remain0.50 + 0.50 = 1.00
.
Therefore, the total error is
0.60 + 1.00 = 1.60
. - We have one value to round up, which is
-
Formatting the Result
We format the total error to three decimal places, resulting in the answer:
"1.600"
.
This example illustrates that by sorting the fractional parts and rounding up only the required number of smallest fractional parts, we optimize the rounding error while achieving our target sum. Using this strategy allows us to determine the best numbers to round up to meet our target sum with the minimal rounding difference.
Solution Implementation
1from typing import List
2
3class Solution:
4 def minimizeError(self, prices: List[str], target: int) -> str:
5 # Initialize the floor sum which represents the sum of all floor values of the prices
6 floor_sum = 0
7 # Initialize an array to hold the fractional parts of the prices
8 fractional_parts = []
9
10 # Iterate over the string representation of prices, convert them to floats,
11 # and calculate the floor sum and store the fractional parts
12 for price_str in prices:
13 price = float(price_str)
14 floor_sum += int(price) # Adding the integer part (floor) of the price
15 # If the price is not an integer, store the fractional part
16 if fraction := price - int(price):
17 fractional_parts.append(fraction)
18
19 # Check if it is impossible to reach the target by summing the integer parts of prices
20 # plus the possible additions of 1 for each fractional part to form integers
21 if not floor_sum <= target <= floor_sum + len(fractional_parts):
22 return "-1" # If it is impossible, return "-1"
23
24 # Calculate the additional sum required to reach the target from the floor sum
25 remaining_sum = target - floor_sum
26
27 # Sort the fractional parts in reverse order to prepare for minimizing the rounding error
28 fractional_parts.sort(reverse=True)
29
30 # Calculate the minimum error by adding the smallest fractional parts up to the remaining sum
31 # and then subtracting this from the total sum of fractional parts
32 # then, adding the count of flooring operations (since we've taken their fractions)
33 min_error = sum(fractional_parts[remaining_sum:]) \
34 - sum(fractional_parts[:remaining_sum]) \
35 + remaining_sum
36
37 # Format the result to 3 decimal places and return as a string
38 return f'{min_error:.3f}'
39
1class Solution {
2 public String minimizeError(String[] prices, int target) {
3 // Initialize the floor sum of prices.
4 int floorSum = 0;
5 // Initialize an array list to store the fractional parts.
6 List<Double> fractions = new ArrayList<>();
7 // Loop over each price.
8 for (String priceStr : prices) {
9 // Convert the string to a double value.
10 double price = Double.parseDouble(priceStr);
11 // Add the floor value of the price to the floor sum.
12 floorSum += (int) price;
13 // Calculate the fractional part of the price.
14 double fraction = price - (int) price;
15 // If there is a fractional part, add it to the fractions list.
16 if (fraction > 0) {
17 fractions.add(fraction);
18 }
19 }
20 // Check if the target is unreachable.
21 if (target < floorSum || target > floorSum + fractions.size()) {
22 return "-1";
23 }
24 // Calculate how many fractions we need to round up to reach the target.
25 int roundUpCount = target - floorSum;
26 // Sort the fractions in non-ascending order.
27 fractions.sort(Collections.reverseOrder());
28
29 // Start calculating the error by adding the number of fractions required to round up.
30 double error = roundUpCount;
31 // Subtract the largest fractions that we're rounding up.
32 for (int i = 0; i < roundUpCount; ++i) {
33 error -= fractions.get(i);
34 }
35 // Add the remaining fractions that we're rounding down.
36 for (int i = roundUpCount; i < fractions.size(); ++i) {
37 error += fractions.get(i);
38 }
39 // Format the error to 3 decimal places.
40 DecimalFormat decimalFormat = new DecimalFormat("#0.000");
41 return decimalFormat.format(error);
42 }
43}
44
1#include <vector>
2#include <string>
3#include <algorithm>
4
5class Solution {
6public:
7 // Function to minimize the rounding error to meet a specific target sum
8 string minimizeError(vector<string>& prices, int target) {
9 int floorSum = 0; // to store the sum of floor values of the prices
10 vector<double> fractions; // to store the fractional parts of the prices
11
12 // Loop through the price strings, convert them to double and calculate floorSum and fractions
13 for (const auto& priceStr : prices) {
14 double price = stod(priceStr); // convert string to double
15 floorSum += static_cast<int>(price); // accumulate the floor part
16 double fraction = price - floor(price); // calculate the fraction part
17
18 // If there's a fractional part, add it to the fractions list
19 if (fraction > 0) {
20 fractions.push_back(fraction);
21 }
22 }
23
24 // If the target is less than the sum of floor values or
25 // more than what can be achieved by taking the ceiling of all fractions
26 // return "-1" indicating it's not possible to meet the target
27 if (target < floorSum || target > floorSum + fractions.size()) {
28 return "-1";
29 }
30
31 // Calculating the number of elements to take the ceiling
32 int ceilCount = target - floorSum;
33
34 // Sort the fractions in order to maximize the lower error when rounding
35 sort(fractions.rbegin(), fractions.rend());
36
37 // Initialize the error sum with the count of ceilings needed,
38 // then adjust by subtracting fractions for ceilings and adding fractions for floors
39 double errorSum = ceilCount;
40 for (int i = 0; i < ceilCount; ++i) {
41 errorSum -= fractions[i]; // subtract elements which are to be ceiled
42 }
43 for (int i = ceilCount; i < fractions.size(); ++i) {
44 errorSum += fractions[i]; // add elements which are to be floored
45 }
46
47 // Convert the double error sum to string with precision handling
48 string errorStr = to_string(errorSum);
49
50 // Truncate the string to have only 3 decimal places
51 size_t decimalPos = errorStr.find('.');
52 if (decimalPos != string::npos) {
53 errorStr = errorStr.substr(0, decimalPos + 4);
54 }
55
56 // Return the error sum as a string
57 return errorStr;
58 }
59};
60
1function minimizeError(prices: string[], target: number): string {
2 let floorSum: number = 0; // To store the sum of floor values of the prices
3 let fractions: number[] = []; // To store the fractional parts of the prices
4
5 // Loop through the price strings, convert them to numbers, and calculate floorSum and fractions
6 prices.forEach(priceStr => {
7 let price: number = parseFloat(priceStr); // Convert string to number
8 floorSum += Math.floor(price); // Accumulate the floor part
9 let fraction: number = price - Math.floor(price); // Calculate the fraction part
10
11 // If there's a fractional part, add it to the fractions array
12 if (fraction > 0) {
13 fractions.push(fraction);
14 }
15 });
16
17 // If the target is less than the sum of floor values or more than the max possible by ceilings
18 if (target < floorSum || target > floorSum + fractions.length) {
19 return "-1"; // Return "-1" indicating it's not possible to meet the target
20 }
21
22 // Calculate the number of elements where we take the ceiling
23 let ceilCount: number = target - floorSum;
24
25 // Sort the fractions to maximize the lower error when rounding
26 fractions.sort((a, b) => b - a);
27
28 // Initialize the error sum with the count of ceilings needed,
29 // then adjust by subtracting fractions for ceilings and adding fractions for floors
30 let errorSum: number = ceilCount;
31 for (let i = 0; i < ceilCount; i++) {
32 errorSum -= fractions[i]; // Subtract elements that will be ceiled
33 }
34 for (let i = ceilCount; i < fractions.length; i++) {
35 errorSum += fractions[i]; // Add elements that will be floored
36 }
37
38 // Convert the error sum to a string with controlled precision
39 let errorStr: string = errorSum.toFixed(3);
40
41 // Return the error sum as a string
42 return errorStr;
43}
44
Time and Space Complexity
Time Complexity
The function minimizeError
has several components, each contributing to the total time complexity:
-
Conversion of strings to floats: The loop converts each string in the
prices
list to a float. This process takesO(n)
wheren
is the number of strings since each string is processed once. -
Rounding down floats and collecting fractional parts: Within the same loop, the code rounds down the float to the nearest integer using
int(p)
and collects the fractional part if any. This is alsoO(n)
time. -
Sorting fractional parts: The fractional parts are sorted in reverse order which has a time complexity of
O(n log n)
. -
Summing specific portions of the array: The algorithm sums parts of the array, which is linear with respect to the number of elements summed. In the worst case, this is
O(n)
.
Therefore, the overall time complexity of minimizeError
is dominated by the sorting step, which makes the time complexity O(n log n)
.
Space Complexity
Regarding the space complexity:
-
Storing fractional parts: The array
arr
stores at mostn
fractional parts. Hence, its space complexity isO(n)
. -
Return value formatting: String formatting for the return value does not use additional space that scales with the input size.
Therefore, the total space complexity of the function is O(n)
.
Learn more about how to find time and space complexity quickly using problem constraints.
Consider the classic dynamic programming of longest increasing subsequence:
Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.
For example, the length of LIS for [50, 3, 10, 7, 40, 80]
is 4
and LIS is
[3, 7, 40, 80]
.
What is the recurrence relation?
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
LeetCode Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way we
Want a Structured Path to Master System Design Too? Don’t Miss This!