2449. Minimum Number of Operations to Make Arrays Similar
Problem Description
You have two positive integer arrays nums
and target
of equal length. Your goal is to make nums
"similar" to target
through a series of operations.
In each operation, you can:
- Choose two different indices
i
andj
(where0 <= i, j < nums.length
) - Add
2
tonums[i]
- Subtract
2
fromnums[j]
Two arrays are considered similar when they contain the same elements with the same frequencies. For example, [1, 2, 3]
and [3, 1, 2]
are similar because they both have one occurrence each of 1, 2, and 3.
The task is to find the minimum number of operations needed to transform nums
into an array that is similar to target
.
The key insight is that each operation maintains the sum of the array (adding 2 to one element and subtracting 2 from another keeps the total unchanged). Also, operations preserve the parity of elements - odd numbers stay odd and even numbers stay even, since we're always adding or subtracting 2.
The solution leverages these properties by:
- Separating elements by parity (odd and even)
- Sorting both arrays with the same criteria
- Pairing corresponding elements after sorting
- Calculating the total adjustments needed
Since each operation changes two elements by a combined difference of 4 (one increases by 2, another decreases by 2), the total difference divided by 4 gives us the minimum number of operations required.
Intuition
Let's think about what happens during each operation. When we add 2
to one element and subtract 2
from another, we're essentially transferring "2 units" between positions. This is like redistributing values while keeping the total sum constant.
The critical observation is that adding or subtracting 2
preserves parity - an odd number remains odd (odd ± 2 = odd), and an even number remains even (even ± 2 = even). This means we can never transform an odd number into an even number or vice versa through our operations.
Since we need the arrays to be similar (same elements with same frequencies), and we can't change parity, each odd number in nums
must eventually become some odd number in target
, and each even number in nums
must become some even number in target
.
This naturally divides our problem into two independent subproblems:
- Transform all odd numbers in
nums
to match odd numbers intarget
- Transform all even numbers in
nums
to match even numbers intarget
Now, how should we pair elements? If we have sorted odd numbers [1, 5, 9]
in nums
and [3, 7, 11]
in target
, the optimal pairing is to match them in order: 1→3
, 5→7
, 9→11
. Why? Because any other pairing would create unnecessary "crossovers" that increase the total difference we need to fix.
For calculating the number of operations: when we transform element a
to element b
, we need |a - b| / 2
adjustments to that element. But remember, each operation affects two elements - one increases and one decreases. So when we sum up all the differences Σ|a - b|
, we're counting each operation twice (once for the element that increases, once for the element that decreases). Additionally, since each operation changes values by 2
, we divide by 2
again. This gives us Σ|a - b| / 4
as the total number of operations.
Solution Approach
The implementation follows a clever sorting strategy to efficiently pair elements based on their parity:
Step 1: Custom Sorting
Both nums
and target
arrays are sorted using the same key function: lambda x: (x & 1, x)
x & 1
is a bitwise operation that returns1
for odd numbers and0
for even numbers- This creates a tuple
(parity, value)
for each element - Sorting by this tuple groups even numbers first (parity = 0), then odd numbers (parity = 1)
- Within each group, numbers are sorted in ascending order
For example, if nums = [8, 3, 5, 2]
:
- After sorting:
[2, 8, 3, 5]
(evens first: 2, 8; then odds: 3, 5)
Step 2: Element Pairing
After sorting both arrays with the same criteria, we use zip(nums, target)
to pair corresponding elements:
- Even numbers in
nums
are paired with even numbers intarget
- Odd numbers in
nums
are paired with odd numbers intarget
- The sorted order ensures optimal pairing (smallest with smallest, etc.)
Step 3: Calculate Total Difference
We compute sum(abs(a - b) for a, b in zip(nums, target))
:
- For each pair
(a, b)
, calculate the absolute difference|a - b|
- Sum all these differences
Step 4: Determine Number of Operations
The final answer is the total difference divided by 4
:
- Each element needs
|a - b| / 2
units of change - Since each operation affects two elements (one +2, one -2), and we're summing differences for all elements, we count each operation twice
- Therefore, we divide by
4
to get the actual number of operations
Example Walkthrough:
If nums = [8, 12, 6]
and target = [2, 14, 10]
:
- After sorting:
nums = [6, 8, 12]
,target = [2, 10, 14]
(all even, so sorted by value) - Pairs:
(6, 2)
,(8, 10)
,(12, 14)
- Differences:
|6-2| + |8-10| + |12-14| = 4 + 2 + 2 = 8
- Operations needed:
8 / 4 = 2
This approach ensures optimal pairing and correctly accounts for the bidirectional nature of each operation.
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 work through a concrete example to illustrate the solution approach.
Given:
nums = [1, 4, 7, 10]
target = [3, 8, 5, 12]
Step 1: Custom Sorting
First, we sort both arrays using the key lambda x: (x & 1, x)
:
For nums
:
1
:(1 & 1, 1) = (1, 1)
(odd)4
:(4 & 1, 4) = (0, 4)
(even)7
:(7 & 1, 7) = (1, 7)
(odd)10
:(10 & 1, 10) = (0, 10)
(even)
Sorted nums
: [4, 10, 1, 7]
(evens first: 4, 10; then odds: 1, 7)
For target
:
3
:(3 & 1, 3) = (1, 3)
(odd)8
:(8 & 1, 8) = (0, 8)
(even)5
:(5 & 1, 5) = (1, 5)
(odd)12
:(12 & 1, 12) = (0, 12)
(even)
Sorted target
: [8, 12, 3, 5]
(evens first: 8, 12; then odds: 3, 5)
Step 2: Element Pairing
After sorting, we pair corresponding elements:
- Even pairs:
(4, 8)
and(10, 12)
- Odd pairs:
(1, 3)
and(7, 5)
Step 3: Calculate Differences
For each pair, calculate the absolute difference:
|4 - 8| = 4
|10 - 12| = 2
|1 - 3| = 2
|7 - 5| = 2
Total difference: 4 + 2 + 2 + 2 = 10
Step 4: Calculate Operations
Number of operations = 10 / 4 = 2.5 → 2
operations (integer division)
Verification:
Let's verify this makes sense:
- Operation 1: Add 2 to
nums[0]=1
(becomes 3), subtract 2 fromnums[2]=7
(becomes 5) - Operation 2: Add 2 to
nums[1]=4
(becomes 6), subtract 2 fromnums[3]=10
(becomes 8) - Operation 3: Add 2 to
nums[1]=6
(becomes 8), subtract 2 fromnums[3]=8
(becomes 6) ... continuing until we match the target frequencies
The actual operations would transform:
4 → 8
(needs +4, so 2 operations where it gains)10 → 12
(needs +2, so 1 operation where it gains)1 → 3
(needs +2, so 1 operation where it gains)7 → 5
(needs -2, so 1 operation where it loses)
Since we need 2 operations total and each operation affects exactly 2 elements, this confirms our calculation.
Solution Implementation
1from typing import List
2
3class Solution:
4 def makeSimilar(self, nums: List[int], target: List[int]) -> int:
5 # Sort both arrays by parity (odd/even) first, then by value
6 # x & 1 returns 1 for odd numbers, 0 for even numbers
7 # This groups even numbers first, then odd numbers, each group sorted in ascending order
8 nums.sort(key=lambda x: (x & 1, x))
9 target.sort(key=lambda x: (x & 1, x))
10
11 # Calculate the total number of operations needed
12 # Each operation involves adding/subtracting 2 to/from a pair of numbers
13 # The sum of absolute differences gives us twice the total operations needed
14 # (since each operation affects two numbers)
15 # Dividing by 4 gives us the minimum number of operations
16 total_difference = sum(abs(num - target_num) for num, target_num in zip(nums, target))
17
18 return total_difference // 4
19
1class Solution {
2 public long makeSimilar(int[] nums, int[] target) {
3 // Sort both arrays to match elements by their relative positions
4 Arrays.sort(nums);
5 Arrays.sort(target);
6
7 // Separate even and odd numbers from nums array
8 List<Integer> evenNums = new ArrayList<>();
9 List<Integer> oddNums = new ArrayList<>();
10
11 // Separate even and odd numbers from target array
12 List<Integer> evenTarget = new ArrayList<>();
13 List<Integer> oddTarget = new ArrayList<>();
14
15 // Partition nums array into even and odd lists
16 for (int num : nums) {
17 if (num % 2 == 0) {
18 evenNums.add(num);
19 } else {
20 oddNums.add(num);
21 }
22 }
23
24 // Partition target array into even and odd lists
25 for (int num : target) {
26 if (num % 2 == 0) {
27 evenTarget.add(num);
28 } else {
29 oddTarget.add(num);
30 }
31 }
32
33 // Calculate total difference for transformation
34 long totalDifference = 0;
35
36 // Sum up absolute differences between corresponding even numbers
37 for (int i = 0; i < evenNums.size(); i++) {
38 totalDifference += Math.abs(evenNums.get(i) - evenTarget.get(i));
39 }
40
41 // Sum up absolute differences between corresponding odd numbers
42 for (int i = 0; i < oddNums.size(); i++) {
43 totalDifference += Math.abs(oddNums.get(i) - oddTarget.get(i));
44 }
45
46 // Divide by 4 because each operation involves +2/-2 on two elements
47 // and we count each difference twice (once for increase, once for decrease)
48 return totalDifference / 4;
49 }
50}
51
1class Solution {
2public:
3 long long makeSimilar(vector<int>& nums, vector<int>& target) {
4 // Sort both arrays to match elements optimally
5 sort(nums.begin(), nums.end());
6 sort(target.begin(), target.end());
7
8 // Separate odd and even numbers from nums array
9 vector<int> numsOdd;
10 vector<int> numsEven;
11
12 // Separate odd and even numbers from target array
13 vector<int> targetOdd;
14 vector<int> targetEven;
15
16 // Partition nums into odd and even numbers
17 for (int value : nums) {
18 if (value & 1) { // Check if number is odd using bitwise AND
19 numsOdd.emplace_back(value);
20 } else {
21 numsEven.emplace_back(value);
22 }
23 }
24
25 // Partition target into odd and even numbers
26 for (int value : target) {
27 if (value & 1) { // Check if number is odd using bitwise AND
28 targetOdd.emplace_back(value);
29 } else {
30 targetEven.emplace_back(value);
31 }
32 }
33
34 // Calculate total difference for matching pairs
35 long long totalDifference = 0;
36
37 // Sum absolute differences between corresponding odd numbers
38 for (int i = 0; i < numsOdd.size(); ++i) {
39 totalDifference += abs(numsOdd[i] - targetOdd[i]);
40 }
41
42 // Sum absolute differences between corresponding even numbers
43 for (int i = 0; i < numsEven.size(); ++i) {
44 totalDifference += abs(numsEven[i] - targetEven[i]);
45 }
46
47 // Divide by 4 because each operation changes two numbers by 2
48 // and we count each difference twice (once for increase, once for decrease)
49 return totalDifference / 4;
50 }
51};
52
1/**
2 * Calculates the minimum number of operations to make two arrays similar
3 * by swapping elements. Each operation consists of swapping two elements
4 * from different arrays that have the same parity (both even or both odd).
5 *
6 * @param nums - First array of numbers to be transformed
7 * @param target - Target array that nums should be transformed into
8 * @returns The minimum number of operations needed
9 */
10function makeSimilar(nums: number[], target: number[]): number {
11 // Sort both arrays in ascending order for optimal pairing
12 nums.sort((a, b) => a - b);
13 target.sort((a, b) => a - b);
14
15 // Arrays to store even and odd numbers from nums
16 const evenNumbersFromNums: number[] = [];
17 const oddNumbersFromNums: number[] = [];
18
19 // Arrays to store even and odd numbers from target
20 const evenNumbersFromTarget: number[] = [];
21 const oddNumbersFromTarget: number[] = [];
22
23 // Separate nums array into even and odd numbers
24 for (const value of nums) {
25 if (value % 2 === 0) {
26 evenNumbersFromNums.push(value);
27 } else {
28 oddNumbersFromNums.push(value);
29 }
30 }
31
32 // Separate target array into even and odd numbers
33 for (const value of target) {
34 if (value % 2 === 0) {
35 evenNumbersFromTarget.push(value);
36 } else {
37 oddNumbersFromTarget.push(value);
38 }
39 }
40
41 // Calculate total difference for transformation
42 let totalDifference = 0;
43
44 // Sum up absolute differences between paired even numbers
45 for (let i = 0; i < evenNumbersFromNums.length; ++i) {
46 totalDifference += Math.abs(evenNumbersFromNums[i] - evenNumbersFromTarget[i]);
47 }
48
49 // Sum up absolute differences between paired odd numbers
50 for (let i = 0; i < oddNumbersFromNums.length; ++i) {
51 totalDifference += Math.abs(oddNumbersFromNums[i] - oddNumbersFromTarget[i]);
52 }
53
54 // Divide by 4 because each swap operation affects 2 elements
55 // and we count each difference twice (once from each perspective)
56 return totalDifference / 4;
57}
58
Time and Space Complexity
Time Complexity: O(n × log n)
, where n
is the length of the array nums
.
The time complexity is dominated by the sorting operations:
nums.sort(key=lambda x: (x & 1, x))
takesO(n × log n)
timetarget.sort(key=lambda x: (x & 1, x))
takesO(n × log n)
time- The
zip
operation and sum calculation takeO(n)
time
Since the sorting operations are performed sequentially, the overall time complexity is O(n × log n) + O(n × log n) + O(n) = O(n × log n)
.
Space Complexity: O(1)
or O(n)
depending on the sorting algorithm implementation.
- If the sorting is done in-place (like Python's Timsort for lists), the space complexity is
O(1)
for the additional variables - However, Python's sort may use up to
O(n)
auxiliary space in the worst case for its merge operations - The lambda functions and the generator expression in sum use
O(1)
additional space
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrect Pairing Without Considering Parity
The Problem: A common mistake is to simply sort both arrays by value and pair them directly, without considering the parity constraint. Since operations only add/subtract 2, odd numbers can never become even and vice versa.
Incorrect Approach:
def makeSimilar(self, nums: List[int], target: List[int]) -> int:
nums.sort() # Wrong: sorts only by value
target.sort()
return sum(abs(a - b) for a, b in zip(nums, target)) // 4
Why It Fails:
If nums = [1, 4, 5, 8]
and target = [2, 3, 6, 7]
:
- Incorrect pairing: (1,2), (4,3), (5,6), (8,7)
- This tries to transform odd→even and even→odd, which is impossible
Solution:
Always separate by parity first using key=lambda x: (x & 1, x)
or key=lambda x: (x % 2, x)
.
Pitfall 2: Wrong Division Factor
The Problem: Dividing by 2 instead of 4 when calculating the final answer.
Incorrect Approach:
def makeSimilar(self, nums: List[int], target: List[int]) -> int:
nums.sort(key=lambda x: (x & 1, x))
target.sort(key=lambda x: (x & 1, x))
return sum(abs(a - b) for a, b in zip(nums, target)) // 2 # Wrong!
Why It Fails: Each operation changes two elements: one by +2 and another by -2. When summing absolute differences, we count the effect of each operation twice (once for the element that increases, once for the element that decreases). The total difference represents the sum of all individual changes needed, where each unit of change is 2. Since each operation provides 4 units of total change (2 up + 2 down), we divide by 4.
Solution: Remember that the division factor is 4 because:
- Each element needs
|a - b|
units of adjustment - Each operation provides 4 units of total adjustment (2 added + 2 subtracted)
- The sum counts each operation's effect twice
Pitfall 3: Using Modulo Instead of Bitwise AND
The Problem:
While both x % 2
and x & 1
work correctly for determining parity, using modulo can be slightly less efficient for large datasets.
Less Optimal:
nums.sort(key=lambda x: (x % 2, x)) # Works but slightly slower
Optimal:
nums.sort(key=lambda x: (x & 1, x)) # Bitwise operation is faster
Why It Matters:
Bitwise AND (&
) is a single CPU instruction, while modulo (%
) might require division. Though the performance difference is minimal in practice, using bitwise operations demonstrates better understanding of low-level optimizations.
Which technique can we use to find the middle of a linked list?
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!