⚙️AlgorithmIntermediate

Minimum Rotation

Key Points

  • The minimum rotation of a string is the lexicographically smallest string you can get by cutting it at some position and swapping the two parts.
  • Booth's algorithm finds the index of the minimum rotation in O(n) time using a KMP-like failure function built over s + s.
  • Duval's algorithm (via Lyndon factorization) also yields an O(n) method; the start of the minimum rotation is the start of a Lyndon word in s + s.
  • A safe and fast practical trick is to conceptually double the string (s + s) so you can compare any rotation as a length-n substring.
  • When strings are periodic, multiple rotations may be equal; algorithms typically return the first (smallest index) minimum rotation.
  • Use minimum rotation to canonicalize cyclic sequences (necklaces), compare circular strings, or deduplicate shapes/polygons.
  • Avoid O() methods like sorting all rotations with an O(n)-per-comparison comparator; use Booth or Duval for true O(n) time.
  • Memory use is linear; implementations often store s + s and small auxiliary arrays.

Prerequisites

  • Lexicographic order and string comparisonUnderstanding how dictionary ordering works is essential for defining and comparing rotations.
  • KMP and the failure (prefix) functionBooth’s algorithm mirrors KMP’s skipping logic; familiarity helps understand the O(n) guarantee.
  • Combinatorics on words (Lyndon words)Duval’s algorithm and the factorization properties explain why the minimum rotation starts at Lyndon boundaries.
  • Amortized analysisJustifies why pointer movement and failure-link jumps lead to linear total work.
  • Modular arithmetic / indexingHelps reason about circular access patterns and the doubling trick s + s.

Detailed Explanation

Tap terms for definitions

01Overview

Hook: Imagine a circular billboard made from a long banner that loops. You can choose where to place the seam before you print the letters, and you want the overall billboard text to look as early as possible in dictionary order. Where should the seam go? Concept: The minimum rotation problem asks for the lexicographically smallest rotation of a string. Given a string s of length n, a rotation R_k(s) cuts s after position k − 1 and swaps the two parts. Among the n possible rotations, we want the smallest one in dictionary order. A naive approach is to generate all n rotations and pick the minimum, but this is too slow for large strings. Example: For s = "bca", the rotations are "bca", "cab", and "abc". The lexicographically smallest is "abc", which begins at index 2 of the original string. Efficient algorithms like Booth's algorithm and Duval's algorithm can find index 2 in O(n) time without generating and storing all rotations.

02Intuition & Analogies

Think of beads on a necklace. The necklace has no start or end, but if we want to write down the beads as a string, we must pick a start bead and go around. Different choices of the start bead give different linear strings—these are the rotations. If you wanted a standard, consistent description for comparing necklaces, you'd choose the rotation that comes first in dictionary order. That is the minimum rotation. Another image: slide a window of length n over the doubled string s + s. Because every rotation is simply some length-n substring of s + s, the problem reduces to: among all windows of length n that start within the first n positions, find the smallest window in lexicographic order. Why can we do it in linear time? Algorithms like KMP (Knuth–Morris–Pratt) avoid re-checking characters by remembering partial matches using a failure (prefix) function. Booth’s algorithm borrows this idea: as you compare two candidate rotations character-by-character, when a mismatch occurs, the failure information tells you how far you can jump ahead without missing a potentially better start. Duval’s perspective uses Lyndon words—strings that are strictly smaller than any of their nontrivial rotations. When you factor s + s into Lyndon words, the beginning of the minimum rotation must align with the beginning of one of these words. Scanning these boundaries cleverly lets you decide the minimum rotation with only O(n) character inspections. The core idea is to avoid comparing the same pairs of characters again and again. Either remember progress (Booth/KMP-like) or only move forward across Lyndon boundaries (Duval), and never backtrack more than a constant number of times per character.

03Formal Definition

Let s be a string of length n over a totally ordered alphabet (e.g., ASCII). For k in [0, n − 1], define the k-rotation (s) by (s) = s[k] s[k+1] ... s[n-1] s[0] s[1] ... s[k-1]. Lexicographic strings is defined as: for strings x and y, if there exists the smallest index i such that x[i] ≠ y[i], and x[i] < y[i]; if all compared characters are equal and < , then . The minimum rotation index k* is k* = argmin_{0 k < n} (s), with ties broken by the smallest k. Equivalently, if we define , then (s) is exactly t[k..k+n-1]. Therefore, k* = argmin_{0 k < n} t[k..k+n-1]. Booth’s algorithm computes k* in O(n) time by maintaining a KMP-style failure array over t and a current best start k; on mismatches, it decides whether the current window is better or worse and skips ahead accordingly. Duval’s algorithm factors t into a nonincreasing sequence of Lyndon words (, , ..., ). The start of the minimal rotation is the start of some factor boundary in t with scanning these boundaries and advancing in O(n) total time yields k*.

