467. 环绕字符串中唯一的子字符串 - 力扣(LeetCode)
题目描述
把字符串 s 看作无限环绕字符串 "...zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd...." 的一部分。 现在给定字符串s,求s中有多少个唯一的非空子串,这个子串在环绕串中出现。
示例: 输入: s = "zab" 输出: 6 解释:有 6 个唯一子串:z、a、b、za、ab、zab
思路分析
核心观察
- 合法子串必须是连续递增:
a→b→c...z→a。 - 同一个结尾字母 ,我们只需要保留最长连续子串的长度。
举例:以
b结尾,最长连续长度是 3,那么它能贡献 3 个子串:b、ab、zab;短的可以直接忽略,避免重复统计。
状态定义
dp[i]:以 s i 结尾的最长连续环绕子串的长度
- 初始化:
dp[i]=1,单个字符自身长度为 1 - 转移条件: 如果
s[i-1]+1 == s[i]或者s[i-1]=='z' && s[i]=='a',说明连续,dp[i] = dp[i-1] + 1否则,dp[i]保持 1,断开。

去重关键(本题精华)
开数组ch[26],ch[c-'a']保存以字符 c 结尾的最长连续子串长度。
为什么取最大值? 比如
s="abab",两个b,第一个 b 的 dp=2,第二个 b 的 dp=2。以 b 结尾最长就是 2,只贡献 2 个子串,防止重复计数。
最后把 ch 数组 26 个值全部累加,就是答案。
【去重例子】s = "abcdxyzabcd"
现在来了两段 abcd,abcd + xyzabcd s = a b c d x y z a b c d
先看两段 d: 第一段末尾 d,dp=4; 第二段末尾 d,dp 算出来是 7。 ch d = max (4,7) → 取 7!
第一段 d 产生的子串:
d、cd、bcd、abcd第二段更长的以 d 结尾串,包含上面全部,还新增更多子串。 旧的那一组完全重复,所以直接丢弃旧的 4,保留最大值 7。
ch a:第一次 a dp=1;后面第二个 a dp=4 → ch a=max (1,4)=4 ch b:第一次 b dp=2;后面第二个 b dp=5 → ch b=max (2,5)=5
cpp
class Solution {
public:
int findSubstringInWraproundString(string s) {
int n = s.size();
vector<int> dp(n,1);
int ch[26]={0};
int ret = 0;
for (int i = 1; i < n; i++) {
if (s[i - 1] + 1 == s[i] || (s[i - 1] == 'z' && s[i] == 'a')) {
dp[i] += dp[i - 1];
}
}
for(int i=0;i<n;i++)
{
ch[s[i]-'a']=max(dp[i],ch[s[i]-'a']);
}
for (auto e : ch)
ret+=e;
return ret;
}
};