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 integerx
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:
- Don't split: Just modify each element directly. The cost would be
|1-3| + |2-2| + |3-1| = 2 + 0 + 2 = 4
- 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 bek
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.
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.
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 EvaluatorExample 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 takesO(n)
time - Sorting
arr
takesO(n ร log n)
time - Sorting
brr
takesO(n ร log n)
time - Computing
c2
with the second sum operation takesO(n)
time - The
min
comparison takesO(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
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
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
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
Coding Interview 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
Want a Structured Path to Master System Design Too? Donโt Miss This!