Facebook Pixel

1850. Minimum Adjacent Swaps to Reach the Kth Smallest Number

Problem Description

Given a string num representing a large integer and an integer k, you need to find the k-th smallest "wonderful" integer and calculate the minimum number of adjacent swaps needed to transform num into it.

A wonderful integer is defined as any permutation of the digits in num that is greater in value than num. These wonderful integers are ordered from smallest to largest.

For example, with num = "5489355142":

  • The 1st smallest wonderful integer is "5489355214"
  • The 2nd smallest wonderful integer is "5489355241"
  • The 3rd smallest wonderful integer is "5489355412"
  • The 4th smallest wonderful integer is "5489355421"

Your task is to:

  1. Find the k-th smallest wonderful integer (which is the k-th next permutation of num in lexicographical order)
  2. Calculate the minimum number of adjacent digit swaps needed to transform the original num into this k-th wonderful integer

The problem guarantees that the k-th smallest wonderful integer exists for the given inputs.

The solution involves two main steps:

  • Using the next permutation algorithm k times to find the target arrangement
  • Calculating the minimum swaps by counting inversion pairs between the original and target arrangements, handling duplicate digits carefully by greedily assigning indices
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

To find the k-th smallest wonderful integer, we need to understand that wonderful integers are essentially the next permutations of num in lexicographical order. The k-th smallest wonderful integer is simply the result of applying the next permutation operation k times starting from num.

Once we have the target permutation, the key insight is that the minimum number of adjacent swaps needed to transform one arrangement into another equals the number of inversion pairs between them. An inversion pair occurs when an element that should come later appears before an element that should come earlier.

Consider a simpler problem first: if all digits were unique, we could map each digit to its position index. For example, if num = "543" and target is "453", we map positions: 5โ†’0, 4โ†’1, 3โ†’2. Then for the target "453", we get position sequence [1, 0, 2]. The inversions in this sequence (where a larger index appears before a smaller one) tell us exactly how many adjacent swaps are needed.

However, when duplicate digits exist, we need to be careful about which occurrence of a digit maps to which position. The greedy approach works here: for each digit in the target string, we should map it to the earliest available occurrence in the original string. This minimizes the total displacement and thus the number of swaps.

By maintaining arrays that track where each digit appears in the original string and processing the target string from left to right, we can build an array of position mappings. The number of inversion pairs in this array gives us our answer - each inversion represents a pair of elements that are out of order and will eventually need to be swapped.

Learn more about Greedy and Two Pointers patterns.

Solution Approach

The solution consists of two main parts: finding the k-th next permutation and calculating the minimum swaps needed.

Part 1: Finding the k-th Next Permutation

The next_permutation function implements the standard algorithm to find the next lexicographically greater permutation:

  1. Starting from the right, find the first index i where nums[i] < nums[i+1]. This is the "pivot" position where we can make the number larger.
  2. If no such i exists (array is in descending order), there's no next permutation.
  3. Find the rightmost element nums[j] that is greater than nums[i].
  4. Swap nums[i] and nums[j].
  5. Reverse the suffix starting at nums[i+1] to get the smallest possible arrangement after the pivot.

We call this function k times on a list copy of num to get the k-th wonderful integer.

Part 2: Calculating Minimum Swaps via Inversion Pairs

To handle duplicate digits, we use the following approach:

  1. Build position arrays: Create a 2D array d where d[digit] contains all positions where that digit appears in the original num. For example, if num = "5355", then d[5] = [0, 2, 3] and d[3] = [1].

  2. Map target to positions: For each digit in the target string s, assign it to the next available position from the original string:

    • Use an idx array to track which occurrence of each digit we've used
    • For each character c in target string s, get its digit value j
    • Assign position arr[i] = d[j][idx[j]] and increment idx[j]

    This greedy assignment ensures we use positions in order, minimizing total displacement.

  3. Count inversion pairs: The final step counts how many pairs (i, j) exist where i < j but arr[i] > arr[j]:

    sum(arr[j] > arr[i] for i in range(n) for j in range(i))

    Each inversion pair represents elements that are out of order and will need to be swapped.

For example, if num = "5489355142" and we want the 1st wonderful integer "5489355214":

  • The digit mapping would track that the '1' at position 7 in original moves to position 8 in target
  • The '4' at position 8 moves to position 7
  • The '2' at position 9 moves to position 9
  • This creates one inversion pair, requiring 1 swap

The time complexity is O(k * n + n^2) where n is the length of the string - k iterations for finding the permutation and n^2 for counting inversions.

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 a concrete example with num = "2431" and k = 2.

Step 1: Find the 2nd wonderful integer

