Facebook Pixel

2717. Semi-Ordered Permutation

EasyArraySimulation
Leetcode Link

Problem Description

You have an array nums that is a permutation of integers from 1 to n. A permutation means it contains each number from 1 to n exactly once, but in some order.

The goal is to make this permutation "semi-ordered," which means:

  • The first element must be 1
  • The last element must be n

To achieve this, you can perform swaps, but with a restriction: you can only swap two adjacent elements at a time.

For example, if you have [3, 1, 2, 4], you could swap positions 0 and 1 to get [1, 3, 2, 4], then swap positions 2 and 3 to get [1, 3, 4, 2], and finally swap positions 1 and 2 to get [1, 4, 3, 2].

The task is to find the minimum number of such adjacent swaps needed to make the array semi-ordered.

The solution works by finding where 1 and n are currently located in the array. If 1 is at index i, it needs i swaps to move it to the front (position 0). If n is at index j, it needs n - 1 - j swaps to move it to the end (position n-1).

However, there's a special case to consider: if 1 is to the right of n initially (i.e., i > j), then as we move 1 leftward and n rightward, they will pass each other once, which means we've counted one swap twice. In this case, we need to subtract 1 from the total count.

The formula becomes:

  • If i < j: total swaps = i + (n - 1 - j) = i + n - j - 1
  • If i > j: total swaps = i + (n - 1 - j) - 1 = i + n - j - 2
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we only need to focus on two specific elements: 1 and n. All other elements don't matter for making the permutation semi-ordered.

Since we can only swap adjacent elements, moving an element from position i to position j requires exactly |i - j| swaps. Think of it like bubble sort - to move an element left by one position, we swap it with its left neighbor; to move it right by one position, we swap it with its right neighbor.

Let's think about what needs to happen:

  • Element 1 needs to reach position 0 (the front)
  • Element n needs to reach position n-1 (the end)

If 1 starts at position i, it needs exactly i leftward moves (swaps) to reach the front. If n starts at position j, it needs exactly n - 1 - j rightward moves to reach the end.

Initially, we might think the answer is simply i + (n - 1 - j). However, there's a subtle detail: what if 1 and n need to cross paths?

Consider when 1 is initially to the right of n (position i > j). As we move 1 leftward and n rightward, at some point they'll be adjacent and need to swap positions. This swap contributes to both elements' movement - it moves 1 one step left AND moves n one step right simultaneously. We've counted this swap twice in our formula, so we need to subtract 1.

When 1 is initially to the left of n (position i < j), they never meet during their journeys to their destinations, so no adjustment is needed.

This leads us to the elegant formula where we add i + n - j - k, where k = 1 when they don't cross paths and k = 2 when they do.

Solution Approach

The implementation is straightforward once we understand the problem:

  1. Find the positions of key elements: Use the index() method to locate where 1 and n are in the array. Store these positions as i and j respectively.

  2. Determine if elements will cross: Check if i < j (meaning 1 is to the left of n).

    • If i < j: The elements won't cross during their movements, so we set k = 1
    • If i > j: The elements will cross paths, counting one swap twice, so we set k = 2
  3. Calculate the minimum swaps: Apply the formula i + n - j - k

    • i: number of swaps to move 1 to position 0
    • n - j - 1: number of swaps to move n to position n-1
    • Simplifying: i + (n - j - 1) = i + n - j - 1
    • Adjust by subtracting an additional 1 if elements cross: i + n - j - 2

The solution uses a ternary operator to elegantly handle both cases: k = 1 if i < j else 2, then returns i + n - j - k.

Time Complexity: O(n) for finding the indices of 1 and n using the index() method.

Space Complexity: O(1) as we only use a constant amount of extra space for variables i, j, and k.

The beauty of this solution lies in its simplicity - rather than simulating the actual swaps, we mathematically determine the exact number of moves needed based on the initial positions of just two elements.

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 the solution with a concrete example: nums = [2, 4, 1, 3]

Step 1: Identify key positions

  • Find where 1 is located: i = 2 (at index 2)
  • Find where n = 4 is located: j = 1 (at index 1)

Step 2: Visualize what needs to happen

  • Current state: [2, 4, 1, 3]
  • Target state: [1, ?, ?, 4] (we need 1 at the front and 4 at the end)

Step 3: Count the moves for each element

  • To move 1 from index 2 to index 0: needs 2 leftward swaps
    • [2, 4, 1, 3][2, 1, 4, 3] (swap positions 1 and 2)
    • [2, 1, 4, 3][1, 2, 4, 3] (swap positions 0 and 1)
  • To move 4 from index 1 to index 3: needs 2 rightward swaps
    • Starting from [1, 2, 4, 3]:
    • [1, 2, 4, 3][1, 2, 3, 4] (swap positions 2 and 3)