04When to Use

Use minimum rotation whenever objects are cyclic but must be stored or compared linearly in a canonical way.

  • Necklace canonicalization: Treat strings equal under rotation as the same object by mapping each to its minimum rotation. This is common in counting unique circular patterns, bracelets, or cyclic codes.
  • Polygon or cycle canonical labeling: For a cycle graph or polygon described by a sequence of vertex labels or edge lengths, rotations correspond to choosing a different start vertex. Minimum rotation gives a canonical representative for comparisons and hashing.
  • Circular string matching: To check if a pattern occurs in a circular text, minimum rotation can normalize the text or the pattern before indexing or hashing.
  • Compression and combinatorics on words: Lyndon factorization (Duval) and minimal rotations are used in proofs and constructions (e.g., de Bruijn sequences, Lyndon bases) and can serve as building blocks for more complex string algorithms.
  • Competitive programming (CF 1700–2100): Problems asking you to group rotated-equal strings, compare cycles, or pick lexicographically optimal cyclic shifts can be solved in O(n) per string using Booth or Duval, avoiding pitfalls of O(n^2) approaches.

⚠️Common Mistakes

  1. Sorting with an O(n)-time comparator on rotations: Writing a comparator that compares two rotations in up to O(n) time and then using sort leads to O(n^2 log n) or worse. Instead, use a linear-time algorithm (Booth/Duval) to find the minimum rotation directly.
  2. Mishandling periodic strings: If s has period p (e.g., s = "ababab"), multiple rotations are equal. Algorithms typically return the smallest index k among all minimum rotations. Do not assume uniqueness of the index unless the string is a Lyndon word.
  3. Off-by-one and window bounds with s + s: Remember that only starts k in [0, n − 1] are valid; windows starting at k ≥ n are not rotations of s but of s shifted beyond its length.
  4. Rebuilding strings unnecessarily: Constructing every rotated string or calling substr in inner loops causes quadratic behavior. Compare characters in-place using indices into s + s.
  5. Character set and comparison issues: Ensure you compare bytes consistently (signed char vs. unsigned char can matter if using extended ASCII). For Unicode, work with code points, not bytes, or define the intended ordering explicitly.
  6. Returning the rotated string vs. the index: Many problems want the index of the cut, not the rotated string. Keep both utilities handy, but avoid extra copies when only the index is needed.
  7. Mixing 1-based and 0-based indexing: The standard implementations return 0-based indices. Convert carefully if the problem statement uses 1-based.

Key Formulas

Rotation definition

Explanation: The k-rotation of s moves the first k characters to the end. We consider all k from 0 to n−1.

Minimum rotation index

Explanation: Among all rotations, pick the one that is smallest in lexicographic order; if tied, the smallest k is chosen. This defines the target index we want to compute.

Doubling trick

Explanation: Each rotation of s is a length-n substring of t = s + s. So finding the minimum rotation is equivalent to finding the smallest such window in t.

Failure (prefix) function

Explanation: The failure function at i is the length of the longest proper prefix of t[0..i] that is also a suffix. Booth uses a KMP-like recurrence over t to skip comparisons.

Time complexity of Booth/Duval

Explanation: Both Booth's algorithm and Duval's algorithm inspect each character a constant number of times, leading to linear time in the length of the string.

Period from prefix function

Explanation: If the string is fully periodic, this yields the smallest period p. Then there are n/p equal minimum rotations; the algorithms return the first index.

Chen–Fox–Lyndon factorization

Explanation: Any string decomposes uniquely into a nonincreasing sequence of Lyndon words. Minimum rotation starts at the beginning of some Lyndon factor in s + s.

Complexity Analysis

Let n be the length of the input string s. Booth’s algorithm (failure-function variant) operates over the doubled string of length 2n. It maintains a current best start k and a KMP-like failure array f for t but indexed relative to k. Each iteration advances j by 1 while possibly updating k (jumping forward) and updating f at position j − k. The key amortization is that k only increases, j only increases, and every time we decrease i via , we strictly reduce i. Therefore, every character of t is compared O(1) times on average. The total time is O(n), and the space is O(n) for the failure array (plus O(n) for t if stored explicitly). Duval’s linear-time minimum rotation routine also runs over using three pointers i, j, and k. Pointer j advances monotonically; pointer i jumps forward by blocks of size (j − k); pointer k never exceeds i + (j − i − 1). Each character comparison either advances j or i, ensuring a total of O(n) comparisons. Space usage is O(1) apart from storing t. A naive approach—building all rotations and taking the minimum—takes O() time and O() space if you store them all, or O() time and O(n) space if you build and compare on the fly. Sorting rotations using a comparator that compares up to n characters per comparison leads to O( log n) time; avoid this in competitive programming. If you must compare two given rotations i and j once, a modular-index comparator costs O(n) time and O(1) space. For comparing two different cyclic strings A and B, computing each minimum rotation by Booth and then comparing the canonical strings yields O( + ) time overall.

