Minimum Possible Integer After at Most K Adjacent Swaps On Digits
HardPrompt
You are given a string num representing the digits of a very large integer and an integer k. You are allowed to swap any two adjacent digits of the integer at most k times.
Return the minimum integer you can obtain also as a string.
Example 1:
Input: num = "4321", k = 4
Output: "1342"
Explanation: The steps to obtain the minimum integer from 4321 with 4 adjacent swaps are shown.Example 2:
Input: num = "100", k = 1
Output: "010"
Explanation: It's ok for the output to have leading zeros, but the input is guaranteed not to have any leading zeros.Example 3:
Input: num = "36789", k = 1000
Output: "36789"
Explanation: We can keep the number without any swaps.
Constraints:
1 <= num.length <= 3 * 104numconsists of only digits and does not contain leading zeros.1 <= k <= 109
Approaches
2 approaches with complexity analysis and trade-offs.
This approach directly simulates the greedy strategy in a straightforward manner. The core idea is that to form the smallest possible number, we should try to place the smallest possible digit at the most significant position (the leftmost). We repeat this process for each subsequent position.
At each step i (from left to right), we look ahead in the string for the smallest digit that we can afford to move to position i with our remaining swaps k. A digit at index j (where j > i) requires j-i adjacent swaps to be moved to position i. Therefore, we only need to search up to k positions ahead. After finding the best digit, we perform the swaps, update k, and move to the next position.
Algorithm
- Convert the input string
numto a mutable representation, like aStringBuilderor anArrayList<Character>. - Iterate from
i = 0ton-1, wherenis the length of the number. This loop determines the digit to be placed at indexiof the result. - Inside the loop, find the smallest digit and its index
best_jwithin the search window[i, min(n-1, i + k)]of the current state of the string. - The search window is limited to
i+kbecause moving a digit from an indexj > i+kwould require more thankswaps. - Once the smallest digit
dat indexbest_jis found, move it to positioni. This is done by removing the character frombest_jand inserting it ati. - The number of swaps required for this move is
best_j - i. Updatekby subtracting this cost. - If
kbecomes 0, no more swaps are possible. The loop will continue, but since the search window[i, i+k]will just be[i, i], it will always pick the digit at the current positioni. - After the loop finishes, the mutable string contains the minimum possible integer. Convert it back to a string and return.
Walkthrough
We can use a mutable string representation, like Java's StringBuilder, to facilitate the operations. The algorithm proceeds as follows:
For each position i from 0 to n-1:
- We scan a portion of the string starting from
ito find the smallest digit. The range of this scan is[i, min(n-1, i + k)]. We find the indexbest_jof the first occurrence of the smallest digit in this range. - We then move this character from
best_jtoi. With aStringBuilder, this involves deleting the character atbest_jand inserting it ati. - The cost of this operation is
best_j - iswaps. We subtract this cost fromk. - We continue this process until we have processed all
npositions or run out of swaps (k=0).
For example, with num = "4321" and k = 4:
- i=0: Search in
"4321"(range[0, 4]). Smallest is '1' at index 3. Cost = 3-0=3. Move '1' to front.numbecomes"1432",kbecomes4-3=1. - i=1: Search in
"432"(range[1, 1+1=2]). Smallest is '3' at index 2. Cost = 2-1=1. Move '3' to index 1.numbecomes"1342",kbecomes1-1=0. - i=2, k=0: No more swaps. The rest of the string remains as is. The final result is
"1342".
class Solution { public String minInteger(String num, int k) { StringBuilder sb = new StringBuilder(num); int n = num.length(); for (int i = 0; i < n && k > 0; i++) { int bestIdx = i; // Find the smallest digit within reach for (int j = i + 1; j < n && j - i <= k; j++) { if (sb.charAt(j) < sb.charAt(bestIdx)) { bestIdx = j; } } if (bestIdx != i) { char c = sb.charAt(bestIdx); sb.deleteCharAt(bestIdx); sb.insert(i, c); k -= (bestIdx - i); } } return sb.toString(); }}Complexity
Time
O(n^2), where n is the length of the input string. The outer loop runs `n` times. In each iteration, we might scan up to `n` characters and perform a string modification (delete + insert), which takes O(n) time.
Space
O(n), where n is the length of the input string. This is for storing the mutable string (e.g., `StringBuilder`).
Trade-offs
Pros
Simple to understand and implement.
Correctly follows the greedy logic.
Cons
The time complexity of O(n^2) is too slow for the given constraints (n up to 3 * 10^4), and will likely result in a 'Time Limit Exceeded' error.
This approach uses the same greedy strategy as the naive solution but optimizes the key operations. The bottleneck in the naive approach is repeatedly scanning the string to find the best digit and calculating the cost of moving it. The indices of digits change after every move, making it hard to track.
To optimize, we can work with the original indices of the digits, which are static. The cost to move a digit from its original index p to the current front is the number of un-used digits currently before it. We can use a Fenwick Tree (also known as a Binary Indexed Tree, or BIT) to efficiently query this count. The BIT allows us to find the number of digits that have already been moved from indices smaller than p in O(log n) time.
Algorithm
- First, pre-process the input string
numto store the original indices of each digit ('0' through '9'). AList<Queue<Integer>>is suitable, wherepositions.get(d)gives a queue of indices for digitd. - Initialize a Fenwick Tree (BIT) of size
nto all zeros. This BIT will be used to track which original indices have already been used. - Initialize an empty
StringBuilderto build the result. - Iterate from
i = 0ton-1to construct the result string character by character. - In each iteration
i, iterate through the digitsdfrom '0' to '9'.- For each digit
d, check if there are any available occurrences by looking at its corresponding queue. - If so, get its first available original index,
p = positions.get(d).peek(). - Calculate the cost to move this digit to the current position. The cost is its relative position among the remaining digits, which is
p - (number of digits from original indices < p that have already been moved). - The number of moved digits before
pcan be found efficiently using the BIT:moved_count = BIT.query(p-1). - The cost is therefore
p - moved_count. - If
cost <= k, this is the best digit to choose for the current positioni. Append it to the result, updatek -= cost, remove the indexpfrom the queue, and mark indexpas used in the BIT by callingBIT.update(p, 1). - Break the inner loop (over digits) and proceed to the next position
i+1.
- For each digit
- After the main loop, return the constructed string.
Walkthrough
The algorithm is as follows:
- Preprocessing: Create a data structure (e.g., an array of queues) to store the original 0-based indices of all occurrences of each digit from '0' to '9'.
- Data Structure: Initialize a Fenwick Tree of size
n. We will use it to keep track of digits that have been moved to the result. When we move a digit from original indexp, we'll perform an updateft.update(p, 1). - Greedy Construction: We build the result string from left to right. For each position
iin the result: a. We search for the best digit to place. We iterate through digitsdfrom '0' to '9'. b. For eachd, we get its earliest original indexpfrom our preprocessed queues. c. We calculate the number of swaps needed. This is the number of digits that were originally beforepbut have not yet been moved. This is equal top - (number of moved digits with index < p). The second term is computed with a BIT query:ft.query(p-1). d. Let the cost bec = p - ft.query(p-1). Ifc <= k, we can afford this move. Since we are iterating from '0' to '9', the first one we can afford is the best one. e. We append this digitdto our result, subtractcfromk, removepfrom the queue ford, and update the BIT to markpas used. f. We then break and move to the next positioni+1.
This process ensures that at each step, we make the locally optimal choice, which leads to the globally optimal solution, and we do so efficiently.
class FenwickTree { private int[] bit; private int n; public FenwickTree(int size) { this.n = size; this.bit = new int[n + 1]; } // Update value at 0-based index public void update(int index, int delta) { index++; // Convert to 1-based index while (index <= n) { bit[index] += delta; index += index & -index; } } // Query sum up to 0-based index public int query(int index) { if (index < 0) return 0; index++; // Convert to 1-based index int sum = 0; while (index > 0) { sum += bit[index]; index -= index & -index; } return sum; }} class Solution { public String minInteger(String num, int k) { int n = num.length(); List<Queue<Integer>> positions = new ArrayList<>(); for (int i = 0; i < 10; i++) { positions.add(new LinkedList<>()); } for (int i = 0; i < n; i++) { positions.get(num.charAt(i) - '0').add(i); } StringBuilder result = new StringBuilder(); FenwickTree ft = new FenwickTree(n); for (int i = 0; i < n; i++) { for (int d = 0; d < 10; d++) { if (!positions.get(d).isEmpty()) { int originalIndex = positions.get(d).peek(); int movedCount = ft.query(originalIndex - 1); int cost = originalIndex - movedCount; if (cost <= k) { k -= cost; result.append((char)('0' + d)); positions.get(d).poll(); ft.update(originalIndex, 1); break; } } } } return result.toString(); }}Complexity
Time
O(n log n). The main loop runs `n` times to build the result string. The inner loop over 10 digits is a constant factor. Inside, the Fenwick Tree operations (query and update) take O(log n) time. Thus, the total time is dominated by n * log(n).
Space
O(n), where n is the length of the input string. O(n) is used for the queues to store indices and another O(n) for the Fenwick Tree.
Trade-offs
Pros
Highly efficient with O(n log n) time complexity, which passes the given constraints.
Optimal solution for this problem structure.
Cons
Requires knowledge of advanced data structures like Fenwick Trees or Segment Trees.
The implementation is more complex than the naive approach.
Solutions
Solution
class Solution {public String minInteger(String num, int k) { Queue<Integer>[] pos = new Queue[10]; for (int i = 0; i < 10; ++i) { pos[i] = new ArrayDeque<>(); } int n = num.length(); for (int i = 0; i < n; ++i) { pos[num.charAt(i) - '0'].offer(i + 1); } StringBuilder ans = new StringBuilder(); BinaryIndexedTree tree = new BinaryIndexedTree(n); for (int i = 1; i <= n; ++i) { for (int v = 0; v < 10; ++v) { if (!pos[v].isEmpty()) { Queue<Integer> q = pos[v]; int j = q.peek(); int dist = tree.query(n) - tree.query(j) + j - i; if (dist <= k) { k -= dist; q.poll(); ans.append(v); tree.update(j, 1); break; } } } } return ans.toString(); }} class BinaryIndexedTree {private int n;private int[] c;public BinaryIndexedTree(int n) { this.n = n; c = new int[n + 1]; }public void update(int x, int delta) { while (x <= n) { c[x] += delta; x += lowbit(x); } }public int query(int x) { int s = 0; while (x > 0) { s += c[x]; x -= lowbit(x); } return s; }public static int lowbit(int x) { return x & -x; }}class BinaryIndexedTree { public: int n ; vector < int > c ; BinaryIndexedTree ( int _n ) : n ( _n ) , c ( _n + 1 ) {} void update ( int x , int delta ) { while ( x <= n ) { c [ x ] += delta ; x += lowbit ( x ); } } int query ( int x ) { int s = 0 ; while ( x > 0 ) { s += c [ x ]; x -= lowbit ( x ); } return s ; } int lowbit ( int x ) { return x & - x ; } }; class Solution { public: string minInteger ( string num , int k ) { vector < queue < int >> pos ( 10 ); int n = num . size (); for ( int i = 0 ; i < n ; ++ i ) pos [ num [ i ] - '0' ]. push ( i + 1 ); BinaryIndexedTree * tree = new BinaryIndexedTree ( n ); string ans = "" ; for ( int i = 1 ; i <= n ; ++ i ) { for ( int v = 0 ; v < 10 ; ++ v ) { auto & q = pos [ v ]; if ( ! q . empty ()) { int j = q . front (); int dist = tree -> query ( n ) - tree -> query ( j ) + j - i ; if ( dist <= k ) { k -= dist ; q . pop (); ans += ( v + '0' ); tree -> update ( j , 1 ); break ; } } } } return ans ; } };class BinaryIndexedTree : def __init__ ( self , n ): self . n = n self . c = [ 0 ] * ( n + 1 ) @ staticmethod def lowbit ( x ): return x & - x def update ( self , x , delta ): while x <= self . n : self . c [ x ] += delta x += BinaryIndexedTree . lowbit ( x ) def query ( self , x ): s = 0 while x : s += self . c [ x ] x -= BinaryIndexedTree . lowbit ( x ) return s class Solution : def minInteger ( self , num : str , k : int ) -> str : pos = defaultdict ( deque ) for i , v in enumerate ( num , 1 ): pos [ int ( v )]. append ( i ) ans = [] n = len ( num ) tree = BinaryIndexedTree ( n ) for i in range ( 1 , n + 1 ): for v in range ( 10 ): q = pos [ v ] if q : j = q [ 0 ] dist = tree . query ( n ) - tree . query ( j ) + j - i if dist <= k : k -= dist q . popleft () ans . append ( str ( v )) tree . update ( j , 1 ) break return '' . join ( ans )Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.