541. Reverse String II
Problem Description
You are given a string s
and an integer k
. Your task is to reverse portions of the string according to a specific pattern.
The pattern works as follows:
- Divide the string into consecutive chunks of
2k
characters each - For each chunk of
2k
characters, reverse only the firstk
characters and leave the remainingk
characters unchanged - Continue this pattern throughout the entire string
There are special cases to handle when you reach the end of the string:
- If there are fewer than
k
characters remaining at the end, reverse all of them - If there are at least
k
but fewer than2k
characters remaining, reverse the firstk
characters and leave the rest as they are
For example, if s = "abcdefg"
and k = 2
:
- Characters at positions 0-1 (
"ab"
) get reversed to"ba"
- Characters at positions 2-3 (
"cd"
) remain unchanged - Characters at positions 4-5 (
"ef"
) get reversed to"fe"
- Character at position 6 (
"g"
) remains unchanged (less thank
characters in this chunk) - Result:
"bacdfeg"
The solution uses a straightforward approach by iterating through the string in steps of 2k
and reversing the appropriate portions using Python's slice notation and the reversed()
function.
Intuition
The key insight is recognizing that this problem follows a repeating pattern every 2k
characters. Instead of trying to handle complex conditions throughout the string, we can process the string in fixed intervals.
Think of the string as being divided into blocks of size 2k
. In each block, we only need to reverse the first half (first k
characters) and leave the second half unchanged. This pattern repeats consistently throughout the string.
The elegance of this approach comes from realizing that Python's slicing handles edge cases automatically. When we write cs[i : i + k]
, if there are fewer than k
characters remaining from position i
, the slice will simply take whatever characters are available. This means:
- If we have exactly
k
or more characters, we reverse the firstk
- If we have fewer than
k
characters at the end, we reverse all of them
By iterating through the string with a step size of 2k
(using range(0, len(cs), 2 * k)
), we automatically land at the start of each block where reversal should begin. This eliminates the need for complex conditional logic to track where we are in the pattern.
Converting the string to a list first is a practical choice since strings are immutable in Python. This allows us to modify portions in-place using slice assignment (cs[i : i + k] = reversed(cs[i : i + k])
), making the reversal operation clean and efficient.
The solution naturally handles all the special cases mentioned in the problem without additional code because the slicing and reversal operations gracefully handle partial chunks at the string's end.
Learn more about Two Pointers patterns.
Solution Approach
The solution uses a two-pointer technique combined with string manipulation to achieve the desired reversals.
Step-by-step implementation:
-
Convert string to list: Since strings are immutable in Python, we first convert the string
s
into a list of characterscs = list(s)
. This allows us to modify portions of the string in-place. -
Iterate in chunks of 2k: We use a for loop with
range(0, len(cs), 2 * k)
to iterate through the string. The step size of2 * k
ensures that variablei
points to the start of each new block where we need to perform a reversal. -
Reverse the first k characters of each chunk: For each iteration, we reverse the characters from index
i
toi + k
using Python's slice assignment and thereversed()
function:cs[i : i + k] = reversed(cs[i : i + k])
The beauty of this approach is that Python's slicing automatically handles boundaries:
- If
i + k
exceeds the length of the list, the slicecs[i : i + k]
will only include characters up to the end of the list - This means if there are fewer than
k
characters remaining, all of them get reversed - If there are between
k
and2k
characters, exactlyk
characters get reversed
- If
-
Join and return: After processing all chunks, we convert the list back to a string using
"".join(cs)
and return the result.
Time Complexity: O(n)
where n
is the length of the string. We traverse the string once and perform reversal operations that are proportional to the string length.
Space Complexity: O(n)
for storing the list representation of the string. The reversal is done in-place within the list.
The two-pointer technique is implicit in the reversed()
function, which internally uses two pointers (start and end) to swap elements and achieve the reversal efficiently.
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 s = "abcdefgh"
and k = 3
.
Initial Setup:
- Convert string to list:
cs = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
- We'll iterate with step size
2k = 6
, soi
will take values: 0, 6
Iteration 1 (i = 0):
- We're at the start of the first chunk (positions 0-5)
- Reverse
cs[0:3]
which is['a', 'b', 'c']
- After reversal:
cs[0:3] = ['c', 'b', 'a']
- Positions 3-5 (
['d', 'e', 'f']
) remain unchanged - Current state:
cs = ['c', 'b', 'a', 'd', 'e', 'f', 'g', 'h']
Iteration 2 (i = 6):
- We're at the start of the second chunk (positions 6-7)
- We want to reverse
cs[6:9]
, but only positions 6-7 exist - Python's slicing gives us
cs[6:9] = ['g', 'h']
(takes what's available) - After reversal:
cs[6:8] = ['h', 'g']
- Current state:
cs = ['c', 'b', 'a', 'd', 'e', 'f', 'h', 'g']
Final Step:
- Join the list back to string:
"cbaderfhg"
- Return result:
"cbaderfhg"
This example demonstrates how:
- The first
2k
block reverses its firstk
characters and leaves the nextk
unchanged - The remaining partial block (less than
k
characters) gets completely reversed - Python's slicing automatically handles the boundary case without extra conditional logic
Solution Implementation
1class Solution:
2 def reverseStr(self, s: str, k: int) -> str:
3 """
4 Reverse every k characters for every 2k characters in the string.
5
6 Args:
7 s: Input string to be partially reversed
8 k: Number of characters to reverse in each 2k block
9
10 Returns:
11 String with every k characters reversed in each 2k block
12 """
13 # Convert string to list for in-place modification
14 char_list = list(s)
15
16 # Process the string in chunks of 2k characters
17 for start_index in range(0, len(char_list), 2 * k):
18 # Reverse the first k characters in the current 2k block
19 # If remaining characters are less than k, reverse all of them
20 char_list[start_index : start_index + k] = reversed(char_list[start_index : start_index + k])
21
22 # Convert the list back to string and return
23 return "".join(char_list)
24
1class Solution {
2 /**
3 * Reverses every k characters for every 2k characters in the string.
4 * For every 2k characters, reverse the first k characters.
5 * If there are fewer than k characters left, reverse all of them.
6 *
7 * @param s the input string to be processed
8 * @param k the number of characters to reverse in each segment
9 * @return the modified string with selective reversals
10 */
11 public String reverseStr(String s, int k) {
12 // Convert string to character array for in-place modification
13 char[] charArray = s.toCharArray();
14 int length = charArray.length;
15
16 // Process the string in chunks of 2k characters
17 for (int startIndex = 0; startIndex < length; startIndex += k * 2) {
18 // Define the range to reverse: first k characters of each 2k block
19 int left = startIndex;
20 // Right boundary is either k-1 positions from start or end of string (whichever is smaller)
21 int right = Math.min(startIndex + k - 1, length - 1);
22
23 // Perform two-pointer reversal for the current segment
24 while (left < right) {
25 // Swap characters at left and right positions
26 char temp = charArray[left];
27 charArray[left] = charArray[right];
28 charArray[right] = temp;
29
30 // Move pointers towards center
31 left++;
32 right--;
33 }
34 }
35
36 // Convert character array back to string and return
37 return new String(charArray);
38 }
39}
40
1class Solution {
2public:
3 string reverseStr(string s, int k) {
4 int stringLength = s.size();
5
6 // Process the string in chunks of 2k characters
7 for (int startIndex = 0; startIndex < stringLength; startIndex += 2 * k) {
8 // Calculate the end index for reversal (either startIndex + k or end of string)
9 int endIndex = min(startIndex + k, stringLength);
10
11 // Reverse the first k characters (or remaining characters if less than k)
12 // in the current 2k chunk
13 reverse(s.begin() + startIndex, s.begin() + endIndex);
14 }
15
16 return s;
17 }
18};
19
1/**
2 * Reverses every k characters for every 2k characters in a string
3 * @param s - The input string to be processed
4 * @param k - The number of characters to reverse in each segment
5 * @returns The modified string with reversed segments
6 */
7function reverseStr(s: string, k: number): string {
8 // Get the length of the input string
9 const length: number = s.length;
10
11 // Convert string to character array for in-place manipulation
12 const charArray: string[] = s.split('');
13
14 // Process the string in chunks of 2k characters
15 for (let startIndex: number = 0; startIndex < length; startIndex += 2 * k) {
16 // Calculate the left and right boundaries for reversal
17 // Left boundary starts at current position
18 let leftPointer: number = startIndex;
19 // Right boundary is either k-1 positions ahead or end of string (whichever is smaller)
20 let rightPointer: number = Math.min(startIndex + k - 1, length - 1);
21
22 // Reverse the characters between left and right pointers using two-pointer technique
23 while (leftPointer < rightPointer) {
24 // Swap characters at left and right positions
25 [charArray[leftPointer], charArray[rightPointer]] = [charArray[rightPointer], charArray[leftPointer]];
26
27 // Move pointers towards center
28 leftPointer++;
29 rightPointer--;
30 }
31 }
32
33 // Join the character array back into a string and return
34 return charArray.join('');
35}
36
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the string s
.
The algorithm iterates through the string in steps of 2k
, processing at most k
characters in each iteration. Since each character is visited and processed exactly once during the reversal operations, the total time complexity is linear with respect to the length of the input string.
Space Complexity: O(n)
, where n
is the length of the string s
.
The space complexity comes from creating a list cs
from the input string s
, which requires O(n)
additional space. The reversed()
function returns an iterator and the slice assignment operation doesn't require additional space beyond the already allocated list. The final join()
operation creates a new string of length n
, but this is considered the output and typically not counted in auxiliary space complexity analysis.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Attempting Direct String Modification
A common mistake is trying to modify the string directly without converting it to a list first:
Incorrect approach:
def reverseStr(self, s: str, k: int) -> str:
for i in range(0, len(s), 2 * k):
s[i : i + k] = reversed(s[i : i + k]) # ERROR: strings are immutable
return s
Why it fails: Strings in Python are immutable, meaning you cannot change individual characters or slices directly. This will raise a TypeError
.
Solution: Always convert the string to a list first, perform modifications, then join back to a string.
2. Manual Index Boundary Checking
Another pitfall is overthinking the boundary conditions and adding unnecessary checks:
Overly complex approach:
def reverseStr(self, s: str, k: int) -> str:
char_list = list(s)
for i in range(0, len(char_list), 2 * k):
if i + k <= len(char_list):
# Reverse exactly k characters
char_list[i : i + k] = reversed(char_list[i : i + k])
else:
# Reverse remaining characters
char_list[i:] = reversed(char_list[i:])
return "".join(char_list)
Why it's unnecessary: Python's slicing automatically handles out-of-bounds indices gracefully. When i + k
exceeds the list length, char_list[i : i + k]
simply returns all elements from i
to the end, making manual boundary checks redundant.
3. Using String Concatenation in Loop
Building the result string through repeated concatenation is inefficient:
Inefficient approach:
def reverseStr(self, s: str, k: int) -> str:
result = ""
for i in range(0, len(s), 2 * k):
# Reverse first k characters
result += s[i : i + k][::-1]
# Add next k characters unchanged
result += s[i + k : i + 2 * k]
return result
Why it's inefficient: String concatenation in a loop creates new string objects repeatedly, leading to O(nΒ²) time complexity in the worst case due to string immutability.
Solution: Use list operations and join at the end for O(n) time complexity, or use more efficient string building methods like io.StringIO()
if multiple concatenations are needed.
4. Forgetting Edge Cases with Empty Strings or k=1
Not handling special cases properly:
# Might not handle edge cases well:
def reverseStr(self, s: str, k: int) -> str:
if not s: # Unnecessary check - the main algorithm handles this
return ""
if k == 1: # Unnecessary optimization - algorithm works correctly
return s
# ... rest of the code
Why it's problematic: Adding special case handling when the main algorithm already handles these cases correctly adds unnecessary complexity and potential for bugs. The original solution handles empty strings (loop doesn't execute) and k=1 (reversing 1 character has no effect) naturally.
How does quick sort divide the problem into subproblems?
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
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!