A clear explanation of checking whether one string can become another by repeated left rotations.
Problem Restatement
We are given two strings:
s
goalA shift on s means moving the leftmost character to the rightmost position.
For example:
abcde -> bcdeaWe need to return True if s can become goal after some number of shifts. Otherwise, return False.
Zero shifts are allowed, so if s == goal, the answer is True.
Input and Output
| Item | Meaning |
|---|---|
| Input | Two strings s and goal |
| Output | True if goal is a rotation of s, otherwise False |
| Operation | Move the first character of s to the end |
| Allowed shifts | Any number, including zero |
| Constraint | Both strings contain lowercase English letters |
Function shape:
class Solution:
def rotateString(self, s: str, goal: str) -> bool:
...Examples
Example 1:
s = "abcde"
goal = "cdeab"Apply two shifts:
abcde -> bcdea -> cdeabSo the answer is:
TrueExample 2:
s = "abcde"
goal = "abced"No sequence of left rotations can produce "abced".
The answer is:
FalseExample 3:
s = "abc"
goal = "abc"Zero shifts are allowed.
The answer is:
TrueFirst Thought: Simulate All Rotations
A direct approach is to rotate s one step at a time.
For each rotation, check whether it equals goal.
For example:
abcde
bcdea
cdeab
deabc
eabcdIf any rotation equals goal, return True.
This works, but it performs repeated string slicing and comparison.
Key Insight
All rotations of s appear as substrings inside:
s + sFor example:
s = "abcde"
s + s = "abcdeabcde"The rotations are visible inside the doubled string:
abcdeabcde
abcde
bcdea
cdeab
deabc
eabcdSo goal is a rotation of s exactly when:
sandgoalhave the same length.goalappears insides + s.
Algorithm
- If
len(s) != len(goal), returnFalse. - Build the doubled string
s + s. - Return whether
goalis a substring ofs + s.
Correctness
If goal is a rotation of s, then goal is formed by cutting s at some position and swapping the two parts.
So for some split:
s = left + right
goal = right + leftThe doubled string is:
s + s = left + right + left + rightThis contains:
right + leftas a contiguous substring. Therefore, goal appears in s + s.
Conversely, if goal has the same length as s and appears inside s + s, then it must start at some position within the first copy of s. The length-len(s) substring starting there is exactly one cyclic rotation of s. Therefore, goal is a valid rotation.
So the algorithm returns True exactly when goal is a rotation of s.
Complexity
Let n = len(s).
| Metric | Value | Why |
|---|---|---|
| Time | O(n) average | We build s + s and search for goal |
| Space | O(n) | The doubled string stores 2n characters |
Depending on the string-search implementation, the theoretical worst case of substring search may vary, but this solution is the intended concise approach.
Implementation
class Solution:
def rotateString(self, s: str, goal: str) -> bool:
return len(s) == len(goal) and goal in s + sCode Explanation
First, the lengths must match:
len(s) == len(goal)A rotation never changes string length.
Then we check whether goal appears in the doubled string:
goal in s + sIf both conditions are true, goal is a rotation of s.
Simulation Version
This version follows the operation directly.
class Solution:
def rotateString(self, s: str, goal: str) -> bool:
if len(s) != len(goal):
return False
current = s
for _ in range(len(s)):
if current == goal:
return True
current = current[1:] + current[0]
return FalseThis is easier to connect to the problem statement, but less concise.
Testing
def run_tests():
sol = Solution()
assert sol.rotateString("abcde", "cdeab") is True
assert sol.rotateString("abcde", "abced") is False
assert sol.rotateString("abc", "abc") is True
assert sol.rotateString("a", "a") is True
assert sol.rotateString("aa", "aa") is True
assert sol.rotateString("abc", "cab") is True
assert sol.rotateString("abc", "acb") is False
assert sol.rotateString("abc", "abcd") is False
print("all tests passed")
run_tests()Test coverage:
| Test | Why |
|---|---|
"abcde" to "cdeab" | Standard valid rotation |
"abcde" to "abced" | Same characters but invalid order |
| Same string | Zero shifts |
| Single character | Smallest input |
| Repeated characters | Rotation with duplicates |
"abc" to "cab" | Last rotation position |
| Different lengths | Impossible immediately |