定义
给定字符串 ,其 Lyndon 分解(Lyndon Factorization)是将 唯一地分解为若干个子串:
其中每个 都是 Lyndon 词,且满足 (字典序递减)。1
Lyndon 词的定义
一个字符串 是 Lyndon 词,当且仅当:
- 是其自身的一个真周期
- 或者等价地, 的字典序严格小于其所有真后缀的字典序
形式化地:
其中 表示字典序小于。
例子
| 字符串 | Lyndon 分解 |
|---|---|
banana | b a n a n a |
aababc | aa b ab c |
aaaa | a a a a |
abcabc | abc abc |
Duval 算法
Lyndon 分解可以在 时间内通过 Duval 算法 计算。2
算法思想
维护三个指针:
- :当前处理位置的起点
- :候选 Lyndon 词内部的比较位置
- :Lyndon 词的长度边界
伪代码
function lyndon(s):
n = |s|
result = []
i = 0
while i < n:
j = i + 1
k = i
while j < n and s[k] <= s[j]:
if s[k] < s[j]:
k = i
else:
k = k + 1
j = j + 1
// 当前 Lyndon 词长度
len = j - k
while i <= k:
result.append(s[i : i + len])
i = i + len
return result
C++ 实现
#include <bits/stdc++.h>
using namespace std;
vector<string> lyndonFactorization(const string& s) {
int n = s.size();
vector<string> result;
int i = 0;
while (i < n) {
int j = i + 1;
int k = i;
while (j < n && s[k] <= s[j]) {
if (s[k] < s[j]) {
k = i;
} else {
k++;
}
j++;
}
int len = j - k;
while (i <= k) {
result.push_back(s.substr(i, len));
i += len;
}
}
return result;
}运行示例
以 s = "aababc" 为例:
初始:i = 0
j = 1, k = 0: s[0]='a' <= s[1]='a', s[0]=s[1] → k=1
j = 2, k = 1: s[1]='a' <= s[2]='b', s[1]<s[2] → k=0
j = 3, k = 0: s[0]='a' <= s[3]='a', s[0]=s[3] → k=1
j = 4, k = 1: s[1]='a' <= s[4]='b', s[1]<s[4] → k=0
j = 5, k = 0: s[0]='a' <= s[5]='c', s[0]<s[5] → k=0
退出循环,len = 5 - 0 = 5
添加 s[0:5] = "aabab"
i = 5
继续处理剩余字符...
性质
唯一性
Lyndon 分解是唯一的——每个字符串只有一种分解方式。
与 Z 函数的关系
Lyndon 分解与 Z 函数 有密切关系:
- 如果 是 Lyndon 词,则
与后缀数组的关系
每个 Lyndon 分解中的子串都是其对应后缀的最小前缀。
经典应用
1. 求字符串的最小表示
通过 Lyndon 分解可以 求字符串的最小表示:
string minRotation(const string& s) {
string ss = s + s;
auto factors = lyndonFactorization(ss);
// 找到第一个分解点
int n = s.size();
int pos = 0;
for (const auto& f : factors) {
if (pos + f.size() >= n) break;
pos += f.size();
}
return ss.substr(pos, n);
}2. 求字符串的最小周期
int minPeriod(const string& s) {
int n = s.size();
auto factors = lyndonFactorization(s);
int total = 0;
for (const auto& f : factors) total += f.size();
return n - total + factors.back().size();
}3. 在文本中查找模式(结合其他算法)
Lyndon 分解可用于构造后缀数组或解决重复子串问题。
例题
例题:验证 Lyndon 分解
问题
给定字符串 和一种划分,验证这是否是 的有效 Lyndon 分解。
验证条件
- 每个子串 是 Lyndon 词
- (字典序递减)
验证算法
bool isLyndonWord(const string& t) {
int n = t.size();
for (int i = 1; i < n; i++) {
if (t.substr(i) < t) return false;
}
return true;
}
bool verifyLyndonFactorization(const string& s, const vector<string>& factors) {
string reconstructed;
for (const auto& f : factors) {
if (!isLyndonWord(f)) return false;
reconstructed += f;
}
if (reconstructed != s) return false;
for (size_t i = 1; i < factors.size(); i++) {
if (factors[i-1] < factors[i]) return false;
}
return true;
}与其他字符串算法的比较
| 算法 | 时间复杂度 | 用途 |
|---|---|---|
| Lyndon 分解 | 字符串唯一分解 | |
| Z 函数 | 字符串匹配 | |
| KMP | 模式匹配 | |
| 后缀数组 | 字符串排序 |