Starting with num = "2431", we apply the next permutation algorithm twice:

First permutation:

  • Find pivot: From right, find first i where nums[i] < nums[i+1]. Here, i = 1 (since 4 < 3 is false, but 2 < 4 is true)
  • Find swap candidate: Find rightmost j where nums[j] > nums[i]. Here, j = 3 (1 > 2 is false, 3 > 2 is true)
  • Swap positions 1 and 3: "2431" โ†’ "3421"
  • Reverse suffix from position 2: "3421" โ†’ "3412" (reversing "21" gives "12")

Second permutation on "3412":

  • Find pivot: i = 2 (since 1 < 2 is true)
  • Find swap candidate: j = 3 (2 > 1 is true)
  • Swap positions 2 and 3: "3412" โ†’ "3421"
  • Reverse suffix from position 3: "3421" โ†’ "3421" (single element, no change)

So the 2nd wonderful integer is "3421".

Step 2: Calculate minimum swaps from "2431" to "3421"

Build position arrays for original num = "2431":

  • d[2] = [0] (digit 2 appears at position 0)
  • d[4] = [1] (digit 4 appears at position 1)
  • d[3] = [2] (digit 3 appears at position 2)
  • d[1] = [3] (digit 1 appears at position 3)

Map target "3421" to positions:

  • Character '3' at target position 0 โ†’ use d[3][0] = 2, so arr[0] = 2
  • Character '4' at target position 1 โ†’ use d[4][0] = 1, so arr[1] = 1
  • Character '2' at target position 2 โ†’ use d[2][0] = 0, so arr[2] = 0
  • Character '1' at target position 3 โ†’ use d[1][0] = 3, so arr[3] = 3

Result: arr = [2, 1, 0, 3]

Count inversion pairs in arr = [2, 1, 0, 3]:

  • Position 0 vs 1: 2 > 1 โœ“ (1 inversion)
  • Position 0 vs 2: 2 > 0 โœ“ (1 inversion)
  • Position 0 vs 3: 2 > 3 โœ—
  • Position 1 vs 2: 1 > 0 โœ“ (1 inversion)
  • Position 1 vs 3: 1 > 3 โœ—
  • Position 2 vs 3: 0 > 3 โœ—

Total inversions = 3

Therefore, transforming "2431" to "3421" requires 3 adjacent swaps.

We can verify this:

  • "2431" โ†’ "2341" (swap positions 2,3)
  • "2341" โ†’ "3241" (swap positions 0,1)
  • "3241" โ†’ "3421" (swap positions 1,2)

Solution Implementation

