Facebook Pixel

1427. Perform String Shifts 🔒

Problem Description

You have a string s consisting of lowercase English letters and a 2D matrix called shift. Each element in shift is a pair [direction, amount] where:

  • direction is either 0 (left shift) or 1 (right shift)
  • amount is the number of positions to shift the string

A left shift by 1 means taking the first character of the string and moving it to the end. For example, "abc" becomes "bca" after a left shift by 1.

A right shift by 1 means taking the last character of the string and moving it to the beginning. For example, "abc" becomes "cab" after a right shift by 1.

You need to perform all the shift operations in sequence and return the final string.

The solution works by calculating the net shift amount. Since left and right shifts cancel each other out, we can sum all shifts (treating left shifts as negative and right shifts as positive). The final position is determined by taking this sum modulo the string length. For a net right shift of x positions, we move the last x characters to the front, which is equivalent to s[-x:] + s[:-x] in Python.

For example, if s = "abcde" and shift = [[0, 1], [1, 2]], we first left shift by 1 getting "bcdea", then right shift by 2 getting "eabcd". The net effect is a right shift by 1 position.

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

Intuition

The key insight is recognizing that performing multiple shifts sequentially is inefficient and unnecessary. Instead of actually moving characters around for each shift operation, we can think about what the final position would be after all shifts are applied.

Consider that left and right shifts are opposite operations - a left shift by 1 undoes a right shift by 1. This means we can combine all the shifts into a single net shift value. If we have 3 left shifts and 5 right shifts, the result is the same as doing 2 right shifts.

To calculate the net shift, we can treat left shifts as negative values and right shifts as positive values. By summing all the shift amounts (with their appropriate signs), we get the total displacement.

Another important observation is that shifting a string by its length brings it back to the original position. For a string of length n, shifting left or right by n positions results in the same string. This is why we use modulo operation x % len(s) - any shift greater than the string length can be reduced to an equivalent smaller shift.

Once we have the net shift amount x, we need to understand what it means:

  • A positive x means net right shift by x positions
  • This is equivalent to taking the last x characters and moving them to the front
  • In Python slice notation: s[-x:] gives us the last x characters, and s[:-x] gives us everything except the last x characters

This approach transforms an O(m * n) problem (where m is the number of shifts and n is the string length) into an O(m + n) solution - we iterate through shifts once to calculate the net shift, then perform a single string manipulation.

Learn more about Math patterns.

Solution Approach

Following the intuition, we implement the solution by calculating the net shift and applying it once:

  1. Calculate the net shift: We iterate through the shift array and accumulate the total shift amount. For each [direction, amount] pair:

    • If direction = 1 (right shift), we add amount to our accumulator
    • If direction = 0 (left shift), we subtract amount from our accumulator

    This is elegantly done in the code using: x = sum((b if a else -b) for a, b in shift)

    • Here a is the direction and b is the amount
    • If a is 1 (truthy), we take b as positive
    • If a is 0 (falsy), we take -b as negative
  2. Normalize the shift amount: Since shifting by the string's length returns the original string, we take modulo: x %= len(s). This ensures:

    • The shift amount is always between 0 and len(s) - 1
    • We avoid unnecessary full rotations
    • Handles cases where the total shift exceeds the string length
  3. Apply the final shift: With the net right shift of x positions, we reconstruct the string:

    • s[-x:] extracts the last x characters (these move to the front)
    • s[:-x] extracts all characters except the last x (these move to the back)
    • Concatenating them gives us the final result: s[-x:] + s[:-x]

For example, if s = "abcde" and net shift x = 2 (right):

  • s[-2:] gives "de"
  • s[:-2] gives "abc"
  • Result: "deabc"

The time complexity is O(m + n) where m is the number of shifts and n is the string length. The space complexity is O(n) for creating the result string.

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 a concrete example to illustrate the solution approach.

Given:

  • s = "abcdef"
  • shift = [[1, 3], [0, 2], [1, 1], [0, 4]]

Step 1: Calculate the net shift

Let's process each shift operation and track the cumulative effect:

  • [1, 3]: Right shift by 3 → Add +3 to accumulator → Total: +3
  • [0, 2]: Left shift by 2 → Add -2 to accumulator → Total: +3 - 2 = +1
  • [1, 1]: Right shift by 1 → Add +1 to accumulator → Total: +1 + 1 = +2
  • [0, 4]: Left shift by 4 → Add -4 to accumulator → Total: +2 - 4 = -2

Net shift x = -2 (this means a net left shift by 2)

Step 2: Normalize the shift amount

