You are given an array X of floating-point numbers x1, x2, ... xn. These can be rounded up or down to create a corresponding array Y of integers y1, y2, ... yn.
Write an algorithm that finds an appropriate Y array with the following properties:
The rounded sums of both arrays should be equal.
The absolute pairwise difference between elements is minimized. In other words, |x1- y1| + |x2- y2| + ... + |xn- yn| should be as small as possible.
For example, suppose your input is [1.3, 2.3, 4.4]. In this case you cannot do better than [1, 2, 5], which has an absolute difference of |1.3 - 1| + |2.3 - 2| + |4.4 - 5| = 1.
Input: X =[1.3,2.3,4.4]Output: Y =[1,2,5]Explanation:
Target sum = round(1.3+2.3+4.4)= round(8.0)=8Sum of floors = floor(1.3)+ floor(2.3)+ floor(4.4)=1+2+4=7Need to round up 1 element to reach target sum 8Choose 4.4->5(difference =0.6) over others for minimal total difference
Total difference =|1.3-1|+|2.3-2|+|4.4-5|=0.3+0.3+0.6=1.2
Input: X =[2.5,3.5,1.0]Output: Y =[2,4,1] or Y =[3,3,1]Explanation:
Target sum = round(2.5+3.5+1.0)= round(7.0)=7Sum of floors = floor(2.5)+ floor(3.5)+ floor(1.0)=2+3+1=6Need to round up 1 element to reach target sum 7Both 2.5->3 and 3.5->4 have same fractional part(0.5), so either choice gives optimal result
Total difference =0.5+0.5+0.0=1.0
Input: X =[1.1,1.9,2.8,3.2]Output: Y =[1,2,3,3]Explanation:
Target sum = round(1.1+1.9+2.8+3.2)= round(9.0)=9Sum of floors = floor(1.1)+ floor(1.9)+ floor(2.8)+ floor(3.2)=1+1+2+3=7Need to round up 2 elements to reach target sum 9Choose elements with largest fractional parts:1.9(0.9) and 2.8(0.8)Result: [1,2,3,3]Total difference =|1.1-1|+|1.9-2|+|2.8-3|+|3.2-3|=0.1+0.1+0.2+0.2=0.6
Input: X =[3.7,2.1,1.9,4.3]Output: Y =[4,2,2,4]Explanation:
Target sum = round(3.7+2.1+1.9+4.3)= round(12.0)=12Sum of floors = floor(3.7)+ floor(2.1)+ floor(1.9)+ floor(4.3)=3+2+1+4=10Need to round up 2 elements to reach target sum 12Choose elements with largest fractional parts:3.7(0.7) and 4.3(0.3)Result: [4,2,2,4]Total difference =|3.7-4|+|2.1-2|+|1.9-2|+|4.3-4|=0.3+0.1+0.1+0.3=0.8
Input: X =[1.0,2.0,3.0]Output: Y =[1,2,3]Explanation:
Target sum = round(1.0+2.0+3.0)= round(6.0)=6Sum of floors = floor(1.0)+ floor(2.0)+ floor(3.0)=1+2+3=6No rounding up needed since target sum already achieved
Total difference =|1.0-1|+|2.0-2|+|3.0-3|=0.0+0.0+0.0=0.0
To minimize the total absolute difference while preserving the rounded sum, we should floor all numbers initially (which minimizes individual differences), then selectively round up elements with the largest fractional parts. The key insight is that rounding up an element with fractional part f costs exactly 1-f in additional difference, so we want to minimize this cost.
classSolution {
public: vector<int> optimalRounding(vector<double>& X) {
int n = X.size();
vector<int> Y(n);
// Calculate target sum (rounded sum of original array)
double totalSum =0;
for (double x : X) {
totalSum += x;
}
int targetSum = (int)round(totalSum);
// Initialize with floor values and calculate current sum
int currentSum =0;
for (int i =0; i < n; i++) {
Y[i] = (int)floor(X[i]);
currentSum += Y[i];
}
// Calculate how many elements need to be rounded up
int roundUpCount = targetSum - currentSum;
// Create pairs of (fractional_part, index) and sort by fractional part
vector<pair<double, int>> fractionalParts;
for (int i =0; i < n; i++) {
double fractionalPart = X[i] - floor(X[i]);
fractionalParts.push_back({fractionalPart, i});
}
// Sort by fractional part in descending order
sort(fractionalParts.begin(), fractionalParts.end(), greater<pair<double, int>>());
// Round up the elements with largest fractional parts
for (int i =0; i < roundUpCount; i++) {
int idx = fractionalParts[i].second;
Y[idx]++;
}
return Y;
}
};
funcoptimalRounding(X []float64) []int {
n:= len(X)
Y:= make([]int, n)
// Calculate target sum (rounded sum of original array)totalSum:=0.0for_, x:=rangeX {
totalSum+=x }
targetSum:= int(math.Round(totalSum))
// Initialize with floor values and calculate current sumcurrentSum:=0fori, x:=rangeX {
Y[i] = int(math.Floor(x))
currentSum+=Y[i]
}
// Calculate how many elements need to be rounded uproundUpCount:=targetSum-currentSum// Create pairs of (fractional_part, index)typeFractionalPairstruct {
fractionalPartfloat64indexint }
fractionalParts:= make([]FractionalPair, n)
fori, x:=rangeX {
fractionalPart:=x-math.Floor(x)
fractionalParts[i] = FractionalPair{fractionalPart, i}
}
// Sort by fractional part in descending ordersort.Slice(fractionalParts, func(i, jint) bool {
returnfractionalParts[i].fractionalPart > fractionalParts[j].fractionalPart })
// Round up the elements with largest fractional partsfori:=0; i < roundUpCount; i++ {
idx:=fractionalParts[i].indexY[idx]++ }
returnY}
classSolution {
publicint[]optimalRounding(double[] X) {
int n = X.length;
int[] Y =newint[n];
// Calculate target sum (rounded sum of original array)double totalSum = 0;
for (double x : X) {
totalSum += x;
}
int targetSum = (int) Math.round(totalSum);
// Initialize with floor values and calculate current sumint currentSum = 0;
for (int i = 0; i < n; i++) {
Y[i]= (int) Math.floor(X[i]);
currentSum += Y[i];
}
// Calculate how many elements need to be rounded upint roundUpCount = targetSum - currentSum;
// Create pairs of (fractional_part, index)classFractionalPair {
double fractionalPart;
int index;
FractionalPair(double fractionalPart, int index) {
this.fractionalPart= fractionalPart;
this.index= index;
}
}
List<FractionalPair> fractionalParts =new ArrayList<>();
for (int i = 0; i < n; i++) {
double fractionalPart = X[i]- Math.floor(X[i]);
fractionalParts.add(new FractionalPair(fractionalPart, i));
}
// Sort by fractional part in descending order fractionalParts.sort((a, b) -> Double.compare(b.fractionalPart, a.fractionalPart));
// Round up the elements with largest fractional partsfor (int i = 0; i < roundUpCount; i++) {
int idx = fractionalParts.get(i).index;
Y[idx]++;
}
return Y;
}
}
classSolution:
defoptimal_rounding(self, X: List[float]) -> List[int]:
n = len(X)
# Calculate target sum (rounded sum of original array) total_sum = sum(X)
target_sum = round(total_sum)
# Initialize with floor values and calculate current sum Y = [int(x) for x in X] # floor operation current_sum = sum(Y)
# Calculate how many elements need to be rounded up round_up_count = target_sum - current_sum
# Create pairs of (fractional_part, index) and sort by fractional part fractional_parts = []
for i, x in enumerate(X):
fractional_part = x - int(x)
fractional_parts.append((fractional_part, i))
# Sort by fractional part in descending order fractional_parts.sort(reverse=True)
# Round up the elements with largest fractional partsfor i in range(round_up_count):
idx = fractional_parts[i][1]
Y[idx] +=1return Y
We can view this problem as an optimization where we want to minimize the total cost of rounding operations. Each element can be either floored (cost = fractional_part) or ceiled (cost = 1 - fractional_part). We use a priority queue to efficiently track which elements benefit most from being rounded up.
classSolution:
defoptimal_rounding_pq(self, X: List[float]) -> List[int]:
import heapq
n = len(X)
# Calculate target sum total_sum = sum(X)
target_sum = round(total_sum)
# Initialize with floor values Y = [int(x) for x in X]
current_sum = sum(Y)
# Build max heap of benefits (use negative values for max heap) benefits = []
for i, x in enumerate(X):
fractional_part = x - int(x)
if fractional_part >0:
# Negative value for max heap behavior heapq.heappush(benefits, (-fractional_part, i))
# Round up elements with highest benefit round_up_count = target_sum - current_sum
for _ in range(round_up_count):
if benefits:
neg_benefit, idx = heapq.heappop(benefits)
Y[idx] +=1return Y
We can model this as a dynamic programming problem where for each element, we decide whether to floor or ceil it, subject to the constraint that the total sum must equal the target. This approach explores all possible combinations but can be optimized using memoization.
Method 1 (Greedy) is the most efficient and intuitive approach, providing optimal results in O(n log n) time
Method 2 (Priority Queue) offers similar performance but with more explicit benefit tracking for educational purposes
Method 3 (Dynamic Programming) provides a complete exploration of all possibilities but with higher time complexity
The greedy approach works optimally because the problem has the greedy choice property: locally optimal choices (rounding up elements with largest fractional parts) lead to globally optimal solution
All methods handle edge cases like arrays with integer values, negative numbers, and various sum constraints
The algorithm maintains the mathematical property that minimizing total absolute difference while preserving sum is equivalent to minimizing the cost of rounding operations
Real-world applications include financial systems, statistical analysis, and data processing where precision and sum preservation are critical
The problem demonstrates the effectiveness of greedy algorithms in optimization scenarios with specific mathematical properties