1class Solution:
2    def getMinSwaps(self, num: str, k: int) -> int:
3        """
4        Find minimum adjacent swaps to transform num to its k-th next permutation.
5      
6        Args:
7            num: String representation of a number
8            k: Number of times to get next permutation
9          
10        Returns:
11            Minimum number of adjacent swaps needed
12        """
13      
14        def next_permutation(digits: List[str]) -> bool:
15            """
16            Modify digits array to its next lexicographical permutation in-place.
17          
18            Args:
19                digits: List of digit characters
20              
21            Returns:
22                True if next permutation exists, False otherwise
23            """
24            n = len(digits)
25          
26            # Step 1: Find rightmost digit that is smaller than its next digit
27            pivot_index = n - 2
28            while pivot_index >= 0 and digits[pivot_index] >= digits[pivot_index + 1]:
29                pivot_index -= 1
30          
31            # If no such digit exists, this is the last permutation
32            if pivot_index < 0:
33                return False
34          
35            # Step 2: Find rightmost digit greater than pivot
36            swap_index = n - 1
37            while swap_index >= 0 and digits[swap_index] <= digits[pivot_index]:
38                swap_index -= 1
39          
40            # Step 3: Swap pivot with the found digit
41            digits[pivot_index], digits[swap_index] = digits[swap_index], digits[pivot_index]
42          
43            # Step 4: Reverse the suffix after pivot position
44            digits[pivot_index + 1:n] = digits[pivot_index + 1:n][::-1]
45          
46            return True
47      
48        # Convert string to list of characters for manipulation
49        target_digits = list(num)
50      
51        # Apply next_permutation k times to get the target configuration
52        for _ in range(k):
53            next_permutation(target_digits)
54      
55        # Create buckets to store positions of each digit (0-9) in original string
56        digit_positions = [[] for _ in range(10)]
57        position_indices = [0] * 10  # Track which position to use next for each digit
58      
59        # Populate position buckets for each digit in original string
60        for position, char in enumerate(num):
61            digit_value = ord(char) - ord('0')
62            digit_positions[digit_value].append(position)
63      
64        # Map target digits to their corresponding positions in original string
65        # This creates a permutation array showing where each element should come from
66        permutation_array = [0] * len(num)
67        for target_position, char in enumerate(target_digits):
68            digit_value = ord(char) - ord('0')
69            # Get next available position for this digit from original string
70            original_position = digit_positions[digit_value][position_indices[digit_value]]
71            permutation_array[target_position] = original_position
72            position_indices[digit_value] += 1
73      
74        # Count inversions in the permutation array
75        # Each inversion represents a required adjacent swap
76        inversion_count = 0
77        for i in range(len(num)):
78            for j in range(i):
79                if permutation_array[j] > permutation_array[i]:
80                    inversion_count += 1
81      
82        return inversion_count
83
1class Solution {
2    /**
3     * Find minimum number of adjacent swaps to transform original string 
4     * to its k-th next permutation
5     * @param num Original number as string
6     * @param k Number of permutations to find
7     * @return Minimum number of adjacent swaps needed
8     */
9    public int getMinSwaps(String num, int k) {
10        // Convert string to char array for in-place permutation
11        char[] permutedArray = num.toCharArray();
12      
13        // Generate the k-th next permutation
14        for (int i = 0; i < k; ++i) {
15            nextPermutation(permutedArray);
16        }
17      
18        // Create buckets to store positions of each digit (0-9) in original string
19        List<Integer>[] digitPositions = new List[10];
20        Arrays.setAll(digitPositions, i -> new ArrayList<>());
21      
22        int n = permutedArray.length;
23      
24        // Record positions of each digit in the original string
25        for (int i = 0; i < n; ++i) {
26            int digit = num.charAt(i) - '0';
27            digitPositions[digit].add(i);
28        }
29      
30        // Track which occurrence of each digit we're using
31        int[] digitOccurrenceIndex = new int[10];
32      
33        // Build mapping array: for each position in permuted string,
34        // find corresponding position in original string
35        int[] originalPositions = new int[n];
36        for (int i = 0; i < n; ++i) {
37            int digit = permutedArray[i] - '0';
38            // Get the next available position of this digit from original string
39            originalPositions[i] = digitPositions[digit].get(digitOccurrenceIndex[digit]++);
40        }
41      
42        // Count inversions in the mapping array
43        // Each inversion represents a required swap
44        int swapCount = 0;
45        for (int i = 0; i < n; ++i) {
46            for (int j = 0; j < i; ++j) {
47                // If element at position j should come after element at position i
48                if (originalPositions[j] > originalPositions[i]) {
49                    ++swapCount;
50                }
51            }
52        }
53      
54        return swapCount;
55    }
56
57    /**
58     * Generate next lexicographic permutation in-place
59     * @param nums Character array to permute
60     * @return true if next permutation exists, false otherwise
61     */
62    private boolean nextPermutation(char[] nums) {
63        int n = nums.length;
64      
65        // Find rightmost position i where nums[i] < nums[i+1]
66        int i = n - 2;
67        while (i >= 0 && nums[i] >= nums[i + 1]) {
68            --i;
69        }
70      
71        // If no such position exists, this is the last permutation
72        if (i < 0) {
73            return false;
74        }
75      
76        // Find rightmost element nums[j] that is greater than nums[i]
77        int j = n - 1;
78        while (j >= 0 && nums[i] >= nums[j]) {
79            --j;
80        }
81      
82        // Swap nums[i] with nums[j]
83        swap(nums, i++, j);
84      
85        // Reverse the suffix starting at nums[i+1]
86        for (j = n - 1; i < j; ++i, --j) {
87            swap(nums, i, j);
88        }
89      
90        return true;
91    }
92
93    /**
94     * Swap two elements in character array
95     * @param nums Character array
96     * @param i First index
97     * @param j Second index
98     */
99    private void swap(char[] nums, int i, int j) {
100        char temp = nums[i];
101        nums[i] = nums[j];
102        nums[j] = temp;
103    }
104}
105
1class Solution {
2public:
3    int getMinSwaps(string num, int k) {
4        // Create a copy of the original number string
5        string targetString = num;
6      
7        // Get the k-th next permutation
8        for (int i = 0; i < k; ++i) {
9            next_permutation(targetString.begin(), targetString.end());
10        }
11      
12        // Store positions of each digit in the original number
13        // digitPositions[d] contains all positions where digit d appears
14        vector<int> digitPositions[10];
15        int n = num.size();
16      
17        for (int i = 0; i < n; ++i) {
18            int digit = num[i] - '0';
19            digitPositions[digit].push_back(i);
20        }
21      
22        // Track how many times we've used each digit
23        int digitUsageCount[10] = {0};
24      
25        // Build the position array for the target permutation
26        // positionMapping[i] represents where the i-th character of targetString
27        // should come from in the original string
28        vector<int> positionMapping(n);
29      
30        for (int i = 0; i < n; ++i) {
31            int digit = targetString[i] - '0';
32            positionMapping[i] = digitPositions[digit][digitUsageCount[digit]++];
33        }
34      
35        // Count inversions in the position mapping
36        // Each inversion represents a required swap
37        int swapCount = 0;
38        for (int i = 0; i < n; ++i) {
39            for (int j = 0; j < i; ++j) {
40                if (positionMapping[j] > positionMapping[i]) {
41                    ++swapCount;
42                }
43            }
44        }
45      
46        return swapCount;
47    }
48};
49
1/**
2 * Calculates the minimum number of adjacent swaps needed to transform 
3 * the original number into its k-th next permutation
4 * @param num - The original number as a string
5 * @param k - The number of permutations to advance
6 * @returns The minimum number of adjacent swaps required
7 */
8function getMinSwaps(num: string, k: number): number {
9    const length = num.length;
10    const digits = num.split('');
11  
12    // Generate the k-th next permutation
13    for (let i = 0; i < k; i++) {
14        nextPermutation(digits);
15    }
16  
17    // Create a mapping of each digit to its positions in the original number
18    // digitPositions[digit] contains all indices where 'digit' appears in num
19    const digitPositions: number[][] = Array.from({ length: 10 }, () => []);
20    for (let i = 0; i < length; i++) {
21        digitPositions[+num[i]].push(i);
22    }
23  
24    // Track which occurrence of each digit we're currently using
25    const digitOccurrenceIndex: number[] = Array(10).fill(0);
26  
27    // Build the target position array
28    // For each digit in the k-th permutation, find its corresponding position in the original
29    const targetPositions: number[] = [];
30    for (let i = 0; i < length; i++) {
31        const currentDigit = +digits[i];
32        const occurrenceCount = digitOccurrenceIndex[currentDigit];
33        targetPositions.push(digitPositions[currentDigit][occurrenceCount]);
34        digitOccurrenceIndex[currentDigit]++;
35    }
36  
37    // Count inversions in the target position array
38    // An inversion occurs when a larger index appears before a smaller one
39    // The number of inversions equals the minimum adjacent swaps needed
40    let swapCount = 0;
41    for (let i = 0; i < length; i++) {
42        for (let j = 0; j < i; j++) {
43            if (targetPositions[j] > targetPositions[i]) {
44                swapCount++;
45            }
46        }
47    }
48  
49    return swapCount;
50}
51
52/**
53 * Transforms an array of digits into its next lexicographical permutation
54 * @param nums - Array of digit characters to be permuted in-place
55 * @returns true if a next permutation exists, false otherwise
56 */
57function nextPermutation(nums: string[]): boolean {
58    const length = nums.length;
59  
60    // Find the rightmost position where nums[i] < nums[i + 1]
61    let pivotIndex = length - 2;
62    while (pivotIndex >= 0 && nums[pivotIndex] >= nums[pivotIndex + 1]) {
63        pivotIndex--;
64    }
65  
66    // If no such position exists, we're at the last permutation
67    if (pivotIndex < 0) {
68        return false;
69    }
70  
71    // Find the rightmost element greater than the pivot
72    let swapIndex = length - 1;
73    while (swapIndex >= 0 && nums[pivotIndex] >= nums[swapIndex]) {
74        swapIndex--;
75    }
76  
77    // Swap the pivot with the found element
78    [nums[pivotIndex], nums[swapIndex]] = [nums[swapIndex], nums[pivotIndex]];
79  
80    // Reverse the suffix after the pivot position to get the next permutation
81    let left = pivotIndex + 1;
82    let right = length - 1;
83    while (left < right) {
84        [nums[left], nums[right]] = [nums[right], nums[left]];
85        left++;
86        right--;
87    }
88  
89    return true;
90}
91