Since we have a negative shift (left shift), we need to convert it to an equivalent right shift:

  • x = -2
  • After modulo: x = -2 % 6 = 4 (in Python, this gives us the equivalent right shift)
  • This makes sense: a left shift by 2 is the same as a right shift by 4 for a string of length 6

Step 3: Apply the final shift

Now we perform a single right shift by 4 positions:

  • Original string: "abcdef"
  • s[-4:] gives us the last 4 characters: "cdef"
  • s[:-4] gives us all except the last 4: "ab"
  • Result: "cdef" + "ab" = "cdefab"

Verification: Let's verify by actually performing the shifts sequentially:

  1. Start: "abcdef"
  2. Right shift by 3: "defabc"
  3. Left shift by 2: "fabcde"
  4. Right shift by 1: "efabcd"
  5. Left shift by 4: "cdefab"

The result matches! By calculating the net shift and applying it once, we avoid the inefficiency of performing each shift operation individually.

Solution Implementation

1class Solution:
2    def stringShift(self, s: str, shift: List[List[int]]) -> str:
3        # Calculate the net shift amount
4        # For each shift operation: if direction is 1 (right shift), add the amount
5        # If direction is 0 (left shift), subtract the amount
6        net_shift = sum((amount if direction else -amount) for direction, amount in shift)
7      
8        # Normalize the shift to be within the string length to avoid unnecessary rotations
9        # Positive net_shift means right shift, negative means left shift
10        net_shift %= len(s)
11      
12        # Perform the string rotation
13        # For right shift: take last 'net_shift' characters and concatenate with the rest
14        # s[-net_shift:] gets the last 'net_shift' characters
15        # s[:-net_shift] gets all characters except the last 'net_shift' characters
16        return s[-net_shift:] + s[:-net_shift]
17
1class Solution {
2    public String stringShift(String s, int[][] shift) {
3        // Calculate the net shift amount
4        int netShift = 0;
5      
6        // Process each shift operation
7        for (int[] operation : shift) {
8            int direction = operation[0];
9            int amount = operation[1];
10          
11            // Left shift (direction = 0) is represented as negative
12            // Right shift (direction = 1) is represented as positive
13            if (direction == 0) {
14                netShift -= amount;  // Left shift
15            } else {
16                netShift += amount;  // Right shift
17            }
18        }
19      
20        // Get the length of the string
21        int length = s.length();
22      
23        // Normalize the net shift to be within the range [0, length)
24        // This handles both positive and negative shifts
25        netShift = ((netShift % length) + length) % length;
26      
27        // Perform the actual string rotation
28        // Split the string at position (length - netShift) and swap the parts
29        String leftPart = s.substring(length - netShift);
30        String rightPart = s.substring(0, length - netShift);
31      
32        return leftPart + rightPart;
33    }
34}
35
1class Solution {
2public:
3    string stringShift(string s, vector<vector<int>>& shift) {
4        // Calculate net shift amount
5        int netShift = 0;
6      
7        // Process each shift operation
8        for (auto& operation : shift) {
9            // operation[0]: direction (0 = left, 1 = right)
10            // operation[1]: shift amount
11          
12            if (operation[0] == 0) {
13                // Left shift: treat as negative right shift
14                netShift -= operation[1];
15            } else {
16                // Right shift: add positive value
17                netShift += operation[1];
18            }
19        }
20      
21        // Get string length
22        int stringLength = s.size();
23      
24        // Normalize the net shift to be within [0, stringLength)
25        // This handles both negative and positive shifts, as well as shifts larger than string length
26        netShift = ((netShift % stringLength) + stringLength) % stringLength;
27      
28        // Perform the actual string rotation
29        // Right shift by netShift means:
30        // - Take last netShift characters and move to front
31        // - Append remaining characters from beginning
32        return s.substr(stringLength - netShift, netShift) + s.substr(0, stringLength - netShift);
33    }
34};
35
1/**
2 * Performs string shifting operations based on given shift instructions
3 * @param s - The input string to be shifted
4 * @param shift - Array of shift operations where each element is [direction, amount]
5 *                direction: 0 for left shift, 1 for right shift
6 *                amount: number of positions to shift
7 * @returns The final shifted string
8 */
9function stringShift(s: string, shift: number[][]): string {
10    // Calculate net shift amount
11    // Left shifts are negative, right shifts are positive
12    let netShift: number = 0;
13  
14    for (const [direction, amount] of shift) {
15        // If direction is 0 (left), subtract amount; if 1 (right), add amount
16        netShift += direction === 0 ? -amount : amount;
17    }
18  
19    // Normalize the shift to be within the string length bounds
20    netShift %= s.length;
21  
22    // Handle negative shifts by converting to equivalent positive shift
23    if (netShift < 0) {
24        netShift += s.length;
25    }
26  
27    // Perform the actual string rotation
28    // slice(-netShift) takes the last 'netShift' characters
29    // slice(0, -netShift) takes all characters except the last 'netShift' ones
30    return s.slice(-netShift) + s.slice(0, -netShift);
31}
32

