Facebook Pixel

3424. Minimum Cost to Make Arrays Identical

Problem Description

You have two integer arrays arr and brr, both of length n, and an integer k. Your goal is to transform arr to make it exactly equal to brr using the following operations:

Operation 1: Split and Rearrange

  • You can split arr into any number of contiguous subarrays
  • These subarrays can then be rearranged in any order
  • This operation costs exactly k (regardless of how many subarrays you create)
  • You can use this operation at most once

Operation 2: Modify Elements

  • You can choose any element in arr and add or subtract a positive integer x to it
  • The cost of this operation is x
  • You can perform this operation any number of times on any elements

The problem asks you to find the minimum total cost to make arr identical to brr.

For example, if arr = [1, 2, 3] and brr = [3, 2, 1], you have two main strategies:

  1. Don't split: Just modify each element directly. The cost would be |1-3| + |2-2| + |3-1| = 2 + 0 + 2 = 4
  2. Split and rearrange: Split arr into individual elements [1], [2], [3], rearrange them to [3], [2], [1], then modify. After optimal rearrangement (sorting both arrays), the cost would be k plus the sum of differences after sorting

The solution recognizes that if you choose to split, the optimal strategy is to split into individual elements (subarrays of length 1) and match them optimally with brr by sorting both arrays. This minimizes the subsequent modification costs.

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

Intuition

The key insight is recognizing that we essentially have only two meaningful strategies:

Strategy 1: No splitting - We keep the elements in their original positions and directly modify each arr[i] to match brr[i]. The cost is simply the sum of absolute differences: sum(|arr[i] - brr[i]|).

Strategy 2: Split and rearrange optimally - If we decide to use the split operation (which costs k), we should maximize its benefit. The most flexible approach is to split arr into individual elements (subarrays of length 1), giving us complete freedom to rearrange them.

But how should we rearrange these individual elements to minimize modification costs? This is where the greedy insight comes in: we should match the smallest element in arr with the smallest element in brr, the second smallest with the second smallest, and so on. This minimizes the total absolute differences.

Why does this pairing work? Consider two elements a1 < a2 from arr and b1 < b2 from brr. If we pair (a1, b1) and (a2, b2), the total cost is |a1 - b1| + |a2 - b2|. If we swap the pairing to (a1, b2) and (a2, b1), the cost becomes |a1 - b2| + |a2 - b1|. Through mathematical analysis, the first pairing (matching sorted order) always gives a cost less than or equal to the second.

Therefore, when we split, we sort both arrays and calculate k + sum(|sorted_arr[i] - sorted_brr[i]|).

The final answer is simply the minimum of these two strategies: min(no_split_cost, split_cost). We choose whichever approach gives us the lower total cost.

Learn more about Greedy and Sorting patterns.

Solution Approach

The implementation follows directly from our intuition about the two strategies:

Step 1: Calculate cost without splitting (c1)

c1 = sum(abs(a - b) for a, b in zip(arr, brr))

We iterate through both arrays simultaneously using zip(arr, brr) and calculate the sum of absolute differences between corresponding elements. This represents the cost if we keep elements in their original positions.

Step 2: Calculate cost with splitting and optimal rearrangement (c2)

arr.sort()
brr.sort()
c2 = k + sum(abs(a - b) for a, b in zip(arr, brr))

First, we sort both arrays in ascending order. This ensures optimal pairing where the i-th smallest element of arr is matched with the i-th smallest element of brr. Then we calculate the sum of absolute differences between these optimally paired elements and add the fixed splitting cost k.

Step 3: Return the minimum cost

return min(c1, c2)

We compare both strategies and return whichever gives us the lower total cost.

Time Complexity: O(n log n) due to sorting both arrays, where n is the length of the arrays.

Space Complexity: O(1) if we're allowed to modify the input arrays (as shown in the solution), or O(n) if we need to preserve the original arrays and create sorted copies.

The beauty of this solution lies in its simplicity - we only need to consider these two cases because any other splitting strategy would be suboptimal. If we split into multiple contiguous subarrays (not individual elements), we'd have less flexibility in rearrangement, leading to potentially higher modification costs without any benefit.

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 concrete example with arr = [3, 7, 2], brr = [1, 4, 8], and k = 5.

Strategy 1: No splitting We keep elements in their original positions and modify each element:

  • Position 0: Change 3 to 1, cost = |3 - 1| = 2
  • Position 1: Change 7 to 4, cost = |7 - 4| = 3
  • Position 2: Change 2 to 8, cost = |2 - 8| = 6
  • Total cost without splitting: 2 + 3 + 6 = 11

Strategy 2: Split and rearrange optimally First, we split arr into individual elements: [3], [7], [2] This costs us k = 5 regardless of how we split.

