LeetCode 1427 - Perform String Shifts
This problem asks us to simulate a sequence of string shift operations on a given string s. Each operation in the shift
Difficulty: 🟢 Easy
Topics: Array, Math, String
Solution
Problem Understanding
This problem asks us to simulate a sequence of string shift operations on a given string s.
Each operation in the shift matrix contains two values:
-
direction -
0means shift left -
1means shift right -
amount -
the number of positions to shift
A left shift removes characters from the front and appends them to the back. For example:
"abcde" -> left shift by 2 -> "cdeab"
A right shift removes characters from the back and inserts them at the front:
"abcde" -> right shift by 2 -> "deabc"
The goal is to apply all operations in order and return the final transformed string.
The input constraints are small:
s.length <= 100shift.length <= 100
This means even a direct simulation approach would work within limits. However, the problem has a mathematical observation that allows a cleaner and more efficient solution.
The important insight is that multiple shifts can cancel each other out. For example:
- left by 3
- right by 3
results in no change at all.
Another important detail is that shifting by the length of the string produces the original string again. For example:
"abcdef" shifted right by 6 -> "abcdef"
So only the shift amount modulo len(s) matters.
Several edge cases are important:
- A shift amount may be
0, meaning no change. - The total effective shift may become negative if left shifts dominate.
- The total shift may exceed the string length many times.
- The string may have length
1, where every shift leaves the string unchanged.
The problem guarantees that the input format is valid, so we do not need to handle malformed operations.
Approaches
Brute Force Approach
The brute force solution directly simulates every operation exactly as described.
For each operation:
- If it is a left shift, repeatedly move characters from the front to the back.
- If it is a right shift, repeatedly move characters from the back to the front.
This produces the correct result because it follows the problem statement literally.
However, repeated character movement is inefficient. If the string has length n and there are m operations, each operation may cost up to O(n) time. In the worst case, the total complexity becomes O(m * n).
Although the constraints are small enough for this to pass, it performs unnecessary repeated work.
Optimal Approach
The key observation is that all shifts can be combined into one net shift.
A right shift increases the effective shift amount, while a left shift decreases it.
For example:
left 2
right 5
left 1
becomes:
-2 + 5 - 1 = +2
which means the final result is simply a right shift by 2.
After computing the total shift:
- Reduce it modulo the string length.
- Perform exactly one final rotation.
This avoids repeated intermediate transformations and makes the implementation much cleaner.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(m × n) | O(n) | Simulates every shift operation directly |
| Optimal | O(m + n) | O(n) | Combines all shifts into one net rotation |
Where:
mis the number of shift operationsnis the length of the string
Algorithm Walkthrough
- Initialize a variable called
net_shiftto0.
This variable stores the combined effect of all operations.
2. Iterate through every operation in shift.
Each operation contains:
directionamount
- Update the net shift based on the direction.
- If
direction == 1, addamountbecause right shifts move characters toward the front. - If
direction == 0, subtractamountbecause left shifts move characters toward the back.
- Reduce the total shift using modulo.
Since rotating a string by its own length returns the original string, compute:
net_shift %= len(s)
This keeps the shift within valid bounds.
- Construct the final rotated string.
A right shift by k means:
- take the last
kcharacters - place them in front
- append the remaining prefix
This becomes:
s[-k:] + s[:-k]
- Return the resulting string.
Why it works
Every left shift and right shift is a cyclic rotation. Cyclic rotations are additive, meaning multiple rotations can be combined into a single equivalent rotation.
Because:
- right shifts contribute positively
- left shifts contribute negatively
the sum of all operations represents the final net rotation.
Modulo arithmetic guarantees that rotations larger than the string length are reduced to their equivalent minimal rotation. Therefore, performing one final rotation produces exactly the same result as executing every operation individually.
Python Solution
from typing import List
class Solution:
def stringShift(self, s: str, shift: List[List[int]]) -> str:
net_shift = 0
n = len(s)
for direction, amount in shift:
if direction == 1:
net_shift += amount
else:
net_shift -= amount
net_shift %= n
return s[-net_shift:] + s[:-net_shift]
The implementation begins by initializing net_shift, which accumulates the total effect of all operations.
The loop processes every shift instruction. Right shifts increase the value, while left shifts decrease it. This converts many operations into one mathematical result.
After processing all operations, the modulo operation ensures the shift amount stays within the string length.
Finally, the string slicing expression performs the rotation efficiently:
s[-net_shift:]extracts the suffix that moves to the fronts[:-net_shift]extracts the remaining prefix
Concatenating them produces the correctly rotated string.
One subtle detail is that Python slicing handles 0 cleanly:
s[-0:] == s
s[:-0] == ""
So the implementation works even when the effective shift is zero.
Go Solution
func stringShift(s string, shift [][]int) string {
netShift := 0
n := len(s)
for _, operation := range shift {
direction := operation[0]
amount := operation[1]
if direction == 1 {
netShift += amount
} else {
netShift -= amount
}
}
netShift %= n
if netShift < 0 {
netShift += n
}
return s[n-netShift:] + s[:n-netShift]
}
The Go implementation follows the same logic as the Python version.
One important difference is modulo behavior. In Python, negative modulo results are automatically converted into positive values. In Go, negative values remain negative, so we explicitly adjust:
if netShift < 0 {
netShift += n
}
Go strings are immutable, just like Python strings, so constructing the final answer requires concatenating slices into a new string.
Worked Examples
Example 1
Input:
s = "abc"
shift = [[0,1],[1,2]]
Initial state:
| Step | Operation | Net Shift |
|---|---|---|
| Start | None | 0 |
| 1 | Left shift by 1 | -1 |
| 2 | Right shift by 2 | 1 |
Now compute:
1 % 3 = 1
Final rotation is right by 1.
Split the string:
| Part | Value |
|---|---|
| Last 1 character | "c" |
| Remaining prefix | "ab" |
Result:
"c" + "ab" = "cab"
Final answer:
"cab"
Example 2
Input:
s = "abcdefg"
shift = [[1,1],[1,1],[0,2],[1,3]]
Track the net shift:
| Step | Operation | Net Shift |
|---|---|---|
| Start | None | 0 |
| 1 | Right shift by 1 | 1 |
| 2 | Right shift by 1 | 2 |
| 3 | Left shift by 2 | 0 |
| 4 | Right shift by 3 | 3 |
Now compute:
3 % 7 = 3
Final rotation is right by 3.
Split the string:
| Part | Value |
|---|---|
| Last 3 characters | "efg" |
| Remaining prefix | "abcd" |
Result:
"efg" + "abcd" = "efgabcd"
Final answer:
"efgabcd"
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(m + n) | We process all operations once, then build the final rotated string once |
| Space | O(n) | The resulting rotated string requires new string storage |
The algorithm performs a single pass through the shift array and a single final string construction. Since string slicing and concatenation create a new string of size n, the overall space complexity is linear in the string length.
Test Cases
from typing import List
class Solution:
def stringShift(self, s: str, shift: List[List[int]]) -> str:
net_shift = 0
n = len(s)
for direction, amount in shift:
if direction == 1:
net_shift += amount
else:
net_shift -= amount
net_shift %= n
return s[-net_shift:] + s[:-net_shift]
sol = Solution()
assert sol.stringShift("abc", [[0, 1], [1, 2]]) == "cab" # Example 1
assert sol.stringShift("abcdefg", [[1, 1], [1, 1], [0, 2], [1, 3]]) == "efgabcd" # Example 2
assert sol.stringShift("a", [[0, 100]]) == "a" # Single character string
assert sol.stringShift("abcde", [[1, 5]]) == "abcde" # Shift equal to string length
assert sol.stringShift("abcde", [[1, 10]]) == "abcde" # Shift multiple of length
assert sol.stringShift("abcde", [[0, 1]]) == "bcdea" # Simple left shift
assert sol.stringShift("abcde", [[1, 1]]) == "eabcd" # Simple right shift
assert sol.stringShift("abcdef", [[0, 2], [1, 2]]) == "abcdef" # Shifts cancel out
assert sol.stringShift("abcdef", [[0, 8]]) == "cdefab" # Large left shift
assert sol.stringShift("abcdef", [[1, 8]]) == "efabcd" # Large right shift
assert sol.stringShift("xyz", [[0, 0], [1, 0]]) == "xyz" # Zero shifts only
| Test | Why |
|---|---|
"abc" with mixed shifts |
Validates the basic example |
"abcdefg" with multiple operations |
Validates combining many shifts |
| Single character string | Ensures trivial strings work correctly |
| Shift equal to length | Confirms modulo behavior |
| Shift larger than length | Tests repeated rotations |
| Simple left shift | Verifies left rotation logic |
| Simple right shift | Verifies right rotation logic |
| Canceling operations | Ensures net shift computation works |
| Large left shift | Tests modulo reduction |
| Large right shift | Tests modulo reduction |
| Zero shifts | Ensures no-op operations behave correctly |
Edge Cases
One important edge case occurs when the total shift is larger than the string length. For example, shifting a string of length 5 by 12 positions is equivalent to shifting by 2. A naive implementation that repeatedly rotates characters may waste time doing unnecessary work. This implementation handles the issue correctly by reducing the final shift with modulo arithmetic.
Another important case is when left and right shifts cancel each other out. For example:
left 3
right 3
should leave the string unchanged. Bugs often appear when implementations process shifts independently instead of combining them mathematically. By accumulating a single net_shift, the algorithm naturally handles cancellation correctly.
A third edge case involves strings of length 1. Any shift operation on a single-character string must return the original string. Some implementations may accidentally produce invalid slice boundaries or redundant work. Because the modulo result becomes 0, the slicing expression safely returns the original string unchanged.
A final subtle case is when the effective shift becomes negative. For example:
left 4
right 1
produces a net shift of -3. The modulo operation converts this into the equivalent positive rotation. This guarantees the final slicing indices remain valid and consistent.