Time and Space Complexity

Time Complexity: O(n ร— (k + n))

The time complexity breaks down as follows:

  • The next_permutation function is called k times in the loop, and each call takes O(n) time (scanning from right to left, swapping, and reversing a portion of the array). This contributes O(k ร— n).
  • Building the position arrays d takes O(n) time as we iterate through the original string once.
  • Creating the arr array takes O(n) time as we iterate through the permuted string s once.
  • The final step calculates inversions using a nested loop: sum(arr[j] > arr[i] for i in range(n) for j in range(i)), which takes O(nยฒ) time.
  • Overall: O(k ร— n) + O(n) + O(n) + O(nยฒ) = O(n ร— k + nยฒ) = O(n ร— (k + n))

Space Complexity: O(n)

The space complexity breaks down as follows:

  • The list s stores a copy of the input string: O(n)
  • The 2D list d stores positions of digits. Since there are at most n positions total distributed across 10 digit buckets: O(n)
  • The idx array has fixed size 10: O(1)
  • The arr array stores n elements: O(n)
  • Overall: O(n) + O(n) + O(1) + O(n) = O(n)

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

Common Pitfalls

1. Incorrect Handling of Duplicate Digits

Pitfall: A naive approach might try to directly map characters from the original string to the target string without considering that duplicate digits need special handling. For example, if num = "5355" and target = "5535", simply finding where each '5' should go without tracking which '5' is which will lead to incorrect swap calculations.