Step 4: Check if elements cross paths

  • Since i = 2 > j = 1, element 1 is initially to the right of element 4
  • This means they will cross paths during their movements
  • When they cross, one swap moves both elements simultaneously
  • We've counted this swap in both elements' move counts, so we need to subtract 1

Step 5: Calculate using the formula

  • Since i > j, we use k = 2
  • Total swaps = i + n - j - k = 2 + 4 - 1 - 2 = 3

Verification by simulating the swaps:

  1. [2, 4, 1, 3][2, 1, 4, 3] (swap indices 1 and 2, moving 1 left)
  2. [2, 1, 4, 3][1, 2, 4, 3] (swap indices 0 and 1, moving 1 left)
  3. [1, 2, 4, 3][1, 2, 3, 4] (swap indices 2 and 3, moving 4 right)

Indeed, we need exactly 3 swaps to make the array semi-ordered!

Solution Implementation

1class Solution:
2    def semiOrderedPermutation(self, nums: List[int]) -> int:
3        """
4        Calculate minimum operations to make a semi-ordered permutation.
5        A semi-ordered permutation has 1 at the beginning and n at the end.
6      
7        Args:
8            nums: A list of integers representing a permutation of [1, 2, ..., n]
9      
10        Returns:
11            Minimum number of adjacent swaps needed
12        """
13        n = len(nums)
14      
15        # Find the current position of 1 and n
16        pos_of_one = nums.index(1)
17        pos_of_n = nums.index(n)
18      
19        # Calculate adjustment based on relative positions
20        # If 1 is already before n, they won't cross during swaps (adjustment = 1)
21        # If 1 is after n, they will cross once during swaps (adjustment = 2)
22        adjustment = 1 if pos_of_one < pos_of_n else 2
23      
24        # Total operations = moves to bring 1 to start + moves to bring n to end - adjustment
25        # pos_of_one: number of swaps to move 1 to index 0
26        # (n - 1 - pos_of_n): number of swaps to move n to index n-1
27        # adjustment: accounts for whether 1 and n cross during swaps
28        return pos_of_one + (n - 1 - pos_of_n) - adjustment + 1
29```
30
31Wait, let me reconsider the logic. The original formula is `i + n - j - k`. Let me trace through it:
32
33```python3
34class Solution:
35    def semiOrderedPermutation(self, nums: List[int]) -> int:
36        """
37        Calculate minimum operations to make a semi-ordered permutation.
38        A semi-ordered permutation has 1 at the beginning and n at the end.
39      
40        Args:
41            nums: A list of integers representing a permutation of [1, 2, ..., n]
42      
43        Returns:
44            Minimum number of adjacent swaps needed
45        """
46        n = len(nums)
47      
48        # Find the current position of element 1 and element n
49        position_of_one = nums.index(1)
50        position_of_n = nums.index(n)
51      
52        # Determine if elements will cross during swapping
53        # If 1 is before n: they won't cross, so we count both movements separately
54        # If 1 is after n: they will cross once, reducing total swaps by 1
55        crossing_adjustment = 1 if position_of_one < position_of_n else 2
56      
57        # Calculate total swaps needed:
58        # - position_of_one: swaps to move 1 to index 0 (leftward)
59        # - (n - 1 - position_of_n): swaps to move n to index n-1 (rightward)
60        # - crossing_adjustment: compensates for potential crossing
61        total_swaps = position_of_one + n - position_of_n - crossing_adjustment
62      
63        return total_swaps
64
1class Solution {
2    public int semiOrderedPermutation(int[] nums) {
3        int arrayLength = nums.length;
4        int positionOfOne = 0;
5        int positionOfN = 0;
6      
7        // Find the positions of element 1 and element n in the array
8        for (int index = 0; index < arrayLength; ++index) {
9            if (nums[index] == 1) {
10                positionOfOne = index;
11            }
12            if (nums[index] == arrayLength) {
13                positionOfN = index;
14            }
15        }
16      
17        // Calculate adjustment factor based on relative positions
18        // If 1 is before n: they will cross during swaps, saving 1 operation
19        // If 1 is after n: no crossing occurs, need 2 extra operations
20        int adjustmentFactor = (positionOfOne < positionOfN) ? 1 : 2;
21      
22        // Total operations = swaps to move 1 to start + swaps to move n to end - adjustment
23        // positionOfOne: number of swaps to move 1 to index 0
24        // (arrayLength - 1 - positionOfN): number of swaps to move n to last index
25        return positionOfOne + arrayLength - positionOfN - adjustmentFactor;
26    }
27}
28
1class Solution {
2public:
3    int semiOrderedPermutation(vector<int>& nums) {
4        int n = nums.size();
5      
6        // Find the index of element 1
7        int positionOfOne = find(nums.begin(), nums.end(), 1) - nums.begin();
8      
9        // Find the index of element n (the largest element)
10        int positionOfN = find(nums.begin(), nums.end(), n) - nums.begin();
11      
12        // Calculate adjustment factor:
13        // - If 1 comes before n, we need one less swap (they don't cross paths)
14        // - If 1 comes after n, they will cross paths during swapping
15        int adjustment = (positionOfOne < positionOfN) ? 1 : 2;
16      
17        // Total swaps needed:
18        // - positionOfOne swaps to move 1 to the front
19        // - (n - 1 - positionOfN) swaps to move n to the end
20        // - Subtract adjustment to account for potential overlap
21        return positionOfOne + n - positionOfN - adjustment;
22    }
23};
24
1/**
2 * Calculates the minimum number of operations to make a permutation semi-ordered.
3 * A semi-ordered permutation has 1 at the first position and n at the last position.
4 * Each operation swaps two adjacent elements.
5 * 
6 * @param nums - An array containing a permutation of integers from 1 to n
7 * @returns The minimum number of operations needed
8 */
9function semiOrderedPermutation(nums: number[]): number {
10    // Get the length of the array
11    const arrayLength: number = nums.length;
12  
13    // Find the index of element 1
14    const indexOfOne: number = nums.indexOf(1);
15  
16    // Find the index of element n (the largest element)
17    const indexOfMax: number = nums.indexOf(arrayLength);
18  
19    // Determine adjustment factor based on relative positions
20    // If 1 comes before n, they won't cross during swaps (adjustment = 1)
21    // If 1 comes after n, they will cross once during swaps (adjustment = 2)
22    const adjustmentFactor: number = indexOfOne < indexOfMax ? 1 : 2;
23  
24    // Calculate total operations:
25    // - indexOfOne: operations to move 1 to the front
26    // - (arrayLength - indexOfMax): operations to move n to the end
27    // - adjustmentFactor: subtract overlap when elements cross or don't cross
28    return indexOfOne + arrayLength - indexOfMax - adjustmentFactor;
29}
30