Next, we need to figure out the optimal rearrangement. We sort both arrays:

  • Sorted arr: [2, 3, 7]
  • Sorted brr: [1, 4, 8]

Now we match elements by their sorted positions:

  • Match smallest with smallest: 2 โ†’ 1, cost = |2 - 1| = 1
  • Match middle with middle: 3 โ†’ 4, cost = |3 - 4| = 1
  • Match largest with largest: 7 โ†’ 8, cost = |7 - 8| = 1
  • Modification cost after rearrangement: 1 + 1 + 1 = 3
  • Total cost with splitting: k + modification cost = 5 + 3 = 8

Final Answer We compare both strategies:

  • Without splitting: 11
  • With splitting: 8
  • Minimum cost = min(11, 8) = 8

In this example, it's worth paying the splitting cost of 5 because it saves us 8 in modification costs (11 - 3 = 8 saved), giving us a net benefit of 3.

Solution Implementation

1class Solution:
2    def minCost(self, arr: List[int], brr: List[int], k: int) -> int:
3        # Calculate cost without sorting (direct element-wise difference)
4        cost_without_sort = sum(abs(a - b) for a, b in zip(arr, brr))
5      
6        # Sort both arrays to potentially minimize differences
7        arr.sort()
8        brr.sort()
9      
10        # Calculate cost after sorting (with penalty k for sorting operation)
11        cost_with_sort = k + sum(abs(a - b) for a, b in zip(arr, brr))
12      
13        # Return the minimum cost between the two strategies
14        return min(cost_without_sort, cost_with_sort)
15
1class Solution {
2    /**
3     * Calculates the minimum cost to make two arrays equal.
4     * We have two options:
5     * 1. Direct transformation: sum of absolute differences between corresponding elements
6     * 2. Sort both arrays first, then transform (with additional cost k)
7     * 
8     * @param arr First integer array
9     * @param brr Second integer array (same length as arr)
10     * @param k Additional cost for sorting operation
11     * @return Minimum cost between direct transformation and sorted transformation
12     */
13    public long minCost(int[] arr, int[] brr, long k) {
14        // Calculate cost without sorting (direct transformation)
15        long costWithoutSorting = calculateTotalDifference(arr, brr);
16      
17        // Sort both arrays for the second option
18        Arrays.sort(arr);
19        Arrays.sort(brr);
20      
21        // Calculate cost after sorting plus the sorting penalty k
22        long costWithSorting = calculateTotalDifference(arr, brr) + k;
23      
24        // Return the minimum of the two costs
25        return Math.min(costWithoutSorting, costWithSorting);
26    }
27
28    /**
29     * Calculates the total absolute difference between corresponding elements
30     * of two arrays.
31     * 
32     * @param arr First integer array
33     * @param brr Second integer array
34     * @return Sum of absolute differences between arr[i] and brr[i] for all i
35     */
36    private long calculateTotalDifference(int[] arr, int[] brr) {
37        long totalDifference = 0;
38      
39        // Iterate through both arrays and sum up absolute differences
40        for (int i = 0; i < arr.length; i++) {
41            totalDifference += Math.abs(arr[i] - brr[i]);
42        }
43      
44        return totalDifference;
45    }
46}
47
1class Solution {
2public:
3    long long minCost(vector<int>& arr, vector<int>& brr, long long k) {
4        // Lambda function to calculate the total absolute difference between two arrays
5        auto calculateTotalDifference = [&](vector<int>& firstArray, vector<int>& secondArray) {
6            long long totalDifference = 0;
7          
8            // Iterate through both arrays and sum up absolute differences at each position
9            for (int i = 0; i < firstArray.size(); ++i) {
10                totalDifference += abs(firstArray[i] - secondArray[i]);
11            }
12          
13            return totalDifference;
14        };
15      
16        // Calculate cost without sorting (original arrays)
17        long long costWithoutSorting = calculateTotalDifference(arr, brr);
18      
19        // Sort both arrays
20        ranges::sort(arr);
21        ranges::sort(brr);
22      
23        // Calculate cost after sorting and add the sorting penalty k
24        long long costWithSorting = calculateTotalDifference(arr, brr) + k;
25      
26        // Return the minimum cost between the two options
27        return min(costWithoutSorting, costWithSorting);
28    }
29};
30
1/**
2 * Calculates the minimum cost between two arrays with optional sorting
3 * @param arr - First input array
4 * @param brr - Second input array  
5 * @param k - Additional cost penalty when arrays are sorted
6 * @returns The minimum cost between original and sorted configurations
7 */
8function minCost(arr: number[], brr: number[], k: number): number {
9    /**
10     * Calculates the total absolute difference between corresponding elements of two arrays
11     * @param firstArray - First array for comparison
12     * @param secondArray - Second array for comparison
13     * @returns Sum of absolute differences between corresponding elements
14     */
15    const calculateTotalDifference = (firstArray: number[], secondArray: number[]): number => {
16        let totalDifference: number = 0;
17      
18        // Iterate through arrays and sum up absolute differences
19        for (let i: number = 0; i < firstArray.length; ++i) {
20            totalDifference += Math.abs(firstArray[i] - secondArray[i]);
21        }
22      
23        return totalDifference;
24    };
25  
26    // Calculate cost without sorting (original configuration)
27    const costWithoutSorting: number = calculateTotalDifference(arr, brr);
28  
29    // Sort both arrays in ascending order
30    arr.sort((a: number, b: number) => a - b);
31    brr.sort((a: number, b: number) => a - b);
32  
33    // Calculate cost after sorting plus the sorting penalty
34    const costWithSorting: number = calculateTotalDifference(arr, brr) + k;
35  
36    // Return the minimum cost between the two configurations
37    return Math.min(costWithoutSorting, costWithSorting);
38}
39

