当前位置:网站首页>leetcode 2223 — 构造字符串的总得分和

leetcode 2223 — 构造字符串的总得分和

2022-04-23 21:59:00 coding-hwz

leetcode 2223 — 构造字符串的总得分和

一、题目描述

在这里插入图片描述

二、算法

这道题实际上就是考察的Z 算法。

1、泛化问题描述

对于个长度为 n n n 的字符串 s s s。定义函数 z [ i ] z[i] z[i] 表示 s s s s [ i : n − 1 ] s[i:n-1] s[i:n1] 的最长公共前缀的长度。 给出计算
z z z 的算法。 注:这里 s [ i : n − 1 ] s[i:n-1] s[i:n1] 包含 s [ i ] s[i] s[i] s [ n − 1 ] s[n-1] s[n1]

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=0k<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:n1] 的最长公共前缀区间。因此,我们可以得到 s [ 0 : r − l ] = s [ l : r ] (3) s[0:r-l]=s[l:r]\tag{3} s[0:rl]=s[l:r](3)
当我们需要计算 z [ i ] z[i] z[i] 时,需要分情况讨论:
(1)如果 i ≤ r i\le r ir,对 (3) 式的左右两端的起始区间同时加上 i − l i-l il 可以得到 s [ i − l : r − l ] = s [ i : r ] (4) s[i-l:r-l]=s[i:r]\tag{4} s[il:rl]=s[i:r](4)
(a)若 z [ i − l ] < r − i + 1 z[i-l] < r-i+1 z[il]<ri+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[il:il+z[il]1]=s[i:i+z[il]1]=s[0:z[il]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[il+z[il]]=s[i+z[il]]=s[z[il]](6)
因此 z [ i ] = z [ i − l ] (7) z[i] = z[i-l]\tag{7} z[i]=z[il](7)
(b)若 z [ i − l ] ≥ r − i + 1 z[i-l] \ge r-i+1 z[il]ri+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[il:rl]=s[i:r]=s[0:ri](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]=ri+1+(s[r:n1]s[ri+1:n1])(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,n1],相当于暴力求解。

sumScores 的实现如下:

long long sumScores(string s) {
    
    auto z = z_function(s);
    return accumulate(z.begin(), z.end(), (long long)0) + s.length();
}

在这里插入图片描述

版权声明
本文为[coding-hwz]所创,转载请带上原文链接,感谢
https://blog.csdn.net/weixin_37950545/article/details/124262186