2340. Minimum Adjacent Swaps to Make a Valid Array π
Problem Description
You have an integer array nums
indexed from 0. You can swap adjacent elements in this array.
Your goal is to make the array "valid" by meeting two conditions:
- The smallest element must be at the leftmost position (index 0)
- The largest element must be at the rightmost position (last index)
If there are multiple elements with the same minimum or maximum value:
- Any of the minimum values can be at the leftmost position
- Any of the maximum values can be at the rightmost position
You need to find the minimum number of adjacent swaps required to achieve this valid configuration.
For example, if nums = [3, 4, 5, 5, 3, 1]
:
- The minimum value is 1 (at index 5)
- The maximum value is 5 (appears at indices 2 and 3)
- You would need to move 1 to the leftmost position
- You would need to move one of the 5s to the rightmost position (the one at index 3 is already closer to the right)
The solution tracks:
- Index
i
: position of the first occurrence of the minimum value - Index
j
: position of the last occurrence of the maximum value
The number of swaps depends on the relative positions:
- If
i == j
: the same element is both min and max (all elements are equal), so 0 swaps needed - If
i < j
: min is left of max, needi + (n-1-j)
swaps - If
i > j
: min is right of max, needi + (n-1-j) - 1
swaps (subtract 1 because moving min left will also move max one position right)
Intuition
The key insight is that we only need to move two specific elements: the minimum value to the leftmost position and the maximum value to the rightmost position. Since we can only swap adjacent elements, we need to count how many positions each element needs to move.
Let's think about this step by step:
-
Finding the right elements to move: We need to identify which minimum and maximum elements to move. If there are multiple minimums, we want the leftmost one (already closest to position 0). If there are multiple maximums, we want the rightmost one (already closest to the last position). This minimizes the number of swaps needed.
-
Counting swaps for each element:
- To move the minimum from position
i
to position 0, we need exactlyi
swaps (moving it left by swapping with each element to its left) - To move the maximum from position
j
to positionn-1
, we need exactlyn-1-j
swaps (moving it right by swapping with each element to its right)
- To move the minimum from position
-
The overlap consideration: Here's the clever part - if the minimum element is initially to the right of the maximum element (
i > j
), something interesting happens. As we move the minimum leftward, it will eventually pass by the maximum element. This passing swap contributes to both movements! It moves the minimum one step left AND the maximum one step right. So we've counted this swap twice - once in the minimum's movement and once in the maximum's movement. That's why we subtract 1 in this case. -
Special case: If
i == j
, the same element is both the minimum and maximum (all array elements are equal), so the array is already valid with 0 swaps.
The formula i + (n-1-j) - (i > j)
elegantly captures all these cases:
- Basic swaps needed:
i
for minimum +(n-1-j)
for maximum - Subtract 1 if
i > j
to account for the double-counted swap - The expression
(i > j)
evaluates to 1 if true, 0 if false, perfectly handling both cases
Learn more about Greedy patterns.
Solution Approach
The implementation uses a single-pass traversal with index tracking to efficiently find the required positions:
1. Initialize index trackers:
i = j = 0
i
tracks the index of the leftmost minimum valuej
tracks the index of the rightmost maximum value
2. Traverse the array once to find optimal positions:
for k, v in enumerate(nums):
if v < nums[i] or (v == nums[i] and k < i):
i = k
if v >= nums[j] or (v == nums[j] and k > j):
j = k
For the minimum value tracking:
- Update
i
if we find a smaller value:v < nums[i]
- Also update
i
if we find an equal minimum that's more to the left:v == nums[i] and k < i
- This ensures we get the leftmost occurrence of the minimum value
For the maximum value tracking:
- Update
j
if we find a larger value:v > nums[j]
- Also update
j
if we find an equal maximum:v >= nums[j]
- The
>=
operator naturally gives us the rightmost occurrence since we're traversing left to right
3. Calculate the minimum swaps:
return 0 if i == j else i + len(nums) - 1 - j - (i > j)
This handles three cases:
- Case 1:
i == j
- The same element is both min and max (all elements equal), return0
- Case 2:
i < j
- Min is left of max, returni + (n-1-j)
swaps - Case 3:
i > j
- Min is right of max, returni + (n-1-j) - 1
swaps
The expression (i > j)
evaluates to True
(1) or False
(0), automatically subtracting 1 when the minimum needs to pass the maximum during swapping.
Time Complexity: O(n)
- Single pass through the array
Space Complexity: O(1)
- Only using two index variables
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 = [2, 1, 4, 1, 3]
:
Step 1: Find the positions of elements to move
We traverse the array to find:
- The leftmost occurrence of the minimum value
- The rightmost occurrence of the maximum value
Index k | Value v | Current min (nums[i]) | Current max (nums[j]) | Update i? | Update j? | New i | New j |
---|---|---|---|---|---|---|---|
0 | 2 | 2 (at index 0) | 2 (at index 0) | No | No | 0 | 0 |
1 | 1 | 2 | 2 | Yes (1 < 2) | No | 1 | 0 |
2 | 4 | 1 | 2 | No | Yes (4 > 2) | 1 | 2 |
3 | 1 | 1 | 4 | No* | No | 1 | 2 |
4 | 3 | 1 | 4 | No | No | 1 | 2 |
*At index 3, we find another 1, but we don't update i because we want the leftmost minimum (i=1 is already left of k=3)
Final positions: i = 1
(minimum value 1), j = 2
(maximum value 4)
Step 2: Calculate swaps needed
Since i < j
(1 < 2), we use the formula: i + (n-1-j)
- Swaps to move min from index 1 to index 0:
i = 1
swap - Swaps to move max from index 2 to index 4:
(5-1-2) = 2
swaps - Total:
1 + 2 = 3
swaps
Step 3: Verify with actual swaps
Starting array: [2, 1, 4, 1, 3]
Move minimum (1 at index 1) to the left:
- Swap indices 0 and 1:
[1, 2, 4, 1, 3]
(1 swap)
Move maximum (4 at index 2) to the right:
- Swap indices 2 and 3:
[1, 2, 1, 4, 3]
(1 swap) - Swap indices 3 and 4:
[1, 2, 1, 3, 4]
(1 swap)
Final array: [1, 2, 1, 3, 4]
β
Total swaps: 3 β
Alternative example to show the overlap case:
For nums = [3, 4, 1, 5]
:
i = 2
(minimum value 1 at index 2)j = 3
(maximum value 5 at index 3)- Since
i < j
, swaps =2 + (4-1-3) - 0 = 2 + 0 = 2
But if we had nums = [3, 5, 1, 4]
:
i = 2
(minimum value 1 at index 2)j = 1
(maximum value 5 at index 1)- Since
i > j
, swaps =2 + (4-1-1) - 1 = 2 + 2 - 1 = 3
The subtraction of 1 accounts for when the minimum (moving left) passes the maximum (moving right) - this single swap helps both elements reach their destinations.
Solution Implementation
1class Solution:
2 def minimumSwaps(self, nums: List[int]) -> int:
3 # Find the leftmost index of the minimum value
4 min_index = 0
5 # Find the rightmost index of the maximum value
6 max_index = 0
7
8 # Iterate through the array to find positions of min and max elements
9 for current_index, value in enumerate(nums):
10 # Update min_index if we find a smaller value or same value at earlier position
11 if value < nums[min_index] or (value == nums[min_index] and current_index < min_index):
12 min_index = current_index
13
14 # Update max_index if we find a larger or equal value at later position
15 if value >= nums[max_index] or (value == nums[max_index] and current_index > max_index):
16 max_index = current_index
17
18 # If min and max are at the same position, no swaps needed
19 if min_index == max_index:
20 return 0
21
22 # Calculate minimum swaps needed:
23 # - min_index: swaps to move minimum to front
24 # - (len(nums) - 1 - max_index): swaps to move maximum to end
25 # - Subtract 1 if min_index > max_index (they cross during swapping)
26 return min_index + len(nums) - 1 - max_index - (1 if min_index > max_index else 0)
27
1class Solution {
2 public int minimumSwaps(int[] nums) {
3 int arrayLength = nums.length;
4
5 // Track indices of minimum and maximum elements
6 int minIndex = 0;
7 int maxIndex = 0;
8
9 // Find the leftmost minimum and rightmost maximum elements
10 for (int currentIndex = 0; currentIndex < arrayLength; ++currentIndex) {
11 // Update minimum index if we find a smaller value
12 // or same value at an earlier position (leftmost)
13 if (nums[currentIndex] < nums[minIndex] ||
14 (nums[currentIndex] == nums[minIndex] && currentIndex < minIndex)) {
15 minIndex = currentIndex;
16 }
17
18 // Update maximum index if we find a larger value
19 // or same value at a later position (rightmost)
20 if (nums[currentIndex] > nums[maxIndex] ||
21 (nums[currentIndex] == nums[maxIndex] && currentIndex > maxIndex)) {
22 maxIndex = currentIndex;
23 }
24 }
25
26 // If min and max are at the same position, no swaps needed
27 if (minIndex == maxIndex) {
28 return 0;
29 }
30
31 // Calculate total swaps needed:
32 // - minIndex: swaps to move min to front
33 // - (arrayLength - 1 - maxIndex): swaps to move max to end
34 // - Subtract 1 if minIndex > maxIndex to avoid double counting
35 // (they cross paths during swapping)
36 return minIndex + arrayLength - 1 - maxIndex - (minIndex > maxIndex ? 1 : 0);
37 }
38}
39
1class Solution {
2public:
3 int minimumSwaps(vector<int>& nums) {
4 int n = nums.size();
5
6 // Find the leftmost index of the minimum element
7 int minIndex = 0;
8 // Find the rightmost index of the maximum element
9 int maxIndex = 0;
10
11 // Iterate through the array to find min and max positions
12 for (int i = 0; i < n; ++i) {
13 // Update minIndex if we find a smaller value, or same value but at a smaller index
14 if (nums[i] < nums[minIndex] || (nums[i] == nums[minIndex] && i < minIndex)) {
15 minIndex = i;
16 }
17
18 // Update maxIndex if we find a larger value, or same value but at a larger index
19 if (nums[i] > nums[maxIndex] || (nums[i] == nums[maxIndex] && i > maxIndex)) {
20 maxIndex = i;
21 }
22 }
23
24 // If min and max are at the same position, no swaps needed
25 if (minIndex == maxIndex) {
26 return 0;
27 }
28
29 // Calculate total swaps needed:
30 // - minIndex: swaps to move min element to the front
31 // - (n - 1 - maxIndex): swaps to move max element to the back
32 // - Subtract 1 if minIndex > maxIndex (they cross paths during swapping)
33 return minIndex + (n - 1 - maxIndex) - (minIndex > maxIndex ? 1 : 0);
34 }
35};
36
1/**
2 * Finds the minimum number of swaps needed to move the smallest element to the beginning
3 * and the largest element to the end of the array.
4 * @param nums - The input array of numbers
5 * @returns The minimum number of swaps required
6 */
7function minimumSwaps(nums: number[]): number {
8 // Index of the smallest element (leftmost if duplicates exist)
9 let minIndex: number = 0;
10 // Index of the largest element (rightmost if duplicates exist)
11 let maxIndex: number = 0;
12 const arrayLength: number = nums.length;
13
14 // Find the indices of minimum and maximum elements
15 for (let currentIndex: number = 0; currentIndex < arrayLength; ++currentIndex) {
16 // Update minimum index if we find a smaller value
17 // or same value but at an earlier position (we want leftmost minimum)
18 if (nums[currentIndex] < nums[minIndex] ||
19 (nums[currentIndex] === nums[minIndex] && currentIndex < minIndex)) {
20 minIndex = currentIndex;
21 }
22
23 // Update maximum index if we find a larger value
24 // or same value but at a later position (we want rightmost maximum)
25 if (nums[currentIndex] > nums[maxIndex] ||
26 (nums[currentIndex] === nums[maxIndex] && currentIndex > maxIndex)) {
27 maxIndex = currentIndex;
28 }
29 }
30
31 // If min and max are the same element, no swaps needed
32 if (minIndex === maxIndex) {
33 return 0;
34 }
35
36 // Calculate total swaps:
37 // - minIndex swaps to move min to position 0
38 // - (arrayLength - 1 - maxIndex) swaps to move max to last position
39 // - Subtract 1 if minIndex > maxIndex because they would swap past each other once
40 return minIndex + arrayLength - 1 - maxIndex - (minIndex > maxIndex ? 1 : 0);
41}
42
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the array nums
.
The algorithm performs a single pass through the array using one for
loop with enumerate(nums)
. During each iteration, it performs constant-time operations:
- Comparing values to find the minimum element (with leftmost position preference)
- Comparing values to find the maximum element (with rightmost position preference)
- Updating indices
i
andj
when necessary
Since we iterate through all n
elements exactly once and perform O(1)
operations for each element, the total time complexity is O(n)
.
Space Complexity: O(1)
The algorithm uses only a fixed amount of extra space:
- Two integer variables
i
andj
to store indices - Loop variable
k
and valuev
from enumeration - These variables don't scale with input size
The space usage remains constant regardless of the input array size, resulting in O(1)
space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrectly Finding the Rightmost Maximum Index
Pitfall: Using v > nums[j]
instead of v >= nums[j]
when updating the maximum index.
# WRONG - This finds the leftmost maximum, not rightmost
for k, v in enumerate(nums):
if v > nums[j]: # Missing the equals case
j = k
Why it's wrong: When there are multiple maximum values, this will only update j
when finding a strictly greater value, resulting in the leftmost occurrence of the maximum rather than the rightmost.
Solution: Always use >=
to ensure the index updates for equal maximum values during left-to-right traversal:
# CORRECT - Finds rightmost maximum
for k, v in enumerate(nums):
if v >= nums[j]: # Includes equal values
j = k
2. Forgetting to Handle the Overlap Case
Pitfall: Not subtracting 1 when the minimum element is to the right of the maximum element.
# WRONG - Always adds both distances
return i + len(nums) - 1 - j
Why it's wrong: When i > j
, moving the minimum element leftward will push the maximum element one position to the right, effectively reducing the total swaps needed by 1.
Solution: Check if indices overlap and adjust accordingly:
# CORRECT - Accounts for overlap
return i + len(nums) - 1 - j - (i > j)
3. Finding the Rightmost Minimum Instead of Leftmost
Pitfall: Not properly handling duplicate minimum values, resulting in finding a suboptimal starting position.
# WRONG - May not find the leftmost minimum
for k, v in enumerate(nums):
if v <= nums[i]: # This would find rightmost minimum
i = k
Why it's wrong: Using <=
for minimum tracking would continuously update i
for equal values, giving us the rightmost occurrence when we need the leftmost.
Solution: Only update for strictly smaller values, or for equal values that appear earlier:
# CORRECT - Finds leftmost minimum
for k, v in enumerate(nums):
if v < nums[i]: # Strictly less than
i = k
4. Initializing Indices Incorrectly
Pitfall: Initializing indices to invalid values or not considering the first element.
# WRONG - May cause index out of bounds or miss first element
min_index = -1
max_index = len(nums)
Solution: Initialize both indices to 0, treating the first element as the initial min and max:
# CORRECT - Start with first element as reference min_index = 0 max_index = 0
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
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!