Why it happens: When multiple identical digits exist, we need to decide which specific occurrence in the original maps to which occurrence in the target. An arbitrary or incorrect mapping can drastically increase the number of required swaps.

Solution: Use a greedy assignment strategy with position tracking:

  • Maintain separate position lists for each digit (0-9)
  • When building the permutation array, assign digits to positions in order (first occurrence to first occurrence, etc.)
  • This ensures minimal total displacement and correct inversion counting

2. Modifying the Original String During Permutation Generation

Pitfall: Directly applying next_permutation on the original string or forgetting to create a copy:

# WRONG: This modifies num directly
digits = num  # This is just a reference!
for _ in range(k):
    next_permutation(digits)

# CORRECT: Create a mutable copy
target_digits = list(num)
for _ in range(k):
    next_permutation(target_digits)

Why it happens: Strings in Python are immutable, but if you accidentally work with references or forget to create a proper copy, you might lose the original configuration needed for swap calculation.

Solution: Always create a list copy of the string before applying permutation operations: target_digits = list(num)

3. Off-by-One Errors in Next Permutation Algorithm

Pitfall: Incorrectly implementing the boundary checks in the next_permutation function, particularly:

  • Starting the pivot search from n-1 instead of n-2
  • Using wrong comparison operators (< vs <=, > vs >=)
  • Incorrect slice indices when reversing the suffix

Example of wrong implementation:

# WRONG: Starting from wrong index
pivot_index = n - 1  # Should be n - 2
while pivot_index >= 0 and digits[pivot_index] >= digits[pivot_index + 1]:
    pivot_index -= 1

Solution: Carefully implement the standard next permutation algorithm:

  • Start pivot search from n-2 (second-to-last element)
  • Use >= when finding the pivot (not >)
  • Use <= when finding the swap element (not <)
  • Reverse from pivot_index + 1 to end

4. Inefficient Inversion Counting

Pitfall: Using inefficient algorithms for counting inversions, especially for large strings. While the O(nยฒ) approach works, for very large inputs it might be slow.

Alternative Solution (for optimization): Use merge sort or a Binary Indexed Tree (Fenwick Tree) to count inversions in O(n log n) time:

def count_inversions_optimized(arr):
    """Count inversions using merge sort approach - O(n log n)"""
    def merge_and_count(arr, temp, left, mid, right):
        i, j, k = left, mid + 1, left
        inv_count = 0
      
        while i <= mid and j <= right:
            if arr[i] <= arr[j]:
                temp[k] = arr[i]
                i += 1
            else:
                temp[k] = arr[j]
                inv_count += (mid - i + 1)
                j += 1
            k += 1
      
        while i <= mid:
            temp[k] = arr[i]
            i += 1
            k += 1
      
        while j <= right:
            temp[k] = arr[j]
            j += 1
            k += 1
      
        for i in range(left, right + 1):
            arr[i] = temp[i]
      
        return inv_count
  
    def merge_sort_and_count(arr, temp, left, right):
        inv_count = 0
        if left < right:
            mid = (left + right) // 2
            inv_count += merge_sort_and_count(arr, temp, left, mid)
            inv_count += merge_sort_and_count(arr, temp, mid + 1, right)
            inv_count += merge_and_count(arr, temp, left, mid, right)
        return inv_count
  
    n = len(arr)
    temp = [0] * n
    arr_copy = arr.copy()
    return merge_sort_and_count(arr_copy, temp, 0, n - 1)

This optimization becomes crucial when dealing with strings of length 10,000+ where the O(nยฒ) approach might timeout.

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

A heap is a ...?


Recommended Readings

Want a Structured Path to Master System Design Too? Donโ€™t Miss This!

Load More