Given a string of lowercase English letters, rearrange the characters to form a new string representing the next immediate sequence in lexicographical (alphabetical) order. If the given string is already last in lexicographical order among all possible arrangements, return the arrangement that's first in lexicographical order.
Input: s = 'abcd'
Output: 'abdc'
Explanation: "abdc" is the next sequence in lexicographical order after rearranging "abcd".
Input: s = 'dcba'
Output: 'abcd'
Explanation: Since "dcba" is the last sequence in lexicographical order, we return the first sequence: "abcd".
New to this one? Start here. Below is the core idea in everyday language, plus a real-world analogy—then the sections that follow build on it with the formal reasoning, code, and step-by-step walkthroughs.
Think of the string like an odometer made of letters, and we want to click it up by the smallest possible amount. To make the tiniest increase, you change a digit as far to the right as you can. On an odometer the right side rolls first (...19 → 20). Here it is similar: keep the long right-hand part that is already "maxed out" (going downhill), bump the first character just before it up to the next available value, then reset everything to its right to the smallest order. If the whole thing is already maxed out (like 999), it rolls over to the smallest (000).
Before devising a solution, let’s first make sure we understand what the next lexicographical sequence of a string is.
An important detail is that a string’s next lexicographical sequence is lexicographically larger than the original string. Consider the string “abc” and all its permutations in a lexicographically ordered sequence:
We can see the increasing order of the strings in the sequence when we translate each letter to its position in the alphabet:
From this, we also notice the next string in the sequence after “abc” is “acb”, which is the first string larger than the original string:
This gives us some indication of what we need to find. The next lexicographical string:
Identifying which characters to rearrange
Since our goal is to make the smallest possible increase, we need to somehow rearrange the characters on the right side of the string.
To understand why, imagine trying to “increase” a string’s value. Increasing the rightmost letter causes the resulting string to be closer to the original string lexicographically than increasing the leftmost letter does:
Therefore, we should focus on rearranging characters on the right-hand side of the string first, if possible.
A key insight is that the last string in a lexicographical sequence (i.e., the largest permutation) will always follow a non-increasing order. We can see this with the string “abcc”, for example, with its largest possible permutation being “ccba”:
How does this help us? We know we need to rearrange the characters on the right of the string, but we don’t know how many to rearrange. From now on, let’s refer to the rightmost characters that should be rearranged as the suffix.
Take the string "abcedda" as an example. We traverse it from right to left with the goal of finding the shortest suffix that can be rearranged to form a larger permutation. The last 4 characters form a non-increasing suffix, and cannot be rearranged to make the string larger:
However, the next character, ‘c’, breaks the non-increasing sequence:
Let’s call this character the pivot:
If no pivot is found, it means the string is already the last lexicographical sequence. In this case, we need to obtain its first lexicographical permutation as the problem states. This can be done by reversing the string:
Rearranging characters
Having identified the shortest suffix to rearrange, the next objective is to rearrange this suffix to make the smallest increase possible. We have to start the rearrangement at the pivot since the rest of the suffix is already arranged in its largest permutation.
To make the character at the pivot position larger, we’d need to swap the pivot with a character larger than it on the right.
In our example, which character should our pivot (‘c’) swap with? We want to swap it with a character larger than 'c,' but not too much larger because the increase should be as small as possible. Let’s figure out how we find such a character.
Since the substring after the pivot is lexicographically non-increasing, we can find the closest character larger than ‘c’ by traversing this suffix from right to left and stopping at the first character larger than it. In other words, we’re finding the rightmost successor to the pivot, which is ‘d’:
Now, we swap the pivot and the rightmost successor:
The character at the pivot has increased, so to get the next permutation, we should make the substring after the pivot as small as possible. After the swap shown above, we see that substring “edca” is not at its smallest permutation. So, we need to minimize the permutation of this substring.
An important observation is that after the previous swap, the substring after the pivot is still lexicographically non-increasing.
This means we can minimize this substring’s permutation by reversing it:
And just like that, we found the next lexicographical sequence! The two-pointer strategy used in this problem is staged traversal, where we first identify the pivot, and then identify the rightmost successor relative to it.
Many steps are involved in identifying the next lexicographical sequence. So, here’s a summary:
Find the rightmost successor to the pivot.
Swap the rightmost successor with the pivot to increase the lexicographical order of the suffix.
Reverse the suffix after the pivot to minimize its permutation.
def next_lexicographical_sequence(s: str) -> str:
letters = list(s)
# Locate the pivot, which is the first character from the right that breaks
# non-increasing order. Start searching from the second-to-last position.
pivot = len(letters) - 2
while pivot >= 0 and letters[pivot] >= letters[pivot + 1]:
pivot -= 1
# If pivot is not found, the string is already in its largest permutation. In
# this case, reverse the string to obtain the smallest permutation.
if pivot == -1:
return ''.join(reversed(letters))
# Find the rightmost successor to the pivot.
rightmost_successor = len(letters) - 1
while letters[rightmost_successor] <= letters[pivot]:
rightmost_successor -= 1
# Swap the rightmost successor with the pivot to increase the lexicographical
# order of the suffix.
letters[pivot], letters[rightmost_successor] = (letters[rightmost_successor], letters[pivot])
# Reverse the suffix after the pivot to minimize its permutation.
letters[pivot + 1:] = reversed(letters[pivot + 1:])
return ''.join(letters)#include <string>
using namespace std;
string nextLexicographicalSequence(string s) {
int n = s.size();
// 1) Find the pivot.
int pivot = n - 2;
while (pivot >= 0 && s[pivot] >= s[pivot + 1]) {
pivot--;
}
// 2) If a pivot exists, swap it with its rightmost larger character.
if (pivot != -1) {
int successor = n - 1;
while (s[successor] <= s[pivot]) {
successor--;
}
char temp = s[pivot];
s[pivot] = s[successor];
s[successor] = temp;
}
// 3) Reverse everything after the pivot (if no pivot, reverses all).
int left = pivot + 1;
int right = n - 1;
while (left < right) {
char temp = s[left];
s[left] = s[right];
s[right] = temp;
left++;
right--;
}
return s;
}// This method lives inside a class, e.g. class Solution { ... }
public String nextLexicographicalSequence(String s) {
char[] letters = s.toCharArray(); // strings are immutable in Java
int n = letters.length;
// 1) Find the pivot.
int pivot = n - 2;
while (pivot >= 0 && letters[pivot] >= letters[pivot + 1]) {
pivot--;
}
// 2) If a pivot exists, swap it with its rightmost larger character.
if (pivot != -1) {
int successor = n - 1;
while (letters[successor] <= letters[pivot]) {
successor--;
}
char temp = letters[pivot];
letters[pivot] = letters[successor];
letters[successor] = temp;
}
// 3) Reverse everything after the pivot (if no pivot, reverses all).
int left = pivot + 1;
int right = n - 1;
while (left < right) {
char temp = letters[left];
letters[left] = letters[right];
letters[right] = temp;
left++;
right--;
}
return new String(letters);
}Time complexity: The time complexity of next_lexicographical_sequence is , where denotes the length of the input string. This is because we perform a maximum of two iterations across the string: one to find the pivot and another to find the rightmost character in the suffix that’s greater in value than the pivot. We also perform one reversal, which takes time.
Space complexity: The space complexity is due to the space taken up by the letters list. In Python, this additional space is used because strings are immutable, which necessitates storing the input string as a list.
Two step-by-step walkthroughs of the algorithm below: a valid case (the expected happy path) and an invalid / edge case that exercises the tricky parts. Each step shows the program state as the code runs—the same steps apply to Python, C++ and Java.
pivot successor changed
Result: "abdce" — the very next arrangement after "abced".
| Phase | Check | Result | String |
|---|---|---|---|
| pivot | index 3: 'e' ≥ 'd'? | yes → keep scanning left | abced |
| pivot | index 2: 'c' ≥ 'e'? | no → pivot = 2 ('c') | abced |
| successor | rightmost letter > 'c' | 'd' at index 4 | abced |
| swap | swap index 2 ↔ 4 | c ↔ d | abdec |
| reverse | reverse indices 3..4 | "ec" → "ce" | abdce |
scanning for a pivot swapped during the reverse
Result: "abcd" — the largest arrangement rolls over to the smallest, exactly like an odometer going 999 → 000.
| Phase | Check | Result | String |
|---|---|---|---|
| pivot | index 2: 'b' ≥ 'a'? | yes → keep scanning left | dcba |
| pivot | index 1: 'c' ≥ 'b'? | yes → keep scanning left | dcba |
| pivot | index 0: 'd' ≥ 'c'? | yes → pivot = −1 (none) | dcba |
| reverse | reverse indices 0..3 | swap 0↔3, then 1↔2 | abcd |
This is why the code has a single reverse at the end with no special “already largest” branch: with pivot = −1, pivot + 1 = 0, so “reverse after the pivot” automatically means “reverse everything.”
Both runs step exactly as the tables above. Python-specific: copy the string into a list, edit it, and rebuild with "".join(letters); “no pivot” is pivot == -1.
Same steps. C++-specific: a string is already editable in place, so you swap characters directly and return s.
Same steps. Java-specific: copy into a char[], edit it, and rebuild with new String(letters).
In addition to the examples discussed, below are more examples to consider when testing your code.
| Input | Expected output | Description |
|---|---|---|
| s = 'a' | 'a' | Tests a string with a single character. |
| s = 'aaaa' | 'aaaa' | Tests a string with a repeated character. |
| s = 'ynitsed' | 'ynsdeit' | Tests a string with a random pivot character. |
Tip: Be precise with your language. It’s crucial to be precise with your choice of words during an interview, especially for technical descriptions. For instance, in this problem, we use “non-increasing” instead of “decreasing,” as “decreasing” implies each term is strictly smaller than the previous one, which isn’t true in this case since adjacent characters can be equal.