leetcode 2223 — 构造字符串的总得分和
一、题目描述
二、算法
这道题实际上就是考察的Z 算法。
1、泛化问题描述
2、分析
与 KMP 类似,计算
z
[
i
]
z[i]
z[i] 时需要用到前面计算过程的经验。这个算法的核心在于:维护了
[
l
,
r
]
[l, r]
[l,r] 区间,满足
r
=
max
0
≤
k
<
i
k
+
z
[
k
]
−
1
(1)
r = \max \limits_{0 \le k < i}k+z[k]-1\tag{1}
r=0≤k<imaxk+z[k]−1(1)
l
+
z
[
l
]
−
1
=
r
(2)
l+z[l]-1=r\tag{2}
l+z[l]−1=r(2)
不难发现,
[
l
,
r
]
[l, r]
[l,r] 实际上是
s
s
s 和
s
[
l
:
n
−
1
]
s[l:n-1]
s[l:n−1] 的最长公共前缀区间。因此,我们可以得到
s
[
0
:
r
−
l
]
=
s
[
l
:
r
]
(3)
s[0:r-l]=s[l:r]\tag{3}
s[0:r−l]=s[l:r](3)
当我们需要计算
z
[
i
]
z[i]
z[i] 时,需要分情况讨论:
(1)如果
i
≤
r
i\le r
i≤r,对 (3) 式的左右两端的起始区间同时加上
i
−
l
i-l
i−l 可以得到
s
[
i
−
l
:
r
−
l
]
=
s
[
i
:
r
]
(4)
s[i-l:r-l]=s[i:r]\tag{4}
s[i−l:r−l]=s[i:r](4)
(a)若
z
[
i
−
l
]
<
r
−
i
+
1
z[i-l] < r-i+1
z[i−l]<r−i+1,那么
s
[
i
−
l
:
i
−
l
+
z
[
i
−
l
]
−
1
]
=
s
[
i
:
i
+
z
[
i
−
l
]
−
1
]
=
s
[
0
:
z
[
i
−
l
]
−
1
]
(5)
s[i-l:i-l+z[i-l]-1]=s[i:i+z[i-l]-1]=s[0:z[i-l]-1] \tag{5}
s[i−l:i−l+z[i−l]−1]=s[i:i+z[i−l]−1]=s[0:z[i−l]−1](5)
s
[
i
−
l
+
z
[
i
−
l
]
]
=
s
[
i
+
z
[
i
−
l
]
]
≠
s
[
z
[
i
−
l
]
]
(6)
s[i-l+z[i-l]]=s[i+z[i-l]] \ne s[z[i-l]]\tag{6}
s[i−l+z[i−l]]=s[i+z[i−l]]=s[z[i−l]](6)
因此
z
[
i
]
=
z
[
i
−
l
]
(7)
z[i] = z[i-l]\tag{7}
z[i]=z[i−l](7)
(b)若
z
[
i
−
l
]
≥
r
−
i
+
1
z[i-l] \ge r-i+1
z[i−l]≥r−i+1,那么我们可以扩展 (4) 式
s
[
i
−
l
:
r
−
l
]
=
s
[
i
:
r
]
=
s
[
0
:
r
−
i
]
(8)
s[i-l:r-l]=s[i:r]=s[0:r-i] \tag{8}
s[i−l:r−l]=s[i:r]=s[0:r−i](8)
这时我们对后续字符进行暴力枚举:
z
[
i
]
=
r
−
i
+
1
+
(
s
[
r
:
n
−
1
]
与
s
[
r
−
i
+
1
:
n
−
1
]
的
最
长
公
共
前
缀
区
间
长
度
)
(9)
z[i]=r-i+1+(s[r:n-1]与s[r-i+1:n-1]的最长公共前缀区间长度)\tag{9}
z[i]=r−i+1+(s[r:n−1]与s[r−i+1:n−1]的最长公共前缀区间长度)(9)
(2)如果
i
>
r
i> r
i>r,说明当前还没有访问过
s
[
i
]
s[i]
s[i],那么我们直接从
s
[
i
]
s[i]
s[i] 开始暴力求解即可。
需要注意,每次处理完,需要判断是否更新
[
l
,
r
]
[l,r]
[l,r] 区间。
3、实现
vector<int> z_function(string s) {
int sLength = s.length(), left = 0, right = 0;
vector<int> z(sLength);
for (int i = 1; i < sLength; ++i) {
if (i <= right) {
if (z[i - left] < right - i + 1) {
z[i] = z[i - left];
}
else {
z[i] = right - i + 1;
while (i + z[i] < sLength && s[i + z[i]] == s[z[i]]) {
++z[i];
}
}
}
else {
z[i] = 0;
while (i + z[i] < sLength && s[i + z[i]] == s[z[i]]) {
++z[i];
}
}
if (i + z[i] - 1 > right) {
left = i;
right = i + z[i] - 1;
}
}
return z;
}
不难发现,两个 else 分支可以合并。因此,代码可以化简为:
vector<int> z_function(string s) {
int sLength= s.length(), left = 0, right = 0;
vector<int> z(sLength);
for (int i = 1; i < sLength; ++i) {
if (i <= right && z[i - left] < right - i + 1) {
z[i] = z[i - left];
}
else {
z[i] = max(0, right - i + 1);
while (i + z[i] < sLength && s[i + z[i]] == s[z[i]]) {
++z[i];
}
}
if (i + z[i] - 1 > right) {
left = i;
right = i + z[i] - 1;
}
}
return z;
}
上述代码从 i = 1 i=1 i=1 开始计算 z [ i ] z[i] z[i] 是因为 i = 0 i=0 i=0 对应的 [ l , r ] [l,r] [l,r] 区间为 [ 0 , n − 1 ] [0,n-1] [0,n−1],相当于暴力求解。
sumScores 的实现如下:
long long sumScores(string s) {
auto z = z_function(s);
return accumulate(z.begin(), z.end(), (long long)0) + s.length();
}