Facebook Pixel

2448. Minimum Cost to Make Array Equal

Problem Description

You have two arrays nums and cost, both containing n positive integers. Each element nums[i] can be increased or decreased by 1, and doing this operation on the i-th element costs cost[i].

Your goal is to make all elements in the nums array equal by performing these operations. You need to find the minimum total cost to achieve this.

For example, if nums = [1, 3, 5] and cost = [2, 1, 3]:

  • To change nums[0] from 1 to any other value, each increment/decrement costs 2
  • To change nums[1] from 3 to any other value, each increment/decrement costs 1
  • To change nums[2] from 5 to any other value, each increment/decrement costs 3

The task is to determine what value all elements should become and calculate the minimum total cost. The total cost is calculated as the sum of |nums[i] - target| * cost[i] for all elements, where target is the chosen equal value.

The solution uses a sorted approach combined with prefix sums. After sorting the (nums[i], cost[i]) pairs by nums[i], it considers each unique value in nums as a potential target. For each potential target value x, it calculates:

  • The cost to increase all smaller values to x: x * Σ(cost[j]) - Σ(nums[j] * cost[j]) for all j where nums[j] < x
  • The cost to decrease all larger values to x: Σ(nums[j] * cost[j]) - x * Σ(cost[j]) for all j where nums[j] > x

The prefix sum arrays f and g store cumulative weighted sums and cumulative costs respectively, allowing efficient calculation of these costs for each potential target.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that the optimal target value must be one of the existing values in the nums array. Why? Consider the cost function for any target value x. The total cost is Σ|nums[i] - x| * cost[i], which is a weighted sum of absolute differences. This function is piecewise linear - it changes its slope only at the points where x equals one of the values in nums. Between any two consecutive values in nums, the cost function is linear, so the minimum cannot occur in the middle of an interval; it must occur at one of the endpoints.

Once we realize we only need to check existing values in nums as potential targets, the next challenge is efficiently calculating the cost for each candidate. A naive approach would calculate Σ|nums[i] - target| * cost[i] for each target, taking O(n²) time.

To optimize this, we can sort the array and use prefix sums. After sorting by nums values, for any target value x, we can split the cost calculation into two parts:

  • Elements smaller than x: contribute (x - nums[i]) * cost[i]
  • Elements larger than x: contribute (nums[i] - x) * cost[i]

By rearranging these sums:

  • Left side cost: x * Σcost[i] - Σ(nums[i] * cost[i]) for all i where nums[i] ≤ x
  • Right side cost: Σ(nums[i] * cost[i]) - x * Σcost[i] for all i where nums[i] > x

We can precompute two prefix sum arrays:

  • f[i]: cumulative sum of nums[j] * cost[j] for j < i
  • g[i]: cumulative sum of cost[j] for j < i

With these prefix sums, calculating the cost for any target value becomes O(1), reducing the overall complexity to O(n log n) due to sorting.

Learn more about Greedy, Binary Search, Prefix Sum and Sorting patterns.

Solution Approach

The implementation follows these steps:

  1. Create and Sort Pairs: First, we zip the nums and cost arrays together and sort them by the nums values. This gives us arr = sorted(zip(nums, cost)), where each element is a tuple (nums[i], cost[i]) sorted by the first element.

  2. Build Prefix Sum Arrays: We create two prefix sum arrays with length n+1:

    • f[i]: stores the cumulative sum of nums[j] * cost[j] for all j < i
    • g[i]: stores the cumulative sum of cost[j] for all j < i

    The construction loop:

    for i in range(1, n + 1):
        a, b = arr[i - 1]  # a = nums value, b = cost value
        f[i] = f[i - 1] + a * b
        g[i] = g[i - 1] + b
  3. Enumerate Each Potential Target: For each position i in the sorted array, we consider arr[i-1][0] as the target value a. We calculate the total cost as the sum of:

    • Left side cost (l): Cost to move all elements less than a up to a
      • Formula: a * g[i-1] - f[i-1]
      • g[i-1] is the sum of costs for elements before position i
      • f[i-1] is the sum of (value * cost) for elements before position i
    • Right side cost (r): Cost to move all elements greater than a down to a
      • Formula: f[n] - f[i] - a * (g[n] - g[i])
      • f[n] - f[i] is the sum of (value * cost) for elements after position i
      • g[n] - g[i] is the sum of costs for elements after position i
  4. Track Minimum Cost: We keep track of the minimum total cost across all potential targets:

    ans = min(ans, l + r)