Time and Space Complexity

The time complexity is O(n + m), where n is the length of the string s and m is the length of the array shift.

Breaking down the operations:

  • The sum operation with list comprehension iterates through all elements in shift, taking O(m) time
  • The modulo operation x %= len(s) takes O(1) time
  • The string slicing operations s[-x:] and s[:-x] each take O(n) time in the worst case
  • String concatenation of the two slices takes O(n) time

The total time complexity is O(m) + O(n) = O(n + m).

The space complexity is O(n) rather than O(1) as stated in the reference answer. This is because:

  • The string slicing operations s[-x:] and s[:-x] create new string objects
  • The concatenation creates another new string object for the result
  • In Python, strings are immutable, so these operations cannot be done in-place

The only way the space complexity could be considered O(1) is if we're only counting auxiliary space (excluding the output string), but the string operations inherently create new strings with O(n) space.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Forgetting to Handle Zero Net Shift

When the net shift is 0 (all shifts cancel out), using s[-0:] and s[:-0] in Python will both return empty strings, resulting in an incorrect empty result instead of the original string.

Why it happens: In Python, s[:-0] evaluates to an empty string because it means "from start to 0 characters from the end," which is nothing.

Solution: Add a special case check:

def stringShift(self, s: str, shift: List[List[int]]) -> str:
    net_shift = sum((amount if direction else -amount) for direction, amount in shift)
    net_shift %= len(s)
  
    # Handle the case when net_shift is 0
    if net_shift == 0:
        return s
  
    return s[-net_shift:] + s[:-net_shift]

2. Misunderstanding Shift Direction

Developers often confuse which direction corresponds to which operation. They might think:

  • Left shift moves characters to the left visually (wrong interpretation)
  • Right shift moves characters to the right visually (wrong interpretation)

Correct understanding:

  • Left shift (0): Takes from the left and moves to the end → "abc" becomes "bca"
  • Right shift (1): Takes from the right and moves to the beginning → "abc" becomes "cab"

3. Not Handling Negative Modulo Correctly

When the net shift is negative (more left shifts than right shifts), the modulo operation in Python handles it correctly, but developers might worry about edge cases or try to overcomplicate the solution.

Example: If net_shift = -7 and len(s) = 5:

  • Python's (-7) % 5 correctly gives 3 (equivalent to a right shift of 3)
  • No special handling needed for negative values in Python

4. Inefficient Multiple String Operations

Some might try to actually perform each shift operation individually:

# Inefficient approach - DON'T DO THIS
for direction, amount in shift:
    for _ in range(amount):
        if direction == 0:  # left shift
            s = s[1:] + s[0]
        else:  # right shift
            s = s[-1] + s[:-1]

This has O(m × k × n) time complexity where k is the average shift amount, which is much worse than the O(m + n) optimal solution.

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

Given a sorted array of integers and an integer called target, find the element that equals to the target and return its index. Select the correct code that fills the ___ in the given code snippet.

1def binary_search(arr, target):
2    left, right = 0, len(arr) - 1
3    while left ___ right:
4        mid = (left + right) // 2
5        if arr[mid] == target:
6            return mid
7        if arr[mid] < target:
8            ___ = mid + 1
9        else:
10            ___ = mid - 1
11    return -1
12
1public static int binarySearch(int[] arr, int target) {
2    int left = 0;
3    int right = arr.length - 1;
4
5    while (left ___ right) {
6        int mid = left + (right - left) / 2;
7        if (arr[mid] == target) return mid;
8        if (arr[mid] < target) {
9            ___ = mid + 1;
10        } else {
11            ___ = mid - 1;
12        }
13    }
14    return -1;
15}
16
1function binarySearch(arr, target) {
2    let left = 0;
3    let right = arr.length - 1;
4
5    while (left ___ right) {
6        let mid = left + Math.trunc((right - left) / 2);
7        if (arr[mid] == target) return mid;
8        if (arr[mid] < target) {
9            ___ = mid + 1;
10        } else {
11            ___ = mid - 1;
12        }
13    }
14    return -1;
15}
16

Recommended Readings

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

Load More