Facebook Pixel

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 first k characters and leave the remaining k 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 than 2k characters remaining, reverse the first k 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 than k 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 first k
  • 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:

  1. Convert string to list: Since strings are immutable in Python, we first convert the string s into a list of characters cs = list(s). This allows us to modify portions of the string in-place.

  2. 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 of 2 * k ensures that variable i points to the start of each new block where we need to perform a reversal.

  3. Reverse the first k characters of each chunk: For each iteration, we reverse the characters from index i to i + k using Python's slice assignment and the reversed() 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 slice cs[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 and 2k characters, exactly k characters get reversed
  4. 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 Evaluator

Example 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, so i 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:

  1. The first 2k block reverses its first k characters and leaves the next k unchanged
  2. The remaining partial block (less than k characters) gets completely reversed
  3. 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

How does quick sort divide the problem into subproblems?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More