Time and Space Complexity

The time complexity of this solution is O(n ร— log n), where n is the length of the arrays arr and brr. This is because:

  • Computing c1 with the first sum operation takes O(n) time
  • Sorting arr takes O(n ร— log n) time
  • Sorting brr takes O(n ร— log n) time
  • Computing c2 with the second sum operation takes O(n) time
  • The min comparison takes O(1) time

The overall time complexity is dominated by the two sorting operations, resulting in O(n ร— log n).

The space complexity is O(log n). While Python's sort() method sorts the lists in-place without requiring additional space proportional to the input size, it uses a recursive sorting algorithm (Timsort) that requires O(log n) space for the recursion stack in the worst case.

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

Common Pitfalls

Pitfall 1: Modifying Input Arrays In-Place

The Problem: The solution sorts arr and brr directly, which modifies the original input arrays. This causes an issue because we need to calculate cost_without_sort using the original array ordering, but if we calculate it after sorting, we'll get incorrect results.

Incorrect Implementation:

def minCost(self, arr: List[int], brr: List[int], k: int) -> int:
    # WRONG: Sorting before calculating the original cost
    arr.sort()
    brr.sort()
    cost_without_sort = sum(abs(a - b) for a, b in zip(arr, brr))  # This uses sorted arrays!
    cost_with_sort = k + sum(abs(a - b) for a, b in zip(arr, brr))
    return min(cost_without_sort, cost_with_sort)

The Fix: Calculate the cost without sorting first, or create copies of the arrays:

def minCost(self, arr: List[int], brr: List[int], k: int) -> int:
    # Option 1: Calculate before sorting (as in the original solution)
    cost_without_sort = sum(abs(a - b) for a, b in zip(arr, brr))
    arr.sort()
    brr.sort()
    cost_with_sort = k + sum(abs(a - b) for a, b in zip(arr, brr))
  
    # Option 2: Use copies to preserve original arrays
    # cost_without_sort = sum(abs(a - b) for a, b in zip(arr, brr))
    # sorted_arr = sorted(arr)
    # sorted_brr = sorted(brr)
    # cost_with_sort = k + sum(abs(a - b) for a, b in zip(sorted_arr, sorted_brr))
  
    return min(cost_without_sort, cost_with_sort)

Pitfall 2: Forgetting the Edge Case Where k = 0

The Problem: When k = 0, the splitting operation is free. Some might think this means we should always split, but the logic remains the same - we still compare both strategies.

Why it's not really a pitfall: The code handles this correctly automatically. When k = 0, cost_with_sort becomes just the sum of differences after sorting, which might still be greater than cost_without_sort.

Pitfall 3: Misunderstanding the Splitting Operation

The Problem: Some might think that splitting has additional constraints or that we need to try different splitting strategies (like splitting into 2 subarrays, 3 subarrays, etc.).

The Key Insight: The optimal splitting strategy, if we choose to split at all, is always to split into individual elements (n subarrays of length 1). This gives maximum flexibility for rearrangement. Any other splitting pattern would be suboptimal because it restricts which elements can be paired together.

Example:

# arr = [5, 1, 3], brr = [1, 3, 5], k = 2

# Suboptimal: Split into [5, 1] and [3]
# Can only rearrange to [3][5, 1] or keep original
# Best achievable: [3, 5, 1] โ†’ costs 2 + 2 + 4 = 8 (plus k = 2) = 10

# Optimal: Split into [5], [1], [3]
# Can rearrange to [1, 3, 5] โ†’ costs 0 + 0 + 0 = 0 (plus k = 2) = 2
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What is the best way of checking if an element exists in a sorted array once in terms of time complexity? Select the best that applies.


Recommended Readings

Want a Structured Path to Master System Design Too? Donโ€™t Miss This!

Load More