Topics › Strings › Basic and Easy String Problems
Rotate String
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given two strings s and goal. A shift (or rotation) of s moves its first character to the end: "abcde" becomes "bcdea". Return true if goal can be obtained from s after some number of shifts (zero shifts included), and false otherwise.
Constraints: 0 ≤ |s|, |goal| ≤ 100 in the LeetCode version; the approaches below scale to 10^5 characters. Lowercase English letters.
Example 1
s = "abcde", goal = "cdeab"
answer = true // two shifts: abcde -> bcdea -> cdeab
Example 2
s = "abcde", goal = "abced"
answer = false // same letters, but no rotation of abcde ends in "ed"
Example 3
s = "aaab", goal = "abaa"
answer = true // abaa is the rotation that starts at index 2
Approach 1: Brute force, generate every rotation
Intuition. There are only n distinct rotations of a string of length n (shifting n times brings it back to the start), so we can simply produce each of them and compare with goal. The rotation after k shifts is the suffix starting at k followed by the prefix of length k: s.substring(k) + s.substring(0, k).
Algorithm.
- If the lengths differ, return
false. Ifsis empty, returntrue(the empty string is a rotation of itself). - For
kfrom0ton - 1: buildrotated = s[k..n) + s[0..k); if it equalsgoal, returntrue. - Return
false.
static boolean bruteForce(String s, String goal) {
if (s.length() != goal.length()) return false;
if (s.isEmpty()) return true;
int n = s.length();
for (int k = 0; k < n; k++) { // k = number of left shifts
String rotated = s.substring(k) + s.substring(0, k);
if (rotated.equals(goal)) return true;
}
return false;
}
Complexity. Time O(n^2): n rotations, each costing O(n) to build and compare. Space O(n) for the current rotation.
An in-place variant compares s[(k + i) mod n] with goal[i] for each i without building the rotated string; that removes the allocation but is still O(n^2) in the worst case.
Approach 2: Better, every rotation is a substring of s + s
Intuition. Write s twice in a row: "abcdeabcde". Every rotation of s appears in this doubled string as a contiguous window of length n: the rotation that starts at index k is exactly (s + s)[k..k+n). The converse also holds: any window of length n in s + s that starts at k < n is a rotation of s, and the window starting at k = n is s itself again. So goal is a rotation of s if and only if goal has length n and occurs in s + s. The length check is essential: "ab" is a substring of "abcabc" but is not a rotation of "abc".
Algorithm.
- If the lengths differ, return
false. - Return whether
s + scontainsgoal.
static boolean better(String s, String goal) {
return s.length() == goal.length() && (s + s).contains(goal);
}
Complexity. Time: String.contains in the JDK is a straightforward search whose worst case is O(n · m), here O(n^2), but on typical inputs it runs close to linear. Space O(n) for the doubled string.
This is the version to write in an interview first: one line, obviously correct once the doubling idea is explained. If the interviewer asks for a guaranteed linear bound, that is the cue for Approach 3.
Approach 3: Optimal, the same idea with a KMP search
Intuition. The only non-linear step above is the substring search, and the Knuth-Morris-Pratt algorithm searches for a pattern of length m in a text of length L in O(L + m) time regardless of the input. KMP precomputes, for the pattern goal, the table lps[i] = length of the longest proper prefix of goal[0..i] that is also a suffix of it. During the scan it keeps j, the number of pattern characters matched so far. On a mismatch it does not restart from the beginning of the pattern; it falls back to j = lps[j - 1], the longest border of what was matched, because those characters are already known to line up with the text.
Algorithm.
- If the lengths differ, return
false. Ifsis empty, returntrue. - Let
text = s + sand computelpsforgoal. - Scan
textwithj = 0:- While
j > 0andtext[i] != goal[j], setj = lps[j - 1]. - If
text[i] == goal[j], incrementj. - If
j == |goal|, a full match ends ati: returntrue.
- While
- Return
false.
static boolean optimal(String s, String goal) {
if (s.length() != goal.length()) return false;
if (s.isEmpty()) return true;
String text = s + s;
int[] lps = buildLps(goal);
int j = 0; // number of characters of goal matched so far
for (int i = 0; i < text.length(); i++) {
while (j > 0 && text.charAt(i) != goal.charAt(j)) j = lps[j - 1];
if (text.charAt(i) == goal.charAt(j)) j++;
if (j == goal.length()) return true;
}
return false;
}
static int[] buildLps(String p) {
int[] lps = new int[p.length()];
int len = 0;
for (int i = 1; i < p.length(); i++) {
while (len > 0 && p.charAt(i) != p.charAt(len)) len = lps[len - 1];
if (p.charAt(i) == p.charAt(len)) len++;
lps[i] = len;
}
return lps;
}
Dry run of the search on s = "abcde", goal = "cdeab", text = "abcdeabcde". The pattern has no repeated characters, so lps = [0, 0, 0, 0, 0].
| i | text[i] | j before | action | j after |
|---|---|---|---|---|
| 0 | a | 0 | a != c, j stays 0 | 0 |
| 1 | b | 0 | b != c | 0 |
| 2 | c | 0 | match | 1 |
| 3 | d | 1 | match | 2 |
| 4 | e | 2 | match | 3 |
| 5 | a | 3 | match | 4 |
| 6 | b | 4 | match, j == 5, return true |
5 |
The fallback shows its value on patterns with repeated characters. Searching for "aab" (lps = [0, 1, 0]) in "aaab": after matching "aa" the scan sees a third a where the pattern wants b. A naive search would restart from scratch one position later; KMP sets j = lps[1] = 1, keeping one matched a, compares the new a with goal[1], matches, and then matches b on the next character. No text character is ever re-read. In the "aaab"/"abaa" test of the Java file the fallbacks are from j = 1 to lps[0] = 0 at i = 1 and i = 2, and the match is found at i = 5.
Complexity. Time O(n): building lps is O(n) and the scan over the 2n-character text is O(n), because j increases at most 2n times and every fallback decreases it, so the total number of fallbacks is bounded by the number of increases. Space O(n) for text and lps.
The j == goal.length() check happens right after the increment and returns at once, so goal.charAt(j) is never evaluated with j == |goal|. A general-purpose KMP that reports all occurrences would set j = lps[j - 1] here and continue instead.
Edge cases and pitfalls
- Different lengths. Always return
false; this check is what makes thes + strick correct. - Both strings empty. The answer is
true.bruteForceandoptimalreturn early;betterrelies on"".contains("")beingtrue. - Zero shifts.
sis a rotation of itself;"abc"/"abc"must returntrue. InbruteForcethis is thek = 0iteration, in the others it is the occurrence ofgoalat index0oftext. - Same letters, different order.
"abcd"/"abdc"is an anagram but not a rotation. Comparing sorted characters or counts is not enough; the cyclic order matters. - All characters equal.
"aaaa"/"aaaa"istrue. KMP handles it:lps = [0, 1, 2, 3]and the first four characters oftextalready match. - Forgetting the empty-string guard in KMP. With
|goal| == 0,goal.charAt(0)would throw. The guard also makeslps[j - 1]safe, sincej > 0whenever it is used. - Doubling cost.
s + sallocates2ncharacters. For a strictO(1)-space answer, run the KMP scan over indices0..2n-1and reads.charAt(i % n)instead of materialising the doubled string.
Related problems
- KMP Algorithm or LPS array covers the prefix-function construction used in the optimal approach.
- Rabin Karp Algorithm is an alternative linear-expected-time substring search that works just as well in Approach 3.
- Left Rotate Array by K Places performs the rotation itself on an array.
- Check if two strings are anagram of each other is the next problem on the sheet, and the “same letters, different order” edge case above.