3587. Minimum Adjacent Swaps to Alternate Parity
Problem Description
You have an array nums
containing distinct integers. Your goal is to rearrange this array so that adjacent elements have alternating parity (one even, one odd).
You can only swap two adjacent elements at a time. For example, if you have [1, 2, 3]
, you can swap positions 0 and 1 to get [2, 1, 3]
, or swap positions 1 and 2 to get [1, 3, 2]
.
A valid arrangement means that for every pair of neighboring elements in the array, one must be even and the other must be odd. For instance:
[1, 2, 3, 4]
is valid (odd-even-odd-even pattern)[2, 1, 4, 3]
is valid (even-odd-even-odd pattern)[1, 3, 2, 4]
is NOT valid (1 and 3 are both odd and adjacent)
Your task is to find the minimum number of adjacent swaps needed to transform the given array into any valid arrangement. If it's impossible to create a valid arrangement (for example, if you have 5 odd numbers and only 1 even number), return -1
.
The key insight is that for a valid alternating pattern to exist, the counts of odd and even numbers can differ by at most 1. If you have n
elements total:
- If
n
is even, you need exactlyn/2
odd numbers andn/2
even numbers - If
n
is odd, you need either(n+1)/2
odd and(n-1)/2
even, or(n-1)/2
odd and(n+1)/2
even
The solution tracks the positions of odd and even numbers separately, then calculates how many swaps are needed to move them to their target positions in a valid alternating arrangement.
Intuition
Think about what a valid arrangement looks like - it must follow either an even-odd-even-odd pattern or an odd-even-odd-even pattern. This means elements need to be in specific positions based on their parity.
First, let's consider when a valid arrangement is even possible. If we have n
total elements and they need to alternate, then:
- For even
n
: we need exactlyn/2
odd andn/2
even numbers - For odd
n
: we need one extra of either odd or even numbers
If the difference between odd and even counts is more than 1, it's impossible to create an alternating pattern.
Now, how do we count the minimum swaps? The key insight is that we only care about getting each number to a valid position, not the specific order within odd or even groups.
Consider this: if we decide to start with an even number, then all even numbers should be at positions 0, 2, 4, ... and all odd numbers should be at positions 1, 3, 5, ... We can track where each even number currently is and where it needs to go.
For example, if we have [1, 2, 3, 4]
and want the pattern to be even-odd-even-odd:
- Even numbers (2, 4) need to be at positions 0 and 2
- Currently, 2 is at position 1 and 4 is at position 3
- We need to move 2 from position 1 to position 0 (distance = 1)
- We need to move 4 from position 3 to position 2 (distance = 1)
- Total moves = 2
The brilliant part is that counting the total distance each element needs to move gives us the minimum swaps! This works because when we swap adjacent elements, we're essentially "bubbling" elements to their target positions, and the sum of distances equals the number of swaps needed.
When odd and even counts are equal, we have two possible patterns to try (starting with odd or starting with even), and we take the minimum. When counts differ by 1, only one pattern is possible - the more frequent parity must occupy the extra position.
Learn more about Greedy patterns.
Solution Approach
The implementation follows a systematic approach to calculate the minimum swaps:
1. Separate elements by parity:
We create a 2D list pos
where:
pos[0]
stores all indices of even numberspos[1]
stores all indices of odd numbers
This is done by iterating through nums
and using x & 1
to determine parity (0 for even, 1 for odd).
2. Check validity:
Calculate the difference between counts: abs(len(pos[0]) - len(pos[1]))
. If this difference is greater than 1, return -1
immediately as no valid arrangement exists.
3. Define the calculation function:
The calc(k)
function computes swaps needed when starting with parity k
:
- If
k = 0
, we want even numbers at positions 0, 2, 4, ... - If
k = 1
, we want odd numbers at positions 0, 2, 4, ...
The function uses: sum(abs(i - j) for i, j in zip(range(0, len(nums), 2), pos[k]))
This calculates:
range(0, len(nums), 2)
generates target positions: 0, 2, 4, ...pos[k]
contains current positions of numbers with parityk
- For each pair
(i, j)
,abs(i - j)
is the distance that element at positionj
needs to move to reach positioni
- The sum gives total moves needed
4. Determine which patterns to try:
- If
len(pos[0]) > len(pos[1])
: More even numbers, so evens must occupy positions 0, 2, 4, ... Returncalc(0)
- If
len(pos[0]) < len(pos[1])
: More odd numbers, so odds must occupy positions 0, 2, 4, ... Returncalc(1)
- If
len(pos[0]) == len(pos[1])
: Equal counts, try both patterns and returnmin(calc(0), calc(1))
Example walkthrough:
For nums = [3, 1, 2, 4]
:
pos[0] = [2, 3]
(positions of even numbers 2, 4)pos[1] = [0, 1]
(positions of odd numbers 3, 1)- Equal counts, so try both patterns
- Pattern 1 (even first): Target positions for evens are 0, 2. Cost =
|0-2| + |2-3| = 3
- Pattern 2 (odd first): Target positions for odds are 0, 2. Cost =
|0-0| + |2-1| = 1
- Return minimum = 1
The algorithm efficiently computes the answer in O(n)
time by leveraging the fact that the sum of distances equals the minimum number of adjacent swaps needed.
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 the solution with nums = [3, 5, 2, 6]
.
Step 1: Separate by parity
- Even numbers: 2 (at index 2), 6 (at index 3)
- Odd numbers: 3 (at index 0), 5 (at index 1)
pos[0] = [2, 3]
(positions of evens)pos[1] = [0, 1]
(positions of odds)
Step 2: Check validity
- Even count: 2, Odd count: 2
- Difference: |2 - 2| = 0 β€ 1 β Valid
Step 3: Try both patterns
Pattern 1: Even-Odd-Even-Odd
- Target positions for evens: 0, 2
- Current positions of evens: 2, 3
- Calculate moves:
- First even (at 2) needs to reach position 0: |0 - 2| = 2 moves
- Second even (at 3) needs to reach position 2: |2 - 3| = 1 move
- Total: 2 + 1 = 3 swaps
Pattern 2: Odd-Even-Odd-Even
- Target positions for odds: 0, 2
- Current positions of odds: 0, 1
- Calculate moves:
- First odd (at 0) needs to reach position 0: |0 - 0| = 0 moves
- Second odd (at 1) needs to reach position 2: |2 - 1| = 1 move
- Total: 0 + 1 = 1 swap
Step 4: Return minimum
- min(3, 1) = 1
The actual swap sequence would be: swap positions 1 and 2 to get [3, 2, 5, 6]
, which follows the odd-even-odd-even pattern.
Solution Implementation
1class Solution:
2 def minSwaps(self, nums: List[int]) -> int:
3 def calculate_swaps(starting_value: int) -> int:
4 """
5 Calculate the number of swaps needed when placing elements with
6 starting_value at even indices (0, 2, 4, ...)
7
8 The sum of absolute differences represents the minimum swaps needed
9 to move elements from their current positions to target positions
10 """
11 target_positions = range(0, len(nums), 2)
12 current_positions = position_lists[starting_value]
13 return sum(abs(target_pos - current_pos)
14 for target_pos, current_pos in zip(target_positions, current_positions))
15
16 # Group indices by element parity (0 or 1)
17 # position_lists[0] stores indices of even elements
18 # position_lists[1] stores indices of odd elements
19 position_lists = [[], []]
20 for index, value in enumerate(nums):
21 position_lists[value & 1].append(index)
22
23 even_count = len(position_lists[0])
24 odd_count = len(position_lists[1])
25
26 # Check if alternating arrangement is possible
27 # For alternating pattern, counts can differ by at most 1
28 if abs(even_count - odd_count) > 1:
29 return -1
30
31 # If more even elements, they must start at index 0
32 if even_count > odd_count:
33 return calculate_swaps(0)
34
35 # If more odd elements, they must start at index 0
36 if even_count < odd_count:
37 return calculate_swaps(1)
38
39 # If equal counts, try both starting patterns and return minimum
40 return min(calculate_swaps(0), calculate_swaps(1))
41
1class Solution {
2 // Store positions of even numbers (index 0) and odd numbers (index 1)
3 private List<Integer>[] positionsByParity = new List[2];
4 private int[] nums;
5
6 public int minSwaps(int[] nums) {
7 this.nums = nums;
8
9 // Initialize lists to store positions of even and odd numbers
10 Arrays.setAll(positionsByParity, index -> new ArrayList<>());
11
12 // Group indices by parity (even numbers at index 0, odd numbers at index 1)
13 for (int i = 0; i < nums.length; ++i) {
14 int parity = nums[i] & 1; // 0 for even, 1 for odd
15 positionsByParity[parity].add(i);
16 }
17
18 // Calculate sizes of even and odd number groups
19 int evenCount = positionsByParity[0].size();
20 int oddCount = positionsByParity[1].size();
21
22 // Check if valid alternating arrangement is possible
23 // The difference between counts cannot exceed 1
24 if (Math.abs(evenCount - oddCount) > 1) {
25 return -1;
26 }
27
28 // If more even numbers, array must start with even
29 if (evenCount > oddCount) {
30 return calculateSwaps(0); // Start with even
31 }
32
33 // If more odd numbers, array must start with odd
34 if (evenCount < oddCount) {
35 return calculateSwaps(1); // Start with odd
36 }
37
38 // If equal counts, try both patterns and return minimum
39 return Math.min(calculateSwaps(0), calculateSwaps(1));
40 }
41
42 /**
43 * Calculate minimum swaps needed to arrange array with given starting parity
44 * @param startingParity 0 for even-first pattern, 1 for odd-first pattern
45 * @return total number of swaps needed
46 */
47 private int calculateSwaps(int startingParity) {
48 int totalSwaps = 0;
49
50 // Elements at even indices (0, 2, 4...) should have the starting parity
51 for (int targetPosition = 0; targetPosition < nums.length; targetPosition += 2) {
52 int elementIndex = targetPosition / 2;
53 int currentPosition = positionsByParity[startingParity].get(elementIndex);
54
55 // Add distance between current and target position
56 totalSwaps += Math.abs(currentPosition - targetPosition);
57 }
58
59 return totalSwaps;
60 }
61}
62
1class Solution {
2public:
3 int minSwaps(vector<int>& nums) {
4 // Create two vectors to store positions of even and odd numbers
5 // positions[0] stores indices of even numbers
6 // positions[1] stores indices of odd numbers
7 vector<int> positions[2];
8
9 // Categorize numbers by their parity and store their original indices
10 for (int i = 0; i < nums.size(); ++i) {
11 positions[nums[i] & 1].push_back(i);
12 }
13
14 // Check if alternating arrangement is possible
15 // The difference between count of evens and odds should be at most 1
16 if (abs(int(positions[0].size() - positions[1].size())) > 1) {
17 return -1;
18 }
19
20 // Lambda function to calculate minimum swaps needed
21 // when starting with a specific parity (0 for even, 1 for odd)
22 auto calculateSwaps = [&](int startingParity) {
23 int totalSwaps = 0;
24 // Place numbers of given parity at even indices (0, 2, 4, ...)
25 for (int i = 0; i < nums.size(); i += 2) {
26 // Calculate distance from current position to target position
27 totalSwaps += abs(positions[startingParity][i / 2] - i);
28 }
29 return totalSwaps;
30 };
31
32 // If we have more even numbers, they must be placed at even indices
33 if (positions[0].size() > positions[1].size()) {
34 return calculateSwaps(0);
35 }
36
37 // If we have more odd numbers, they must be placed at even indices
38 if (positions[0].size() < positions[1].size()) {
39 return calculateSwaps(1);
40 }
41
42 // If counts are equal, try both arrangements and return minimum
43 return min(calculateSwaps(0), calculateSwaps(1));
44 }
45};
46
1/**
2 * Calculates the minimum number of swaps needed to make the array alternating between even and odd numbers
3 * @param nums - Input array of numbers
4 * @returns Minimum number of swaps required, or -1 if impossible
5 */
6function minSwaps(nums: number[]): number {
7 // Create two arrays to store positions of even and odd numbers
8 // positions[0] stores indices of even numbers, positions[1] stores indices of odd numbers
9 const positions: number[][] = [[], []];
10
11 // Populate position arrays based on parity of each number
12 for (let i = 0; i < nums.length; i++) {
13 // Use bitwise AND with 1 to determine if number is odd (1) or even (0)
14 positions[nums[i] & 1].push(i);
15 }
16
17 // Check if alternating arrangement is possible
18 // The difference in count between even and odd numbers should not exceed 1
19 if (Math.abs(positions[0].length - positions[1].length) > 1) {
20 return -1;
21 }
22
23 /**
24 * Calculates the total distance to move numbers of given parity to their target positions
25 * @param parityIndex - 0 for even numbers starting at index 0, 1 for odd numbers starting at index 0
26 * @returns Total distance (number of swaps) needed
27 */
28 const calculateSwaps = (parityIndex: number): number => {
29 let totalDistance = 0;
30
31 // Check every alternate position starting from 0
32 for (let targetIndex = 0; targetIndex < nums.length; targetIndex += 2) {
33 // Calculate distance between current position and target position
34 // targetIndex >> 1 is equivalent to Math.floor(targetIndex / 2)
35 const currentPosition = positions[parityIndex][targetIndex >> 1];
36 totalDistance += Math.abs(currentPosition - targetIndex);
37 }
38
39 return totalDistance;
40 };
41
42 // If there are more even numbers, they must start at index 0
43 if (positions[0].length > positions[1].length) {
44 return calculateSwaps(0);
45 }
46
47 // If there are more odd numbers, they must start at index 0
48 if (positions[0].length < positions[1].length) {
49 return calculateSwaps(1);
50 }
51
52 // If counts are equal, try both arrangements and return the minimum
53 return Math.min(calculateSwaps(0), calculateSwaps(1));
54}
55
Time and Space Complexity
Time Complexity: O(n)
The algorithm performs the following operations:
- One pass through the array to build the
pos
lists, which takesO(n)
time - The
calc
function computes the sum of absolute differences between corresponding positions. In the worst case, it processes approximatelyn/2
elements (when the array is evenly split between 0s and 1s), performingO(1)
work for each element, resulting inO(n)
time - The algorithm calls
calc
at most twice (when counts are equal), each takingO(n)
time - All other operations (length comparisons, min calculation) take
O(1)
time
Therefore, the overall time complexity is O(n) + O(n) + O(n) = O(n)
.
Space Complexity: O(n)
The algorithm uses:
- Two lists in
pos
to store the positions of 0s and 1s. Combined, these lists store exactlyn
positions (every element's index is stored exactly once), requiringO(n)
space - The
range
object in thecalc
function generates values on-the-fly and doesn't consume additional space proportional ton
- All other variables use
O(1)
space
Therefore, the overall space complexity is O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the Distance Sum as Number of Swaps
The Pitfall:
A common misconception is thinking that the sum of distances sum(abs(i - j))
might overcount or undercount the actual number of adjacent swaps needed. Developers often try to divide this sum by 2 or apply other transformations, thinking elements are being moved twice.
Why This Happens: When visualizing the swapping process, it seems like moving one element might affect others, leading to the belief that simple distance summation is incorrect.
The Solution: The sum of distances is exactly equal to the minimum number of adjacent swaps. Here's why:
- When we calculate
abs(target_pos - current_pos)
for each element, we're counting how many positions it needs to move - Adjacent swaps can be thought of as "bubble sort" movements
- Each unit of distance requires exactly one adjacent swap
- Elements moving in opposite directions don't interfere - they pass through each other via swaps
Correct Understanding Example:
# If element at index 3 needs to move to index 0: # [a, b, c, X] -> [a, b, X, c] -> [a, X, b, c] -> [X, a, b, c] # Distance = 3, Swaps needed = 3
2. Incorrectly Handling the Equal Count Case
The Pitfall: When even and odd counts are equal, developers sometimes arbitrarily choose one pattern without checking both possibilities.
Incorrect Implementation:
if even_count == odd_count: return calculate_swaps(0) # Wrong! Only checks one pattern
The Solution: Always check both patterns when counts are equal and return the minimum:
if even_count == odd_count:
return min(calculate_swaps(0), calculate_swaps(1))
This is crucial because depending on the initial arrangement, one pattern might require significantly fewer swaps than the other.
3. Off-by-One Errors in Parity Checking
The Pitfall: Using incorrect logic to determine which parity should start at position 0 when counts are unequal.
Common Mistake:
# Incorrect logic - reversed conditions if odd_count > even_count: return calculate_swaps(0) # Wrong! Should be calculate_swaps(1)
The Solution:
Remember the rule: whichever parity has more elements MUST occupy positions 0, 2, 4, ... (the positions generated by range(0, len(nums), 2)
).
- If more odds exist, odds go to positions 0, 2, 4, ... β
calculate_swaps(1)
- If more evens exist, evens go to positions 0, 2, 4, ... β
calculate_swaps(0)
4. Not Recognizing Impossible Cases Early
The Pitfall: Attempting to calculate swaps even when the difference in counts makes alternating arrangement impossible.
The Solution: Always validate feasibility first:
if abs(even_count - odd_count) > 1:
return -1
This check should come before any swap calculations to avoid unnecessary computation and potential errors.
What's the output of running the following function using the following tree as input?
1def serialize(root):
2 res = []
3 def dfs(root):
4 if not root:
5 res.append('x')
6 return
7 res.append(root.val)
8 dfs(root.left)
9 dfs(root.right)
10 dfs(root)
11 return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4 StringJoiner res = new StringJoiner(" ");
5 serializeDFS(root, res);
6 return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10 if (root == null) {
11 result.add("x");
12 return;
13 }
14 result.add(Integer.toString(root.val));
15 serializeDFS(root.left, result);
16 serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2 let res = [];
3 serialize_dfs(root, res);
4 return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8 if (!root) {
9 res.push("x");
10 return;
11 }
12 res.push(root.val);
13 serialize_dfs(root.left, res);
14 serialize_dfs(root.right, res);
15}
16
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
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!