1053. Previous Permutation With One Swap
Problem Description
Given an array of positive integers arr
(not necessarily distinct), you need to find the lexicographically largest permutation that is smaller than arr
by making exactly one swap. If no such permutation exists, return the original array unchanged.
A swap means exchanging the positions of two numbers arr[i]
and arr[j]
in the array.
To understand lexicographical ordering: an array is lexicographically smaller than another if at the first position where they differ, the first array has a smaller element. For example, [3,1,2]
is lexicographically smaller than [3,2,1]
because at index 1, we have 1 < 2
.
The goal is to find the largest possible array that is still smaller than the original by swapping exactly one pair of elements. This means we want to make the smallest possible decrease to the array's lexicographical value.
For example:
- If
arr = [3,2,1]
, we can swap3
and2
to get[2,3,1]
, which is lexicographically smaller than[3,2,1]
- If
arr = [1,1,5]
, no single swap can make it smaller, so we return[1,1,5]
- If
arr = [3,1,1,3]
, we can swap the first3
with the second1
to get[1,1,3,3]
The key challenge is identifying which two elements to swap to achieve the lexicographically largest result among all possible smaller permutations.
Intuition
To make the array lexicographically smaller with exactly one swap, we need to find a position where we can place a smaller number. Think about it from left to right - the leftmost position where we make a change will have the most significant impact on the lexicographical order.
However, we want the largest permutation that is smaller than the original. This means we should make the smallest possible decrease. So we need to:
- Find the rightmost position where we can make a beneficial swap
- At that position, swap with the largest possible smaller value
Let's think about when a swap can make the array smaller. If we have a decreasing sequence like [5,4,3,2,1]
, no swap can make it smaller - it's already the smallest possible arrangement of these numbers. But if we have any position where arr[i-1] > arr[i]
, that means there's a smaller number to the right that we could potentially swap with arr[i-1]
.
So our strategy becomes:
-
Find the swap position: Traverse from right to left to find the first position
i-1
wherearr[i-1] > arr[i]
. This is the rightmost position where the array "increases" when viewed from right to left. This positioni-1
is where we want to place a smaller number. -
Find the best swap candidate: Once we know position
i-1
needs a smaller value, we need to find which number to swap it with. We want the largest number that is still smaller thanarr[i-1]
. We search from the right again to find the rightmost such number (to get the largest possible value among candidates). -
Handle duplicates: If there are duplicate values, we need to be careful. We should pick the rightmost occurrence of our chosen value to ensure we get the lexicographically largest result. That's why we check
arr[j] != arr[j-1]
- to avoid swapping with a duplicate that isn't the rightmost one.
This greedy approach works because making the swap as far right as possible (step 1) and choosing the largest valid replacement (step 2) ensures we get the lexicographically largest array that is smaller than the original.
Learn more about Greedy patterns.
Solution Approach
The implementation follows a greedy approach with two main scanning phases:
Phase 1: Find the position to swap from
We traverse the array from right to left (from index n-1
to 1
):
for i in range(n - 1, 0, -1):
if arr[i - 1] > arr[i]:
When we find the first position where arr[i-1] > arr[i]
, we've identified that arr[i-1]
is the element that needs to be swapped. This is the rightmost position where we can make a beneficial swap to create a smaller permutation.
Phase 2: Find the best element to swap with
Once we've found position i-1
, we need to find the best candidate to swap it with. We scan from the right again (from index n-1
to i-1
):
for j in range(n - 1, i - 1, -1):
if arr[j] < arr[i - 1] and arr[j] != arr[j - 1]:
The conditions here are crucial:
arr[j] < arr[i - 1]
: We need an element smaller thanarr[i-1]
to make the array lexicographically smallerarr[j] != arr[j - 1]
: This ensures we pick the rightmost occurrence of a value when duplicates exist, giving us the lexicographically largest result among valid swaps
Perform the swap and return
When we find the suitable j
, we swap the elements and immediately return:
arr[i - 1], arr[j] = arr[j], arr[i - 1] return arr
Edge case: No valid swap exists
If we complete the first loop without finding any position where arr[i-1] > arr[i]
, it means the array is already in ascending order (like [1,2,3,4]
) or is the smallest possible permutation of its elements. In this case, we return the original array unchanged.
The time complexity is O(n)
where n
is the length of the array, as we potentially scan the array twice in the worst case. The space complexity is O(1)
as we only use a constant amount of extra space for the loop variables.
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 the solution with arr = [3,1,1,3]
:
Phase 1: Find the position to swap from
We scan from right to left looking for the first position where arr[i-1] > arr[i]
:
- i = 3:
arr[2] = 1
,arr[3] = 3
. Since1 < 3
, continue. - i = 2:
arr[1] = 1
,arr[2] = 1
. Since1 = 1
, continue. - i = 1:
arr[0] = 3
,arr[1] = 1
. Since3 > 1
, we found our position!
So i-1 = 0
is the position we need to swap from. We need to replace arr[0] = 3
with something smaller.
Phase 2: Find the best element to swap with
Now we scan from right to left (from index 3 to 1) to find the best candidate to swap with arr[0] = 3
:
- j = 3:
arr[3] = 3
. Since3 ≮ 3
, continue. - j = 2:
arr[2] = 1
. Check conditions:arr[2] < arr[0]
? Yes,1 < 3
✓arr[2] != arr[1]
? No,1 = 1
✗- Skip this position (it's not the rightmost occurrence of value 1)
- j = 1:
arr[1] = 1
. Check conditions:arr[1] < arr[0]
? Yes,1 < 3
✓arr[1] != arr[0]
? Yes,1 ≠ 3
✓- Found our swap candidate!
Phase 3: Perform the swap
Swap arr[0]
and arr[1]
:
- Before:
[3,1,1,3]
- After:
[1,3,1,3]
The result [1,3,1,3]
is lexicographically smaller than [3,1,1,3]
(comparing position 0: 1 < 3
), and it's the largest such permutation achievable with one swap.
Note how the condition arr[j] != arr[j-1]
helped us skip the duplicate at position 2 and choose the rightmost occurrence at position 1, ensuring we get the lexicographically largest result among all valid swaps.
Solution Implementation
1class Solution:
2 def prevPermOpt1(self, arr: List[int]) -> List[int]:
3 """
4 Find the largest permutation that is smaller than arr and can be made with one swap.
5 If no such permutation exists, return the original array.
6
7 Args:
8 arr: List of integers
9
10 Returns:
11 List of integers after at most one swap to get the largest smaller permutation
12 """
13 n = len(arr)
14
15 # Traverse from right to left to find the first position where arr[i-1] > arr[i]
16 # This is the position where we can make a swap to get a smaller permutation
17 for i in range(n - 1, 0, -1):
18 if arr[i - 1] > arr[i]:
19 # Found a position where the previous element is greater than current
20 # Now find the best element to swap with arr[i-1]
21
22 # Traverse from right to find the largest element that is:
23 # 1. Smaller than arr[i-1] (to ensure smaller permutation)
24 # 2. Not equal to its previous element (to avoid duplicate swaps)
25 for j in range(n - 1, i - 1, -1):
26 if arr[j] < arr[i - 1] and arr[j] != arr[j - 1]:
27 # Swap the elements to get the largest smaller permutation
28 arr[i - 1], arr[j] = arr[j], arr[i - 1]
29 return arr
30
31 # No valid swap found, return the original array
32 return arr
33
1class Solution {
2 /**
3 * Finds the previous permutation with one swap that is lexicographically largest
4 * among all permutations smaller than the current array.
5 *
6 * @param arr The input integer array
7 * @return The modified array after one swap, or original array if no valid swap exists
8 */
9 public int[] prevPermOpt1(int[] arr) {
10 int n = arr.length;
11
12 // Traverse from right to left to find the first position where arr[i-1] > arr[i]
13 // This identifies the leftmost position that can be swapped to get a smaller permutation
14 for (int i = n - 1; i > 0; i--) {
15 if (arr[i - 1] > arr[i]) {
16 // Found a decreasing pair at position (i-1, i)
17 // Now find the best candidate to swap with arr[i-1]
18
19 // Traverse from right to find the largest element smaller than arr[i-1]
20 // Skip duplicates by checking arr[j] != arr[j-1]
21 for (int j = n - 1; j > i - 1; j--) {
22 if (arr[j] < arr[i - 1] && arr[j] != arr[j - 1]) {
23 // Found the optimal swap position
24 // Swap elements at positions i-1 and j
25 int temp = arr[i - 1];
26 arr[i - 1] = arr[j];
27 arr[j] = temp;
28
29 return arr;
30 }
31 }
32 }
33 }
34
35 // No valid swap found, return the original array
36 return arr;
37 }
38}
39
1class Solution {
2public:
3 vector<int> prevPermOpt1(vector<int>& arr) {
4 int n = arr.size();
5
6 // Traverse from right to left to find the first decreasing pair
7 // This identifies the position where we can make a swap to get a smaller permutation
8 for (int i = n - 1; i > 0; --i) {
9 // Found a position where arr[i-1] > arr[i]
10 // This means we can swap arr[i-1] with a smaller element to its right
11 if (arr[i - 1] > arr[i]) {
12 // Find the largest element to the right of arr[i-1] that is smaller than arr[i-1]
13 // We traverse from right to left to find the rightmost such element
14 for (int j = n - 1; j > i - 1; --j) {
15 // Check if arr[j] is smaller than arr[i-1]
16 // Also ensure arr[j] != arr[j-1] to avoid swapping with duplicate values
17 // This ensures we get the lexicographically largest permutation that is smaller
18 if (arr[j] < arr[i - 1] && arr[j] != arr[j - 1]) {
19 // Perform the swap to get the previous permutation
20 swap(arr[i - 1], arr[j]);
21 return arr;
22 }
23 }
24 }
25 }
26
27 // No valid swap found, array is already the smallest permutation
28 return arr;
29 }
30};
31
1/**
2 * Finds the previous lexicographically smaller permutation of the array.
3 * If no such permutation exists, returns the original array.
4 * @param arr - The input array to find previous permutation for
5 * @returns The modified array with previous permutation or original if none exists
6 */
7function prevPermOpt1(arr: number[]): number[] {
8 const arrayLength: number = arr.length;
9
10 // Traverse from right to left to find the first decreasing pair
11 for (let leftIndex = arrayLength - 1; leftIndex > 0; --leftIndex) {
12 // Found a position where left element is greater than right element
13 if (arr[leftIndex - 1] > arr[leftIndex]) {
14 // Find the largest element to the right that is smaller than arr[leftIndex - 1]
15 // and is not a duplicate of its previous element
16 for (let rightIndex = arrayLength - 1; rightIndex > leftIndex - 1; --rightIndex) {
17 // Check if current element is smaller than the pivot element
18 // and is not equal to its previous element (to get the largest valid swap)
19 if (arr[rightIndex] < arr[leftIndex - 1] && arr[rightIndex] !== arr[rightIndex - 1]) {
20 // Swap the two elements to get the previous permutation
21 const temp: number = arr[leftIndex - 1];
22 arr[leftIndex - 1] = arr[rightIndex];
23 arr[rightIndex] = temp;
24 return arr;
25 }
26 }
27 }
28 }
29
30 // No previous permutation exists, return original array
31 return arr;
32}
33
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the array. While the code contains nested loops, the outer loop traverses from right to left looking for the first position where arr[i-1] > arr[i]
. Once such a position is found, the inner loop searches from the right for the largest element that is smaller than arr[i-1]
and not a duplicate of its predecessor. After the swap is performed, the function returns immediately. In the worst case, the outer loop might traverse the entire array once, and for one iteration of the outer loop, the inner loop might also traverse a portion of the array once. However, since the function returns as soon as a valid swap is found, the total number of elements examined is at most 2n
, giving us O(n)
time complexity.
The space complexity is O(1)
as the algorithm only uses a constant amount of extra space for the loop variables i
and j
, and performs the swap operation in-place without requiring any additional data structures that scale with the input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall: Not Handling Duplicate Values Correctly
One of the most common mistakes when solving this problem is failing to properly handle duplicate values when selecting which element to swap with. Consider this scenario:
Problem Example:
arr = [3, 1, 1, 3]
Incorrect Approach:
If we don't check for duplicates and simply swap arr[i-1]
with the first element we find that is smaller, we might swap with index 1 (the first 1
), resulting in:
[1, 3, 1, 3] # Not optimal!
Why This Is Wrong: The issue is that swapping with the leftmost occurrence of a duplicate value doesn't give us the lexicographically largest result. We want to minimize the change to the array, which means swapping with the rightmost valid element.
Correct Approach:
By including the condition arr[j] != arr[j - 1]
in our search, we ensure we skip over duplicate values and find the rightmost occurrence. This would correctly swap with index 2 (the second 1
), giving us:
[1, 1, 3, 3] # Optimal result
Solution to Avoid This Pitfall:
The key is the condition in the second loop:
if arr[j] < arr[i - 1] and arr[j] != arr[j - 1]:
The arr[j] != arr[j - 1]
check serves two purposes:
- Skip duplicates: When we encounter consecutive duplicate values, we skip all but the rightmost one
- Maximize the result: By choosing the rightmost valid element to swap, we ensure the resulting permutation is as large as possible while still being smaller than the original
Alternative Implementation to Make Intent Clearer: If you want to make the duplicate handling more explicit, you could modify the code like this:
for j in range(n - 1, i - 1, -1):
if arr[j] < arr[i - 1]:
# Skip if this is not the rightmost occurrence of this value
if j < n - 1 and arr[j] == arr[j + 1]:
continue
# Found the rightmost valid element to swap
arr[i - 1], arr[j] = arr[j], arr[i - 1]
return arr
This alternative makes it clearer that we're looking for the rightmost occurrence of each distinct value that satisfies our swap condition.
Which of the following shows the order of node visit in a Breadth-first Search?
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
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!