796. Rotate String
Problem Description
You are given two strings s
and goal
. Your task is to determine if string s
can be transformed into string goal
through a series of shift operations.
A shift operation takes the leftmost character of the string and moves it to the rightmost position. For instance:
- Starting with
s = "abcde"
- After 1 shift:
"bcdea"
(moved 'a' to the end) - After 2 shifts:
"cdeab"
(moved 'b' to the end) - After 3 shifts:
"deabc"
(moved 'c' to the end) - And so on...
The function should return true
if it's possible to obtain goal
from s
after any number of shifts (including zero shifts), and false
otherwise.
The solution leverages a clever observation: if goal
can be obtained by rotating s
, then goal
must appear as a substring within s + s
(the concatenation of s
with itself). This works because s + s
contains all possible rotations of s
as substrings. Additionally, the lengths of both strings must be equal for a valid rotation to exist.
For example, if s = "abcde"
, then s + s = "abcdeabcde"
, which contains all rotations: "abcde"
, "bcdea"
, "cdeab"
, "deabc"
, and "eabcd"
.
Intuition
The key insight comes from visualizing what happens when we continuously shift a string. If we keep shifting s = "abcde"
, we get:
- Original:
"abcde"
- 1 shift:
"bcdea"
- 2 shifts:
"cdeab"
- 3 shifts:
"deabc"
- 4 shifts:
"eabcd"
- 5 shifts:
"abcde"
(back to original)
Notice that after n
shifts (where n
is the length of the string), we return to the original string. This creates a cycle of all possible rotations.
Now, imagine writing the string twice in a row: "abcdeabcde"
. If we look at this doubled string, we can find every possible rotation as a substring of length n
:
- Starting at index 0:
"abcde"
(0 shifts) - Starting at index 1:
"bcdea"
(1 shift) - Starting at index 2:
"cdeab"
(2 shifts) - Starting at index 3:
"deabc"
(3 shifts) - Starting at index 4:
"eabcd"
(4 shifts)
This pattern reveals that s + s
contains all possible rotations of s
as contiguous substrings. Therefore, if goal
is a valid rotation of s
, it must appear somewhere within s + s
.
The length check len(s) == len(goal)
is necessary because a rotation preserves the string length. Without this check, we might incorrectly match substrings of different lengths.
This elegant approach reduces the rotation problem to a simple substring search, avoiding the need to explicitly generate and check each possible rotation.
Solution Approach
The implementation is remarkably concise, using a string concatenation and substring search approach:
def rotateString(self, s: str, goal: str) -> bool:
return len(s) == len(goal) and goal in s + s
Let's break down the solution step by step:
-
Length Check:
len(s) == len(goal)
- First, we verify that both strings have the same length
- This is a necessary condition since rotation doesn't change the string length
- If lengths differ, we can immediately return
false
-
String Concatenation:
s + s
- We create a new string by concatenating
s
with itself - For example, if
s = "abcde"
, thens + s = "abcdeabcde"
- This doubled string contains all possible rotations as substrings
- We create a new string by concatenating
-
Substring Search:
goal in s + s
- Python's
in
operator checks ifgoal
exists as a substring in the concatenated string - This operation has a time complexity of O(n) in Python's implementation using efficient string matching algorithms
- Python's
-
Combined Logic: The return statement uses
and
to ensure both conditions are met:- Equal lengths AND goal is found in the doubled string
Example Walkthrough:
- Input:
s = "abcde"
,goal = "cdeab"
- Check:
len("abcde") == len("cdeab")
→5 == 5
→True
- Concatenate:
"abcde" + "abcde" = "abcdeabcde"
- Search: Is
"cdeab"
in"abcdeabcde"
? → Yes (at index 2) - Result:
True and True
→True
Time Complexity: O(n) where n is the length of the string, for the substring search operation.
Space Complexity: O(n) for creating the concatenated string s + s
.
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 a concrete example with s = "abc"
and goal = "cab"
.
Step 1: Check if lengths are equal
len("abc") = 3
len("cab") = 3
- Lengths match ✓
Step 2: Create the doubled string
s + s = "abc" + "abc" = "abcabc"
Step 3: Visualize all rotations in the doubled string
"abcabc" ^^^ → "abc" (0 shifts - original) ^^^ → "bca" (1 shift - 'a' moved to end) ^^^ → "cab" (2 shifts - 'ab' moved to end)
Step 4: Check if goal exists in doubled string
- Is
"cab"
in"abcabc"
? - Yes! It appears starting at index 2
Step 5: Return result
- Both conditions satisfied: equal length AND substring found
- Return
True
Counter-example: If goal = "acb"
instead:
s + s = "abcabc"
- Is
"acb"
in"abcabc"
? No! - This makes sense because
"acb"
is not a rotation of"abc"
- it would require rearranging characters, not just shifting - Return
False
Solution Implementation
1class Solution:
2 def rotateString(self, s: str, goal: str) -> bool:
3 """
4 Check if string 'goal' can be obtained by rotating string 's'.
5
6 A rotation means moving some number of characters from the beginning
7 to the end of the string. For example, rotating "abcde" by 2 positions
8 gives "cdeab".
9
10 The key insight: if 'goal' is a rotation of 's', then 'goal' must be
11 a substring of 's + s'. This works because concatenating 's' with itself
12 contains all possible rotations of 's' as substrings.
13
14 Args:
15 s: The original string
16 goal: The target string to check if it's a rotation of s
17
18 Returns:
19 True if goal is a rotation of s, False otherwise
20 """
21 # First check if lengths are equal (necessary condition for rotation)
22 # Then check if goal appears as a substring in the concatenated string s + s
23 # Example: s = "abcde", s + s = "abcdeabcde"
24 # All rotations like "cdeab", "deabc" etc. will be substrings of "abcdeabcde"
25 return len(s) == len(goal) and goal in s + s
26
1class Solution {
2 /**
3 * Determines if string 'goal' can be obtained by rotating string 's'.
4 * A rotation means moving some number of characters from the beginning to the end.
5 *
6 * Algorithm approach:
7 * - If we concatenate 's' with itself (s + s), all possible rotations of 's'
8 * will appear as substrings in the concatenated string
9 * - For example: if s = "abcde", then s + s = "abcdeabcde"
10 * All rotations like "bcdea", "cdeab", "deabc", "eabcd" exist as substrings
11 *
12 * @param s The original string to be rotated
13 * @param goal The target string we want to achieve through rotation
14 * @return true if 'goal' is a rotation of 's', false otherwise
15 */
16 public boolean rotateString(String s, String goal) {
17 // First check: strings must have equal length for rotation to be possible
18 // Second check: if goal is a rotation of s, it must appear in (s + s)
19 return s.length() == goal.length() && (s + s).contains(goal);
20 }
21}
22
1class Solution {
2public:
3 bool rotateString(string s, string goal) {
4 // Check if lengths are equal (necessary condition for rotation)
5 // and if goal is a substring of s concatenated with itself
6 // This works because any rotation of s will appear as a substring in s+s
7 // Example: s = "abcde", goal = "cdeab"
8 // s + s = "abcdeabcde" which contains "cdeab"
9 return s.size() == goal.size() && strstr((s + s).data(), goal.data());
10 }
11};
12
1/**
2 * Determines if string 'goal' is a rotation of string 's'.
3 * A rotation means moving some characters from the beginning to the end.
4 * For example: "abcde" rotated by 2 positions becomes "cdeab".
5 *
6 * @param s - The original string
7 * @param goal - The target string to check if it's a rotation of s
8 * @returns true if goal is a rotation of s, false otherwise
9 */
10function rotateString(s: string, goal: string): boolean {
11 // First check: strings must have the same length to be rotations of each other
12 const isSameLength: boolean = s.length === goal.length;
13
14 // Second check: if s is a rotation of goal, then s will appear as a substring
15 // in the concatenation of goal with itself (goal + goal)
16 // Example: if s="cdeab" and goal="abcde", then "abcdeabcde" contains "cdeab"
17 const isRotation: boolean = (goal + goal).includes(s);
18
19 // Both conditions must be true for goal to be a valid rotation of s
20 return isSameLength && isRotation;
21}
22
Time and Space Complexity
Time Complexity: O(n²)
in the worst case, where n
is the length of string s
.
The algorithm first checks if lengths are equal (O(1)
), then concatenates s
with itself creating s + s
(O(n)
), and finally performs substring search using the in
operator to check if goal
exists in s + s
. The substring search operation in Python uses a pattern matching algorithm that has O(n*m)
complexity in the worst case, where n
is the length of the text being searched and m
is the length of the pattern. Since we're searching for goal
(length n
) in s + s
(length 2n
), this gives us O(2n * n) = O(n²)
worst-case time complexity.
Space Complexity: O(n)
, where n
is the length of string s
.
The concatenation s + s
creates a new string of length 2n
, which requires O(2n) = O(n)
additional space. The in
operator itself may use some additional space for the pattern matching algorithm, but this is typically O(1)
or at most O(n)
depending on the implementation, so the overall space complexity remains O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting the Length Check
One of the most common mistakes is omitting the length equality check and only relying on the substring search.
Incorrect Implementation:
def rotateString(self, s: str, goal: str) -> bool:
return goal in s + s # Missing length check!
Why it fails: Without the length check, the function would incorrectly return True
for cases where goal
is a substring but not a complete rotation.
- Example:
s = "abcde"
,goal = "cde"
s + s = "abcdeabcde"
contains"cde"
as a substring- But
"cde"
is NOT a rotation of"abcde"
(different lengths)
Solution: Always include the length check:
def rotateString(self, s: str, goal: str) -> bool:
return len(s) == len(goal) and goal in s + s
2. Edge Case: Empty Strings
While the current solution handles empty strings correctly, developers might overthink and add unnecessary special case handling.
Overcomplicated Implementation:
def rotateString(self, s: str, goal: str) -> bool:
if not s and not goal:
return True
if not s or not goal:
return False
return len(s) == len(goal) and goal in s + s
Why it's unnecessary: The original solution already handles empty strings correctly:
- If both are empty:
len("") == len("")
isTrue
, and"" in ""
isTrue
- If only one is empty: The length check fails, returning
False
Solution: Trust the elegance of the original solution - it handles edge cases naturally.
3. Attempting Manual Rotation Instead of Using the Concatenation Trick
Some developers might try to manually perform rotations, leading to more complex and error-prone code.
Inefficient Implementation:
def rotateString(self, s: str, goal: str) -> bool:
if len(s) != len(goal):
return False
for i in range(len(s)):
if s[i:] + s[:i] == goal: # Manual rotation
return True
return False
Why it's problematic:
- More complex code with higher chance of bugs
- O(n²) time complexity due to string slicing and comparison in a loop
- Less readable and harder to maintain
Solution: Use the concatenation approach for O(n) time complexity and cleaner code:
def rotateString(self, s: str, goal: str) -> bool:
return len(s) == len(goal) and goal in s + s
4. Misunderstanding the Problem: Confusing Shift with Other Operations
Some might confuse "shift" operations with character swapping or reversing.
Wrong Interpretation Example:
Thinking that transforming "abcde"
to "edcba"
(reverse) is possible through shifts. It's not - shifts only move characters from front to back, maintaining their relative order.
Solution: Remember that a shift operation only moves the leftmost character to the rightmost position. All valid rotations maintain the cyclic order of characters.
Which of the following array represent a max heap?
Recommended Readings
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
Runtime Overview When learning about algorithms and data structures you'll frequently encounter the term time complexity This concept is fundamental in computer science and offers insights into how long an algorithm takes to complete given a certain input size What is Time Complexity Time complexity represents the amount of time
Want a Structured Path to Master System Design Too? Don’t Miss This!