The mathematical derivation for the cost formulas:

  • For elements less than target a: Cost = Σ(a - nums[j]) * cost[j] = a * Σcost[j] - Σ(nums[j] * cost[j])
  • For elements greater than target a: Cost = Σ(nums[j] - a) * cost[j] = Σ(nums[j] * cost[j]) - a * Σcost[j]

Time complexity: O(n log n) for sorting, O(n) for building prefix sums, and O(n) for enumeration, resulting in O(n log n) overall. Space complexity: O(n) for the sorted array and prefix sum arrays.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a small example with nums = [1, 3, 5, 2] and cost = [2, 1, 3, 4].

Step 1: Create and Sort Pairs We combine nums and cost, then sort by nums values:

  • Original pairs: [(1,2), (3,1), (5,3), (2,4)]
  • Sorted array: arr = [(1,2), (2,4), (3,1), (5,3)]

Step 2: Build Prefix Sum Arrays We create arrays f and g where:

  • f[i] = cumulative sum of nums[j] * cost[j] for j < i
  • g[i] = cumulative sum of cost[j] for j < i
iarr[i-1]f[i]g[i]
0-00
1(1,2)0 + 1×2 = 20 + 2 = 2
2(2,4)2 + 2×4 = 102 + 4 = 6
3(3,1)10 + 3×1 = 136 + 1 = 7
4(5,3)13 + 5×3 = 287 + 3 = 10

Step 3: Calculate Cost for Each Potential Target

For target = 1 (i=1):

  • Left cost: 1×g[0] - f[0] = 1×0 - 0 = 0 (no elements to the left)
  • Right cost: (f[4]-f[1]) - 1×(g[4]-g[1]) = (28-2) - 1×(10-2) = 26 - 8 = 18
  • Total: 0 + 18 = 18

For target = 2 (i=2):

  • Left cost: 2×g[1] - f[1] = 2×2 - 2 = 2 (move 1→2 costs 1×2)
  • Right cost: (f[4]-f[2]) - 2×(g[4]-g[2]) = (28-10) - 2×(10-6) = 18 - 8 = 10
  • Total: 2 + 10 = 12

For target = 3 (i=3):

  • Left cost: 3×g[2] - f[2] = 3×6 - 10 = 8 (move 1→3 costs 2×2, move 2→3 costs 1×4)
  • Right cost: (f[4]-f[3]) - 3×(g[4]-g[3]) = (28-13) - 3×(10-7) = 15 - 9 = 6
  • Total: 8 + 6 = 14

For target = 5 (i=4):

  • Left cost: 5×g[3] - f[3] = 5×7 - 13 = 22 (move all elements up to 5)
  • Right cost: (f[4]-f[4]) - 5×(g[4]-g[4]) = 0 - 0 = 0 (no elements to the right)
  • Total: 22 + 0 = 22

Step 4: Find Minimum The minimum cost is 12, achieved when target = 2.

We can verify: Making all elements equal to 2:

  • Change 1→2: |1-2|×2 = 2
  • Change 3→2: |3-2|×1 = 1
  • Change 5→2: |5-2|×3 = 9
  • Change 2→2: |2-2|×4 = 0
  • Total: 2 + 1 + 9 + 0 = 12 ✓

Solution Implementation