Code Examples

Booth's algorithm (failure-function variant) for minimum rotation
1#include <bits/stdc++.h>
2using namespace std;
3
4// Returns the 0-based index k such that rotation R_k(s) is lexicographically minimum.
5// Implements Booth's algorithm using a KMP-like failure function over t = s + s.
6int booth_min_rotation_index(const string &s) {
7 int n = (int)s.size();
8 if (n == 0) return 0; // define empty string's min rotation index as 0
9 string t = s + s; // length 2n
10 int m = (int)t.size(); // 2n
11 vector<int> f(m, -1); // failure function relative to current best start
12 int k = 0; // best rotation start seen so far in t
13
14 for (int j = 1; j < m; ++j) {
15 // i is length of current border relative to k
16 int i = f[j - k - 1];
17 // Fall back using failure links while mismatch persists
18 while (i != -1 && t[j] != t[k + i + 1]) {
19 if (t[j] < t[k + i + 1]) {
20 // Found a smaller character; new best start is j - i - 1
21 k = j - i - 1;
22 }
23 i = f[i];
24 }
25 // Now either i == -1 or t[j] == t[k + i + 1]
26 if (t[j] != t[k + i + 1]) {
27 // No extension possible; border length becomes -1
28 if (t[j] < t[k + i + 1]) {
29 // New better rotation starts at position j
30 k = j;
31 }
32 f[j - k] = -1;
33 } else {
34 // Extend border
35 f[j - k] = i + 1;
36 }
37 // Optimization: once k reaches n, the answer modulo n is fixed
38 if (k >= n) break;
39 }
40 return k % n;
41}
42
43// Build the minimum rotation string using the index.
44string booth_min_rotation(const string &s) {
45 if (s.empty()) return s;
46 int k = booth_min_rotation_index(s);
47 return s.substr(k) + s.substr(0, k);
48}
49
50int main() {
51 ios::sync_with_stdio(false);
52 cin.tie(nullptr);
53
54 vector<string> tests = {
55 "bca", // -> abc
56 "aaaa", // -> aaaa (many equal rotations)
57 "caba", // -> abac
58 "ababab", // -> ababab (many equal rotations)
59 "dbcaacb" // random
60 };
61
62 for (const auto &s : tests) {
63 int idx = booth_min_rotation_index(s);
64 string rot = booth_min_rotation(s);
65 cout << "s = " << s << ", index = " << idx << ", min rotation = " << rot << "\n";
66 }
67 return 0;
68}
69

This implementation doubles the string to linearize cyclic access and maintains a KMP-like failure array tied to the current best start k. When a mismatch occurs, the algorithm either advances using failure links or updates k if it discovers a lexicographically smaller candidate. Each character is processed a constant number of times, so the runtime is linear. The helper builds the resulting minimum rotation using the computed index.

