Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
You have a permutation of 1..n and at most k swaps; produce the lexicographically largest result. Greedily walk positions left to right: position i should hold the value n − i (the largest value not yet locked). If it already does, move on; otherwise swap in that value from wherever it currently sits, using a value→index map for O(1) lookup, and spend one swap. Stop when k runs out. O(n) time and space.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you see that the largest permutation fills positions left to right with n, n−1, n−2, …, and whether you keep a value→index map so each swap is O(1) rather than scanning.
Where candidates slip
Linear-scanning for the desired value each step (making it O(n·k) or worse), or forgetting to update the map after a swap so later lookups point to stale indices.
How to stand out
Justify greedy with an exchange argument: a swap that raises an earlier position outranks any number of swaps improving later positions, because earlier positions are more significant.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewPermutation of 1..n, budget kk > 0 and positions remain?YesNoExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Brute Force — Search All Reachable Permutations
1Hint 1Explore hintHide hint
With at most k swaps, the reachable set is every permutation within k swaps of the start. You could BFS/DFS that space and keep the largest.
2Hint 2Explore hintHide hint
Each state branches into C(n,2) ≈ n²/2 possible swaps; depth up to k. The tree has on the order of (n²)^k leaves.
3Hint 3Explore hintHide hint
Even n = 100 and k = 5 is astronomically large. It only confirms what greedy gives directly.
A · Core idea
Define the search space as all permutations reachable with at most k swaps from the input. Explore it (DFS/BFS), comparing each reachable permutation and keeping the lexicographically largest.
Why it's correct. Every sequence achievable within the budget is generated, so the maximum among them is the answer.
Why it's hopeless. Each step can swap any pair — about n²/2 choices — and we may take up to k steps, giving an exponential (n²/2)^k tree. For the given constraints this is impossible.
The structure it reveals. Compare any two reachable permutations: whichever has a larger value at the first differing position wins, regardless of everything after. So spending a swap to raise an earlier position always beats spending it later. That single fact says: process positions left to right, and at each one place the largest value you can — no search needed.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
arr = [2, 1, 3], k = 1swap (0,2)
[3,1,2]
best = [3,1,2][3, 1, 2]C · Code with teaching comments
Reference implementation previewpublic int[] largestPermBrute(int n, int[] arr, int k) { int[] best = arr.clone(); search(arr.clone(), k, best); return best; }Explore the full codeHide the full code
D · Why it works and complexityO(n^(2k) · ...) timeO(branching) spaceOpen the invariant and proofExplore the proofHide the proof
Lexicographic comparison is dominated by the earliest differing position, so a swap that improves an earlier index is worth more than any swap improving a later one. Greedy left-to-right placement is therefore optimal — the brute-force tree collapses to a single pass.
ComplexityO(n^(2k) · ...) time, O(branching) space — Each of up to k swap levels branches over ~n²/2 swaps; the reachable-permutation tree is roughly (n²/2)^k states. Exponential in k.
E · Edge cases and pitfalls3(n²)^k state explosion — only for tiny n and k.Explore edge casesHide edge cases
Pitfalls
- (n²)^k state explosion — only for tiny n and k.
- Comparing arrays the wrong way (not lexicographically by first difference).
- Re-exploring identical states without memoisation — still exponential.
Approach 2 of 2 · Optimal
Greedy — Place Largest Value Leftmost with a Value→Index Map (Optimal)
1Hint 1Explore hintHide hint
The largest permutation of 1..n is n, n−1, …, 1. So position i (0-indexed) wants the value n − i.
2Hint 2Explore hintHide hint
If position i already holds n − i, it's free — move on. Otherwise you must bring n − i here, which costs one swap.
3Hint 3Explore hintHide hint
To swap in O(1), keep a map from value to its current index. After swapping, update the map for BOTH values that moved, then decrement k.
A · Core idea
The lexicographically largest arrangement of 1..n is the descending sequence n, n−1, …, 1. So at position i (0-indexed) the ideal value is n − i. Walk positions left to right; at each one, if the ideal value isn't already there, swap it in from its current location and spend one swap. Stop when k reaches 0.
Why most-significant-position-first is optimal — the exchange argument. Lexicographic order is decided at the first position where two sequences differ. Suppose an optimal solution does NOT place the largest available value at the leftmost still-improvable position i, but instead uses a swap to improve some later position j > i. Re-route that one swap to instead place n − i at position i. The new sequence is larger at position i than the optimal was (it now holds the max possible value there), and positions 0..i−1 are unchanged — so it is lexicographically ≥ the supposed optimum, using no more swaps. Hence there's always an optimal solution that fixes the leftmost improvable position first; induction gives the full greedy.
Why a value→index map. To place n − i we must know where it currently sits. A map keyed by value gives that in O(1). The naive alternative — scanning for it — is O(n) per position and O(n·k) overall, which is too slow.
Why we update the map after each swap. Swapping arr[i] and arr[j] moves two values. The value n − i now lives at i, and the value previously at i now lives at j. Both map entries must be corrected, or a later lookup will point at a stale index and corrupt the result.
Why skipping correct positions matters. If arr[i] already equals n − i, fixing it would 'cost' a wasted self-swap. We skip it for free, preserving budget for positions that actually need it.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
arr = [4, 2, 3, 5, 1], k = 1Array state
i=0, want 5
arr[0]=4 ≠ 5; 5 at index 3
[5, 2, 3, 4, 1]C · Code with teaching comments
D · Why it works and complexityO(n) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
This is most-significant-digit-first greedy. Because the array is a permutation, the target at each index is known (n − i), so there's no search — just locate-and-swap with an O(1) map. The reusable kernel: 'maximise lexicographically under a move budget' → fix earliest positions first, each move buying the biggest legal improvement.
ComplexityO(n) time, O(n) space — Building the value→index map is O(n); the single left-to-right pass does O(1) work per position (map lookup + swap + two map updates). Total O(n) time, O(n) space for the map.
E · Edge cases and pitfalls6arr = [1,2,3], k = 100 — Position 0 wants 3 (swap with idx2 → [3,2,1]), position 1 wants 2 (already there). Result [3,2,1] using 1 swap; extra budget unused.Explore edge casesHide edge cases
Edge cases handled
arr = [1,2,3], k = 100Position 0 wants 3 (swap with idx2 → [3,2,1]), position 1 wants 2 (already there). Result [3,2,1] using 1 swap; extra budget unused.already largest, e.g. [3,2,1]Every arr[i] == n − i → no swaps spent → returned unchanged.k = 0Loop body never runs → array returned as-is.
Pitfalls
- Linear-scanning for the desired value each step → O(n·k), too slow.
- Forgetting to update BOTH map entries after a swap → stale indices corrupt later placements.
- Spending a swap when arr[i] already equals n − i → wastes budget (guard with the equality check).
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic int[] largestPermutation(int n, int[] arr, long k) { Map<Integer, Integer> pos = new HashMap<>(); for (int i = 0; i < n; i++) pos.put(arr[i], i); for (int i = 0; i < n && k > 0; i++) { int want = n - i;Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Linear scan instead of a value→index map
Searching the array for n − i every step is O(n) per position → O(n·k) overall, which TLEs for large n.
Explore the code comparisonHide the code comparison
Linear scan instead of a value→index map
Searching the array for n − i every step is O(n) per position → O(n·k) overall, which TLEs for large n.
Don't write this
for i in range(n):
want = n - i
if arr[i] != want:
j = arr.index(want) # O(n) scan
arr[i], arr[j] = arr[j], arr[i]
k -= 1
if k == 0: breakWrite this instead
pos = {v: i for i, v in enumerate(arr)}
for i in range(n):
if k == 0: break
want = n - i
if arr[i] != want:
j = pos[want]
moved = arr[i]
arr[i], arr[j] = want, moved
pos[moved] = j; pos[want] = i
k -= 12Stale map after swap
After swapping, both moved values changed index. Updating only one (or neither) makes future lookups point to the wrong slot.
Explore the code comparisonHide the code comparison
Stale map after swap
After swapping, both moved values changed index. Updating only one (or neither) makes future lookups point to the wrong slot.
Don't write this
int j = pos.get(want);
int moved = arr[i];
arr[i] = want; arr[j] = moved;
pos.put(want, i); // forgot to update 'moved'Write this instead
int j = pos.get(want);
int moved = arr[i];
arr[i] = want; arr[j] = moved;
pos.put(moved, j);
pos.put(want, i);3Wasting a swap on an already-correct position
If arr[i] already equals n − i, performing a self-swap decrements k for no gain, possibly leaving a later position unfixed.
Explore the code comparisonHide the code comparison
Wasting a swap on an already-correct position
If arr[i] already equals n − i, performing a self-swap decrements k for no gain, possibly leaving a later position unfixed.
Don't write this
for i in range(n):
if k == 0: break
j = pos[n - i]
arr[i], arr[j] = arr[j], arr[i] # swaps even when i == j
k -= 1Write this instead
for i in range(n):
if k == 0: break
want = n - i
if arr[i] != want:
j = pos[want]
arr[i], arr[j] = arr[j], arr[i]
pos[arr[j]] = j; pos[want] = i
k -= 1Revise
Carry the pattern into the interview
Pattern to remember
Greedy left-to-right placement of the largest remaining value, with a value→index map for O(1) swaps
Code pattern to memorize
pos[v] = index of value v # build once
for i in 0..n-1:
if k == 0: break
want = n - i # largest value that belongs at position i
if arr[i] != want:
j = pos[want]
swap arr[i], arr[j]
pos[arr[j]] = j; pos[want] = i # update the map for both moved values
k -= 1
return arrRules to remember
- 1The input is a permutation of 1..n, so the largest possible value for position i is exactly
n − i(0-indexed positions). - 2Greedy is correct: fixing the most significant position with the biggest available value beats any other use of a swap.
- 3Use a value→index map so you can find where the desired value lives in O(1), then swap it into place.
- 4Each productive placement costs exactly one swap; skip positions already holding the right value (they cost nothing).
- 5Stop as soon as
khits 0 — remaining positions stay as they are.
Use this pattern when
- Maximise (or minimise) a sequence lexicographically under a bounded number of swaps
- Values are a permutation so the target at each position is known in advance
- Most-significant-position-first greedy with O(1) locate-and-swap
- A swap budget caps how many positions you can fix
Choose another pattern when
- Only adjacent swaps allowed → bubble-style / inversion counting, not direct placement
- Values aren't a permutation → the target at each position isn't simply n − i
- You must minimise total swap DISTANCE rather than count → different cost model
- Unlimited swaps → just sort, no greedy budget reasoning needed
Interview takeaway
Lexicographic maximisation under a swap budget is a most-significant-digit-first greedy: place the largest legal value at the leftmost position, each fix costing one swap, and keep a value→index map so locating the swap partner is O(1). The exchange argument proves fixing earlier positions first is optimal.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternMost-significant-position-first greedy under a move budget: since the input is a permutation, each position's ideal value is known (n − i), so place it directly with an O(1) value→index map. The exchange argument — improving an earlier position dominates any later improvement — proves optimality.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Sherlock and the BeastConstructive greedy that also front-loads the largest symbol for the biggest number.
- Maximum SwapComing soonOne swap to maximise a number's digits — same 'biggest digit to the front' greedy.
- Minimum Adjacent Swaps to SortComing soonWhen only adjacent swaps are allowed the cost model changes to inversion counting.
- Jim and the OrdersAnother permutation-of-indices problem solved by ordering on a derived key.