1class Solution:
2    def minCost(self, nums: List[int], cost: List[int]) -> int:
3        # Sort pairs of (number, cost) by number value
4        sorted_pairs = sorted(zip(nums, cost))
5        n = len(sorted_pairs)
6      
7        # prefix_weighted_sum[i] = sum of (number * cost) for first i elements
8        prefix_weighted_sum = [0] * (n + 1)
9        # prefix_cost_sum[i] = sum of costs for first i elements
10        prefix_cost_sum = [0] * (n + 1)
11      
12        # Build prefix sums
13        for i in range(1, n + 1):
14            num_val, cost_val = sorted_pairs[i - 1]
15            prefix_weighted_sum[i] = prefix_weighted_sum[i - 1] + num_val * cost_val
16            prefix_cost_sum[i] = prefix_cost_sum[i - 1] + cost_val
17      
18        min_total_cost = float('inf')
19      
20        # Try each position as the target point
21        for i in range(1, n + 1):
22            target_num = sorted_pairs[i - 1][0]
23          
24            # Cost to move all elements on the left to target_num
25            # For elements left of target: cost = sum(cost[j] * (target - num[j]))
26            left_cost = target_num * prefix_cost_sum[i - 1] - prefix_weighted_sum[i - 1]
27          
28            # Cost to move all elements on the right to target_num
29            # For elements right of target: cost = sum(cost[j] * (num[j] - target))
30            right_cost = (prefix_weighted_sum[n] - prefix_weighted_sum[i]) - target_num * (prefix_cost_sum[n] - prefix_cost_sum[i])
31          
32            # Update minimum total cost
33            min_total_cost = min(min_total_cost, left_cost + right_cost)
34      
35        return min_total_cost
36
1class Solution {
2    public long minCost(int[] nums, int[] cost) {
3        int n = nums.length;
4      
5        // Create a 2D array to store pairs of [number, cost]
6        int[][] numCostPairs = new int[n][2];
7        for (int i = 0; i < n; i++) {
8            numCostPairs[i] = new int[] {nums[i], cost[i]};
9        }
10      
11        // Sort pairs by number value in ascending order
12        Arrays.sort(numCostPairs, (a, b) -> a[0] - b[0]);
13      
14        // prefixWeightedSum[i] stores sum of (number * cost) for first i elements
15        long[] prefixWeightedSum = new long[n + 1];
16        // prefixCostSum[i] stores sum of costs for first i elements
17        long[] prefixCostSum = new long[n + 1];
18      
19        // Build prefix sums
20        for (int i = 1; i <= n; i++) {
21            long currentNum = numCostPairs[i - 1][0];
22            long currentCost = numCostPairs[i - 1][1];
23            prefixWeightedSum[i] = prefixWeightedSum[i - 1] + currentNum * currentCost;
24            prefixCostSum[i] = prefixCostSum[i - 1] + currentCost;
25        }
26      
27        long minTotalCost = Long.MAX_VALUE;
28      
29        // Try each position as the target value to make all numbers equal to
30        for (int i = 1; i <= n; i++) {
31            long targetValue = numCostPairs[i - 1][0];
32          
33            // Calculate cost for elements on the left (smaller than target)
34            // Cost = target * sum(costs) - sum(number * cost)
35            long leftCost = targetValue * prefixCostSum[i - 1] - prefixWeightedSum[i - 1];
36          
37            // Calculate cost for elements on the right (larger than target)
38            // Cost = sum(number * cost) - target * sum(costs)
39            long rightCost = prefixWeightedSum[n] - prefixWeightedSum[i] 
40                           - targetValue * (prefixCostSum[n] - prefixCostSum[i]);
41          
42            // Update minimum cost
43            minTotalCost = Math.min(minTotalCost, leftCost + rightCost);
44        }
45      
46        return minTotalCost;
47    }
48}
49
1using ll = long long;
2
3class Solution {
4public:
5    long long minCost(vector<int>& nums, vector<int>& cost) {
6        int n = nums.size();
7      
8        // Create pairs of (value, cost) and sort by value
9        vector<pair<int, int>> valueCostPairs(n);
10        for (int i = 0; i < n; ++i) {
11            valueCostPairs[i] = {nums[i], cost[i]};
12        }
13        sort(valueCostPairs.begin(), valueCostPairs.end());
14      
15        // Build prefix sums for weighted values and costs
16        // prefixWeightedSum[i] = sum of (value * cost) for first i elements
17        // prefixCostSum[i] = sum of costs for first i elements
18        vector<ll> prefixWeightedSum(n + 1, 0);
19        vector<ll> prefixCostSum(n + 1, 0);
20      
21        for (int i = 1; i <= n; ++i) {
22            auto [value, costVal] = valueCostPairs[i - 1];
23            prefixWeightedSum[i] = prefixWeightedSum[i - 1] + 1LL * value * costVal;
24            prefixCostSum[i] = prefixCostSum[i - 1] + costVal;
25        }
26      
27        // Try each position as the target value
28        ll minTotalCost = LLONG_MAX;
29      
30        for (int i = 1; i <= n; ++i) {
31            auto [targetValue, _] = valueCostPairs[i - 1];
32          
33            // Cost to move all elements on the left to targetValue
34            // For each element j < i: cost[j] * (targetValue - value[j])
35            ll leftCost = 1LL * targetValue * prefixCostSum[i - 1] - prefixWeightedSum[i - 1];
36          
37            // Cost to move all elements on the right to targetValue
38            // For each element j > i: cost[j] * (value[j] - targetValue)
39            ll rightCost = (prefixWeightedSum[n] - prefixWeightedSum[i]) - 
40                          1LL * targetValue * (prefixCostSum[n] - prefixCostSum[i]);
41          
42            // Update minimum total cost
43            minTotalCost = min(minTotalCost, leftCost + rightCost);
44        }
45      
46        return minTotalCost;
47    }
48};
49
1type ll = number;
2
3function minCost(nums: number[], cost: number[]): number {
4    const n = nums.length;
5  
6    // Create pairs of [value, cost] and sort by value
7    const valueCostPairs: [number, number][] = [];
8    for (let i = 0; i < n; i++) {
9        valueCostPairs.push([nums[i], cost[i]]);
10    }
11    valueCostPairs.sort((a, b) => a[0] - b[0]);
12  
13    // Build prefix sums for weighted values and costs
14    // prefixWeightedSum[i] = sum of (value * cost) for first i elements
15    // prefixCostSum[i] = sum of costs for first i elements
16    const prefixWeightedSum: ll[] = new Array(n + 1).fill(0);
17    const prefixCostSum: ll[] = new Array(n + 1).fill(0);
18  
19    for (let i = 1; i <= n; i++) {
20        const [value, costVal] = valueCostPairs[i - 1];
21        prefixWeightedSum[i] = prefixWeightedSum[i - 1] + value * costVal;
22        prefixCostSum[i] = prefixCostSum[i - 1] + costVal;
23    }
24  
25    // Try each position as the target value
26    let minTotalCost: ll = Number.MAX_SAFE_INTEGER;
27  
28    for (let i = 1; i <= n; i++) {
29        const [targetValue] = valueCostPairs[i - 1];
30      
31        // Cost to move all elements on the left to targetValue
32        // For each element j < i: cost[j] * (targetValue - value[j])
33        const leftCost: ll = targetValue * prefixCostSum[i - 1] - prefixWeightedSum[i - 1];
34      
35        // Cost to move all elements on the right to targetValue
36        // For each element j > i: cost[j] * (value[j] - targetValue)
37        const rightCost: ll = (prefixWeightedSum[n] - prefixWeightedSum[i]) - 
38                             targetValue * (prefixCostSum[n] - prefixCostSum[i]);
39      
40        // Update minimum total cost
41        minTotalCost = Math.min(minTotalCost, leftCost + rightCost);
42    }
43  
44    return minTotalCost;
45}
46