Time: O(n)Space: O(n)
Duval-based linear algorithm and Lyndon factorization insight
1#include <bits/stdc++.h>
2using namespace std;
3
4// Duval's algorithm to factorize a string into Lyndon words.
5// Returns half-open intervals [l, r) of each Lyndon factor in order.
6vector<pair<int,int>> lyndon_factorization(const string &s) {
7 int n = (int)s.size();
8 vector<pair<int,int>> res;
9 int i = 0;
10 while (i < n) {
11 int j = i + 1, k = i;
12 while (j < n && s[k] <= s[j]) {
13 if (s[k] < s[j]) k = i; // reset k when strictly smaller seen
14 else ++k; // advance k when equal
15 ++j;
16 }
17 // Now s[i..k] is the Lyndon word, and it repeats (j - k) times
18 while (i <= k) {
19 res.emplace_back(i, i + (j - k));
20 i += (j - k);
21 }
22 }
23 return res;
24}
25
26// Linear-time minimum rotation using Duval's idea (often presented as Booth's other variant).
27// Returns the 0-based index of the minimum rotation.
28int duval_min_rotation_index(const string &s) {
29 int n = (int)s.size();
30 if (n == 0) return 0;
31 string t = s + s; // length 2n
32 int i = 0, ans = 0;
33 while (i < n) { // only need starts < n
34 ans = i;
35 int j = i + 1, k = i;
36 // Find the first position where t[k] < t[j] or t[k] > t[j]
37 while (j < i + n && t[k] <= t[j]) {
38 if (t[k] < t[j]) k = i; // reset k on strictly smaller
39 else ++k; // advance k on equal
40 ++j;
41 }
42 // Skip over the block of size (j - k)
43 while (i <= k) i += (j - k);
44 }
45 return ans;
46}
47
48string duval_min_rotation(const string &s) {
49 if (s.empty()) return s;
50 int idx = duval_min_rotation_index(s);
51 return s.substr(idx) + s.substr(0, idx);
52}
53
54int main() {
55 ios::sync_with_stdio(false);
56 cin.tie(nullptr);
57
58 vector<string> tests = {
59 "bca", "aaaa", "caba", "ababab", "zzzxyzz"
60 };
61
62 for (const auto &s : tests) {
63 int idx = duval_min_rotation_index(s);
64 auto factors = lyndon_factorization(s + s);
65 cout << "s = " << s << "\n";
66 cout << " min-rotation index (Duval-based) = " << idx << ", string = "
67 << s.substr(idx) + s.substr(0, idx) << "\n";
68 cout << " Lyndon factors of s+s (as [l,r)):\n ";
69 for (auto [l, r] : factors) {
70 cout << "[" << l << "," << r << ") '" << (s + s).substr(l, r - l) << "' ";
71 }
72 cout << "\n\n";
73 }
74 return 0;
75}
76

The first function computes the Lyndon factorization of any string in O(n). The second function is a linear-time routine (often described as Duval’s or a Duval-style variant of Booth) to find the minimum rotation index without explicitly listing all factors. It advances pointers i, j, k over s + s so each character is considered a constant number of times.

Time: O(n)Space: O(n) for t = s + s; O(1) extra for the index algorithm, O(k) to store factors
Cyclic comparison and canonicalization of necklaces (deduplicate under rotation)
1#include <bits/stdc++.h>
2using namespace std;
3
4// Booth: linear-time minimum rotation index
5int booth_min_rotation_index(const string &s) {
6 int n = (int)s.size();
7 if (n == 0) return 0;
8 string t = s + s;
9 int m = (int)t.size();
10 vector<int> f(m, -1);
11 int k = 0;
12 for (int j = 1; j < m; ++j) {
13 int i = f[j - k - 1];
14 while (i != -1 && t[j] != t[k + i + 1]) {
15 if (t[j] < t[k + i + 1]) k = j - i - 1;
16 i = f[i];
17 }
18 if (t[j] != t[k + i + 1]) {
19 if (t[j] < t[k + i + 1]) k = j;
20 f[j - k] = -1;
21 } else {
22 f[j - k] = i + 1;
23 }
24 if (k >= n) break;
25 }
26 return k % n;
27}
28
29// Returns the canonical (minimum rotation) representative of s
30string canonical_necklace(const string &s) {
31 if (s.empty()) return s;
32 int k = booth_min_rotation_index(s);
33 return s.substr(k) + s.substr(0, k);
34}
35
36// Compares two cyclic strings A and B lexicographically by their canonical representatives
37int compare_cyclic(const string &A, const string &B) {
38 string a = canonical_necklace(A);
39 string b = canonical_necklace(B);
40 if (a < b) return -1;
41 if (a > b) return 1;
42 return 0;
43}
44
45int main() {
46 ios::sync_with_stdio(false);
47 cin.tie(nullptr);
48
49 // Example: deduplicate strings up to rotation
50 vector<string> S = {"bca", "abc", "cab", "aaaa", "aa", "a", "xyzz", "zzxy", "zzyx"};
51 unordered_set<string> uniq;
52 for (auto &s : S) uniq.insert(canonical_necklace(s));
53
54 cout << "Unique necklaces (canonical forms):\n";
55 for (const auto &x : uniq) cout << x << "\n";
56
57 // Compare two cyclic strings
58 string A = "caba", B = "abac"; // these are rotations of each other
59 int cmp = compare_cyclic(A, B);
60 cout << "Compare cyclic A vs B: ";
61 if (cmp < 0) cout << "A < B\n";
62 else if (cmp > 0) cout << "A > B\n";
63 else cout << "A == B (as necklaces)\n";
64
65 return 0;
66}
67

This program shows how to use the minimum rotation as a canonical form for cyclic strings (necklaces). We deduplicate a list of strings under rotation by inserting their canonical forms into a hash set. We also compare two cyclic strings by comparing their canonical representatives. The approach runs in total linear time in the input size.

Time: O(total_length)Space: O(total_length)