899. Orderly Queue
Problem Description
You have a string s
and an integer k
. You can perform the following operation any number of times:
- Choose one of the first
k
letters in the string - Remove it from its current position
- Append it to the end of the string
Your goal is to find the lexicographically smallest string possible after performing any number of these operations.
For example, if s = "cba"
and k = 1
, you can only move the first character each time:
- Starting with
"cba"
- Move 'c' to end:
"bac"
- Move 'b' to end:
"acb"
- Move 'a' to end:
"cba"
(back to original)
The smallest string achievable would be "acb"
.
If k ≥ 2
, you have more flexibility. You can choose from the first 2 (or more) characters to move, which gives you the ability to rearrange the string more freely.
The key insight is:
- When
k = 1
, you can only rotate the string, so you need to check all possible rotations and return the smallest one - When
k > 1
, you can actually achieve any permutation of the string through a series of moves, so the answer is simply the sorted string
Intuition
Let's think about what operations we can actually perform with different values of k
.
When k = 1
, we're severely limited - we can only take the first character and move it to the end. This is essentially a rotation operation. If we keep doing this, we'll cycle through all possible rotations of the string. For a string of length n
, after n
operations we're back where we started. So with k = 1
, we can only achieve n
different strings (all rotations), and we need to find the lexicographically smallest one.
When k = 2
, something interesting happens. We can now choose between the first two characters to move. This gives us much more power. Consider what happens when we want to swap two adjacent characters in the middle of the string, say we want to swap x
and y
in abc[xy]def
:
- Move
a
,b
,c
to the end one by one:[xy]defabc
- Now
x
andy
are at the front. Movey
to the end:[x]defabcy
- Move
x
to the end:defabcy[x]
- Move
d
,e
,f
to the end:abcy[x]def
- Continue rotating until we get:
abc[yx]def
We've successfully swapped x
and y
! This means with k ≥ 2
, we can swap any two adjacent characters.
And if we can swap adjacent characters, we can perform bubble sort! We can keep swapping adjacent characters until the entire string is sorted. This is why when k > 1
, the answer is simply the sorted version of the string - it's the lexicographically smallest possible arrangement, and we can achieve it through a series of moves.
The solution elegantly handles both cases: rotate through all possibilities when k = 1
, or return the sorted string when k > 1
.
Solution Approach
The solution implements a case-by-case approach based on the value of k
:
Case 1: When k = 1
Since we can only move the first character to the end, we need to check all possible rotations of the string. The implementation:
- Initialize
ans
with the original strings
- Perform
len(s) - 1
rotations (we don't need to do alln
rotations since the last one would bring us back to the original) - In each iteration:
- Move the first character to the end:
s = s[1:] + s[0]
- Compare with the current answer and keep the smaller one:
ans = min(ans, s)
- Move the first character to the end:
- Return the lexicographically smallest string found
if k == 1:
ans = s
for _ in range(len(s) - 1):
s = s[1:] + s[0]
ans = min(ans, s)
return ans
Case 2: When k > 1
As established in the intuition, when k > 1
, we can achieve any permutation of the string. The lexicographically smallest permutation is simply the sorted string.
return "".join(sorted(s))
The sorted(s)
function converts the string into a list of characters, sorts them in ascending order, and "".join()
concatenates them back into a string.
Time Complexity:
- For
k = 1
:O(n²)
wheren
is the length of the string (we performn
rotations, each takingO(n)
time for string operations and comparison) - For
k > 1
:O(n log n)
due to sorting
Space Complexity:
- For
k = 1
:O(n)
for storing the answer string - For
k > 1
:O(n)
for the sorted character list
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 two examples to illustrate how the solution works for different values of k
.
Example 1: s = "dcba"
, k = 1
Since k = 1
, we can only move the first character to the end. Let's trace through all rotations:
- Start with
"dcba"
(initializeans = "dcba"
) - First rotation: Move 'd' to end →
"cbad"
- Compare:
min("dcba", "cbad")
="cbad"
(updateans = "cbad"
)
- Compare:
- Second rotation: Move 'c' to end →
"badc"
- Compare:
min("cbad", "badc")
="badc"
(updateans = "badc"
)
- Compare:
- Third rotation: Move 'b' to end →
"adcb"
- Compare:
min("badc", "adcb")
="adcb"
(updateans = "adcb"
)
- Compare:
We've checked all meaningful rotations. The answer is "adcb"
.
Example 2: s = "dcba"
, k = 2
Since k > 1
, we can achieve any permutation of the string through a series of moves. The lexicographically smallest permutation is the sorted string.
- Sort the characters: ['d', 'c', 'b', 'a'] → ['a', 'b', 'c', 'd']
- Join them:
"abcd"
The answer is "abcd"
.
Notice how with k = 2
, we get a better result ("abcd"
) than with k = 1
("adcb"
). This demonstrates the increased flexibility when we can choose from multiple characters at the front of the string.
Solution Implementation
1class Solution:
2 def orderlyQueue(self, s: str, k: int) -> str:
3 """
4 Find the lexicographically smallest string that can be obtained by repeatedly:
5 - Choose one of the first k letters and move it to the end of the string
6
7 Args:
8 s: Input string to be rearranged
9 k: Number of first letters we can choose from to move to the end
10
11 Returns:
12 Lexicographically smallest string possible
13 """
14
15 if k == 1:
16 # When k=1, we can only rotate the string
17 # Try all possible rotations and find the minimum
18 min_string = s
19
20 # Generate all rotations by moving first character to end
21 for _ in range(len(s) - 1):
22 s = s[1:] + s[0] # Move first character to end
23 min_string = min(min_string, s) # Keep track of minimum
24
25 return min_string
26
27 else:
28 # When k >= 2, we can achieve any permutation
29 # The smallest permutation is the sorted string
30 return "".join(sorted(s))
31
1class Solution {
2 public String orderlyQueue(String s, int k) {
3 // When k = 1, we can only rotate the string
4 // We need to find the lexicographically smallest rotation
5 if (k == 1) {
6 String minString = s;
7 StringBuilder rotatedString = new StringBuilder(s);
8
9 // Try all possible rotations by moving first character to the end
10 for (int i = 0; i < s.length() - 1; i++) {
11 // Move first character to the end
12 rotatedString.append(rotatedString.charAt(0)).deleteCharAt(0);
13
14 // Update minimum if current rotation is lexicographically smaller
15 if (rotatedString.toString().compareTo(minString) < 0) {
16 minString = rotatedString.toString();
17 }
18 }
19 return minString;
20 }
21
22 // When k >= 2, we can achieve any permutation of the string
23 // The lexicographically smallest permutation is the sorted string
24 char[] charArray = s.toCharArray();
25 Arrays.sort(charArray);
26 return String.valueOf(charArray);
27 }
28}
29
1class Solution {
2public:
3 string orderlyQueue(string s, int k) {
4 // When k = 1, we can only rotate the string
5 // We need to find the lexicographically smallest rotation
6 if (k == 1) {
7 string minString = s;
8
9 // Try all possible rotations by moving first character to the end
10 for (int i = 0; i < s.size() - 1; ++i) {
11 // Rotate: move first character to the end
12 s = s.substr(1) + s[0];
13
14 // Update minimum if current rotation is smaller
15 if (s < minString) {
16 minString = s;
17 }
18 }
19
20 return minString;
21 }
22
23 // When k >= 2, we can achieve any permutation of the string
24 // The smallest permutation is the sorted string
25 sort(s.begin(), s.end());
26 return s;
27 }
28};
29
1/**
2 * Finds the lexicographically smallest string that can be obtained by performing operations on string s.
3 * Operation: Choose one of the first k letters and move it to the end of the string.
4 *
5 * @param s - The input string to be rearranged
6 * @param k - The number of first letters that can be chosen for the operation
7 * @returns The lexicographically smallest string possible
8 */
9function orderlyQueue(s: string, k: number): string {
10 // When k > 1, we can achieve any permutation of the string
11 // Therefore, return the sorted string (lexicographically smallest)
12 if (k > 1) {
13 const charactersArray: string[] = [...s];
14 charactersArray.sort();
15 return charactersArray.join('');
16 }
17
18 // When k = 1, we can only rotate the string
19 // Try all possible rotations and find the lexicographically smallest one
20 const stringLength: number = s.length;
21 let lexicographicallySmallest: string = s;
22
23 // Generate all rotations by moving characters from front to back
24 for (let rotationIndex = 1; rotationIndex < stringLength; rotationIndex++) {
25 // Create rotation: take substring from index i to end + substring from start to index i
26 const currentRotation: string = s.slice(rotationIndex) + s.slice(0, rotationIndex);
27
28 // Update minimum if current rotation is lexicographically smaller
29 if (currentRotation < lexicographicallySmallest) {
30 lexicographicallySmallest = currentRotation;
31 }
32 }
33
34 return lexicographicallySmallest;
35}
36
Time and Space Complexity
The time complexity depends on the value of k
:
-
When
k = 1
: The code performsn-1
rotations of the string, where each rotation involves string slicing and concatenation operations that takeO(n)
time. Additionally, each comparison withmin()
takesO(n)
time. Therefore, the total time complexity isO(n^2)
. -
When
k > 1
: The code simply sorts the string usingsorted()
, which has a time complexity ofO(n log n)
.
Overall, the worst-case time complexity is O(n^2)
when k = 1
.
The space complexity is O(n)
in both cases:
- When
k = 1
: The variableans
stores a copy of the string, and string slicing operations create new strings, requiringO(n)
space. - When
k > 1
: Thesorted()
function creates a new list of characters requiringO(n)
space, andjoin()
creates a new string of lengthn
.
Therefore, the time complexity is O(n^2)
and the space complexity is O(n)
, where n
is the length of the string.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misunderstanding the k > 1 Case
The Mistake:
Many people initially think that when k = 2
, you can only perform limited rearrangements, similar to k = 1
. They might try to implement complex logic to track which characters can be moved based on the current first k
positions.
Why It Happens:
The problem statement makes it seem like k = 2
is just slightly more flexible than k = 1
, but the reality is that k ≥ 2
allows you to achieve ANY permutation of the string through a series of moves.
The Mathematical Insight:
When k ≥ 2
, you can effectively "bubble sort" the string:
- You can always bring any character to the front by strategically moving other characters to the back
- This is possible because with 2 positions to choose from, you can preserve one character while cycling others
Solution:
Recognize that for k ≥ 2
, the answer is always the sorted string:
if k >= 2:
return "".join(sorted(s))
Pitfall 2: Inefficient Rotation Implementation for k = 1
The Mistake: Using string concatenation in a loop without considering the number of iterations needed:
# Inefficient: Checking all n rotations when n-1 is sufficient
ans = s
for i in range(len(s)): # Should be range(len(s) - 1)
s = s[1:] + s[0]
ans = min(ans, s)
Why It Happens:
After n
rotations (where n = len(s)
), the string returns to its original form. The last rotation is redundant.
Solution:
Only perform n-1
rotations since the n
-th rotation brings you back to the original string:
ans = s
for _ in range(len(s) - 1): # Note: len(s) - 1, not len(s)
s = s[1:] + s[0]
ans = min(ans, s)
Pitfall 3: Not Considering the Original String for k = 1
The Mistake: Forgetting to include the original string in the comparison:
# Wrong: Starting with empty or first rotation
ans = "" # or ans = s[1:] + s[0]
for _ in range(len(s)):
s = s[1:] + s[0]
if not ans or s < ans:
ans = s
Why It Happens: The original string might already be the lexicographically smallest rotation, and skipping it would give an incorrect answer.
Solution: Always initialize the answer with the original string before starting rotations:
ans = s # Start with original string
for _ in range(len(s) - 1):
s = s[1:] + s[0]
ans = min(ans, s)
A heap is a ...?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
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
Want a Structured Path to Master System Design Too? Don’t Miss This!