Time and Space Complexity

The time complexity is O(n), where n is the length of the array. This is because the index() method needs to traverse the array to find the positions of elements 1 and n, which takes linear time in the worst case. Each index() call can potentially scan through all n elements, and we make two such calls.

The space complexity is O(1). The algorithm only uses a constant amount of extra space to store the variables n, i, j, and k, regardless of the input size. No additional data structures that scale with the input are created.

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

Common Pitfalls

1. Incorrectly Handling the Crossing Case

The most common mistake is forgetting to account for when elements 1 and n cross paths during swapping. When 1 starts to the right of n (i.e., position_of_one > position_of_n), they will inevitably pass each other as 1 moves left and n moves right.

Incorrect approach:

# Wrong: Always adding the distances without considering crossing
return position_of_one + (n - 1 - position_of_n)

Why it fails: This counts one swap twice when the elements cross. For example, with [2, 4, 1, 3], element 1 is at index 2 and element 4 is at index 1. Without the crossing adjustment, you'd calculate 2 + (4-1-1) = 4 swaps, but the actual answer is 3.

Correct approach:

# Check if elements will cross and adjust accordingly
if position_of_one > position_of_n:
    return position_of_one + (n - 1 - position_of_n) - 1
else:
    return position_of_one + (n - 1 - position_of_n)

2. Edge Case: Elements Already in Position

Another pitfall is not considering that 1 or n might already be in their target positions. While the formula handles this mathematically (returning 0 swaps for that element), explicitly checking can prevent confusion:

Example scenarios:

  • If 1 is already at index 0: position_of_one = 0, contributing 0 swaps
  • If n is already at index n-1: position_of_n = n-1, contributing 0 swaps
  • If both are in position: the function should return 0

Solution: The current formula naturally handles these cases, but adding early return conditions can improve readability:

# Optional optimization for clarity
if position_of_one == 0 and position_of_n == n - 1:
    return 0

3. Misunderstanding Adjacent Swaps

Some might think they can directly swap non-adjacent elements or calculate Manhattan distance. Remember that only adjacent elements can be swapped, which is why we count the number of positions an element must move.

Wrong interpretation: Thinking you can swap 1 directly with any element to place it at index 0.

Correct understanding: Element 1 must be swapped with each adjacent element step by step until it reaches index 0, requiring exactly position_of_one swaps.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What are the two properties the problem needs to have for dynamic programming to be applicable? (Select 2)


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More