Backtracking · 13 of 19
Find Maximum number possible by doing at-most K swaps
Swap the digits of a number at most k times to make it as big as possible, trying every copy of the biggest digit when there is a tie.
- Simple way
- time O(n^(2k) * n)
room O(n) - Clever way
- time O(n^k * n)
room O(n)
The problem, simply
You get a number as a string of digits, and a number k. One swap means picking two digits and trading their places. You may do at most k swaps.
Give back the biggest number you can make.
A story to remember it
Picture it
The digits of 27177 in a row. The biggest digit, 7, has three copies. Which one we bring to the front changes how the rest of the number can be fixed.
- looking now
- checking
Examples
One swap
- In
num = '254', k = 1- Out
524
Bring the 5 to the front.
Enough swaps to sort
- In
num = '1234567', k = 4- Out
7654321
Three swaps already sort it from big to small: 1 with 7, 2 with 6, 3 with 5.
Twins matter
- In
num = '8799', k = 2- Out
9987
Swap the 8 with the first 9 to get 9789, then the 7 with the last 9 to get 9987. Grabbing the last 9 first gives only 9978.
Already the biggest
- In
num = '9876', k = 2- Out
9876
No swap can help. Do not swap just because you are allowed to.
Two swaps on 1234
- In
num = '1234', k = 2- Out
4321
1 with 4 gives 4231, then 2 with 3 gives 4321.
Way 1: the simple way
Try every pair of spots as the next swap, but only when it makes the left digit bigger. Go k levels deep, and remember the biggest number seen anywhere.
- Compare the current number with the best and keep the bigger one.
- If no swaps are left, stop.
- For every pair
i < jwithnum[i] < num[j]: swap, go one level deeper withk - 1, then swap back.
class Solution {
private String best;
public String findMaximumNum(String num, int k) {
best = num;
tryAll(num.toCharArray(), k);
return best;
}
private void tryAll(char[] num, int k) {
String now = new String(num);
if (now.compareTo(best) > 0) best = now; // same length, so text order is number order
if (k == 0) return;
for (int i = 0; i < num.length - 1; i++) {
for (int j = i + 1; j < num.length; j++) {
if (num[i] < num[j]) { // only swaps that grow the left spot
swap(num, i, j);
tryAll(num, k - 1);
swap(num, i, j); // undo
}
}
}
}
private void swap(char[] a, int i, int j) {
char t = a[i]; a[i] = a[j]; a[j] = t;
}
}How long it takes: each level tries up to n * n / 2 pairs and we go k levels deep, and each number is copied in O(n). That is O(n^(2k) * n). Extra room: the call stack is k deep and we hold a copy of the number, O(n).
Way 2: the clever way
A smart swap always puts the biggest possible digit as far left as possible. So walk the spots from left to right.
- At spot
idx, find the biggest digit to its right. - If the digit at
idxis already that big, move toidx + 1. This costs no swap. - Otherwise, the biggest digit may appear more than once. Try swapping with each copy. For each one: swap, update the best, go to
idx + 1withk - 1, swap back. - Stop when no swaps are left or the spots run out.
Why try every copy? The copy you pick decides which small digit gets sent where, and that changes what later swaps can fix.
Try every copy of the biggest digit
01 / 15
Start with 27177 and 2 swaps. The best number so far is the number itself.
- best so far
- 27177
- looking now
- checking
- seen
- done
- answer
- rejected
- path
- just added
class Solution {
private String best;
public String findMaximumNum(String num, int k) {
best = num;
solve(num.toCharArray(), k, 0);
return best;
}
private void solve(char[] num, int k, int idx) {
if (k == 0 || idx == num.length) return;
char max = num[idx]; // biggest digit from idx to the end
for (int i = idx + 1; i < num.length; i++) {
if (num[i] > max) max = num[i];
}
if (max == num[idx]) { // already the biggest, move on for free
solve(num, k, idx + 1);
return;
}
for (int i = idx + 1; i < num.length; i++) {
if (num[i] == max) { // try every copy of the biggest digit
swap(num, idx, i);
String now = new String(num);
if (now.compareTo(best) > 0) best = now;
solve(num, k - 1, idx + 1);
swap(num, idx, i); // undo
}
}
}
private void swap(char[] a, int i, int j) {
char t = a[i]; a[i] = a[j]; a[j] = t;
}
}How long it takes: each swap level branches into at most n copies, there are at most k swap levels, and each step scans and copies the number in O(n). That is O(n^k * n), and usually far less, because most digits have only one copy. Extra room: O(n) for the call stack and the copy.
Watch out for
- Spending a swap when the digit is already the biggest. Move on for free, or you waste swaps.
- Comparing numbers with
Integer.parseInt. The number may be too long. Since all strings have the same length,compareTogives the right order. - Updating the best only at the end of a branch. Update it after every swap, because fewer than
kswaps may already give the best.
What interviewers ask next
- Only one swap allowed (LeetCode 670). Then the rightmost copy is right: for each digit, remember the last spot where it appears, and find the first spot that has a bigger digit later. That is
O(n). - Exactly
kswaps, not at most. Leftover swaps can be wasted in pairs: swap two digits, then swap them back. One single leftover swap can be wasted on two equal digits, if the number has any. If not, run the full search of Way 1 and accept a number only when exactlykswaps were used. - Smallest number instead of biggest. Same idea with the smallest digit, but the first spot cannot get a 0, because that would remove a digit.
- Only swaps of neighbors allowed. Then greedily bring the biggest digit within the next
k + 1spots to the front, paying its distance in swaps.
Remember this
Grown-up words: the spots are indices, trying each copy and undoing it is backtracking, keeping the biggest seen is a global best, and skipping useless swaps is pruning.