Time and Space Complexity

Time Complexity: O(n × log n), where n is the length of the array nums.

  • The dominant operation is sorting the array of tuples (nums[i], cost[i]), which takes O(n × log n) time.
  • Creating the prefix sum arrays f and g requires iterating through the sorted array once, taking O(n) time.
  • Finding the minimum cost by iterating through all possible median positions takes O(n) time.
  • Overall: O(n × log n) + O(n) + O(n) = O(n × log n)

Space Complexity: O(n)

  • The sorted array arr stores n tuples, requiring O(n) space.
  • The prefix sum arrays f and g each require O(n + 1) = O(n) space.
  • Additional variables use O(1) space.
  • Overall: O(n) + O(n) + O(n) = O(n)

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Not Considering Duplicate Values in the Sorted Array

A common mistake is assuming that each unique value needs to be checked as a potential target. However, when there are duplicate values in nums, the current implementation correctly handles this by checking each position, even if the values are the same. This is actually optimal because:

  • When multiple elements have the same value but different costs, the prefix sums naturally accumulate correctly
  • The algorithm doesn't need special handling for duplicates

Why this works: For duplicate values at positions i and i+1, when we calculate the cost for position i+1, the element at position i (with the same value) contributes 0 to the total cost since |nums[i] - target| = 0.

