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:
- Find the k-th smallest wonderful integer (which is the k-th next permutation of
num
in lexicographical order) - 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
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:
- Starting from the right, find the first index
i
wherenums[i] < nums[i+1]
. This is the "pivot" position where we can make the number larger. - If no such
i
exists (array is in descending order), there's no next permutation. - Find the rightmost element
nums[j]
that is greater thannums[i]
. - Swap
nums[i]
andnums[j]
. - 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:
-
Build position arrays: Create a 2D array
d
whered[digit]
contains all positions where that digit appears in the originalnum
. For example, ifnum = "5355"
, thend[5] = [0, 2, 3]
andd[3] = [1]
. -
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 strings
, get its digit valuej
- Assign position
arr[i] = d[j][idx[j]]
and incrementidx[j]
This greedy assignment ensures we use positions in order, minimizing total displacement.
- Use an
-
Count inversion pairs: The final step counts how many pairs
(i, j)
exist wherei < j
butarr[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 EvaluatorExample 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
wherenums[i] < nums[i+1]
. Here,i = 1
(since 4 < 3 is false, but 2 < 4 is true) - Find swap candidate: Find rightmost
j
wherenums[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
, soarr[0] = 2
- Character '4' at target position 1 โ use
d[4][0] = 1
, soarr[1] = 1
- Character '2' at target position 2 โ use
d[2][0] = 0
, soarr[2] = 0
- Character '1' at target position 3 โ use
d[1][0] = 3
, soarr[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 calledk
times in the loop, and each call takesO(n)
time (scanning from right to left, swapping, and reversing a portion of the array). This contributesO(k ร n)
. - Building the position arrays
d
takesO(n)
time as we iterate through the original string once. - Creating the
arr
array takesO(n)
time as we iterate through the permuted strings
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 takesO(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 mostn
positions total distributed across 10 digit buckets:O(n)
- The
idx
array has fixed size 10:O(1)
- The
arr
array storesn
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 ofn-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.
A heap is a ...?
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
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
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!