2. Integer Overflow in Prefix Sum Calculations

The most critical pitfall is potential integer overflow when calculating prefix_weighted_sum. Since we're multiplying nums[i] * cost[i] and accumulating these products, the values can grow very large.

Problem scenario:

  • If nums[i] and cost[i] can be up to 10^5 each, and we have n = 10^5 elements
  • The maximum value of a single product: 10^5 * 10^5 = 10^10
  • The maximum accumulated sum: 10^5 * 10^10 = 10^15

Solution: In Python, integers have arbitrary precision, so overflow isn't an issue. However, in languages like Java or C++, you should:

# Use long long in C++ or long in Java
# In Python, this is handled automatically
prefix_weighted_sum = [0] * (n + 1)  # Python handles large integers

3. Off-by-One Errors in Prefix Sum Indexing

A subtle but common mistake is incorrectly indexing the prefix arrays when calculating left and right costs.

Incorrect approach:

# Wrong: Using wrong indices
left_cost = target_num * prefix_cost_sum[i] - prefix_weighted_sum[i]  # Should be i-1
right_cost = (prefix_weighted_sum[n] - prefix_weighted_sum[i-1]) - target_num * (prefix_cost_sum[n] - prefix_cost_sum[i-1])  # Should be i

Correct approach:

# Correct: For position i (1-indexed), elements before it are at indices [0, i-1]
left_cost = target_num * prefix_cost_sum[i - 1] - prefix_weighted_sum[i - 1]
right_cost = (prefix_weighted_sum[n] - prefix_weighted_sum[i]) - target_num * (prefix_cost_sum[n] - prefix_cost_sum[i])

4. Not Considering the Weighted Median Optimization

While the current O(n log n) solution works, a more sophisticated approach using weighted median can solve this in O(n) time after sorting. The pitfall is not recognizing that this is essentially a weighted median problem.

Insight: The optimal target is the weighted median of the values, where weights are the costs. This can be found by:

  1. Finding the point where cumulative cost reaches half of total cost
  2. That position's value is the optimal target

Optimized approach:

def minCost(self, nums: List[int], cost: List[int]) -> int:
    sorted_pairs = sorted(zip(nums, cost))
    total_cost = sum(cost)
    accumulated = 0
  
    # Find weighted median
    for num_val, cost_val in sorted_pairs:
        accumulated += cost_val
        if accumulated >= total_cost / 2:
            target = num_val
            break
  
    # Calculate minimum cost using the weighted median
    return sum(abs(num - target) * c for num, c in zip(nums, cost))

This optimization reduces the enumeration step from O(n) checks to O(1) by directly finding the optimal target.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which technique can we use to find the middle of a linked list?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More