1. 问题描述:
给你一个下标从 0 开始的字符串 s 。另给你一个下标从 0 开始、长度为 k 的字符串 queryCharacters ,一个下标从 0 开始、长度也是 k 的整数下标数组 queryIndices ,这两个都用来描述 k 个查询。第 i 个查询会将 s 中位于下标 queryIndices[i] 的字符更新为 queryCharacters[i] 。返回一个长度为 k 的数组 lengths ,其中 lengths[i] 是在执行第 i 个查询之后 s 中仅由单个字符重复组成的最长子字符串的长度 。
示例 1:
输入:s = "babacc", queryCharacters = "bcb", queryIndices = [1,3,3]
输出:[3,3,4]
解释:
- 第 1 次查询更新后 s = "bbbacc" 。由单个字符重复组成的最长子字符串是 "bbb" ,长度为 3 。
- 第 2 次查询更新后 s = "bbbccc" 。由单个字符重复组成的最长子字符串是 "bbb" 或 "ccc",长度为 3 。
- 第 3 次查询更新后 s = "bbbbcc" 。由单个字符重复组成的最长子字符串是 "bbbb" ,长度为 4 。
因此,返回 [3,3,4] 。
示例 2:
输入:s = "abyzz", queryCharacters = "aa", queryIndices = [2,1]
输出:[2,3]
解释:
- 第 1 次查询更新后 s = "abazz" 。由单个字符重复组成的最长子字符串是 "zz" ,长度为 2 。
- 第 2 次查询更新后 s = "aaazz" 。由单个字符重复组成的最长子字符串是 "aaa" ,长度为 3 。
因此,返回 [2,3] 。
提示:
1 <= s.length <= 10 ^ 5
s 由小写英文字母组成
k == queryCharacters.length == queryIndices.length
1 <= k <= 10 ^ 5
queryCharacters 由小写英文字母组成
0 <= queryIndices[i] < s.length
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-substring-of-one-repeating-character/
2. 思路分析:
分析题目可以知道每一次修改的是s中某个位置的字符,然后查询整个串中连续最长子字符串的长度,我们可以将s看成是一个区间,相当于修改区间中的某个字符,然后查询区间中连续最长子字符串的长度,这两个操作属于线段树的经典操作,也即区间单点修改与区间查询。线段树的核心需要考虑如何来维护线段树节点的信息,由题目可知我们需要求解连续子字符串最长的长度,因为线段树节点会将当前区间分割为两个子区间,而连续子字符串可能出现在两个区间的分界点上,也可能出现在左边的区间或者出现在右边的区间,我们可以通过维护下面几个信息达到维护区间中连续子字符串最长的长度的目的:
- 区间左端点连续子字符串的最大长度lmax(前缀),区间右端点连续子字符串的最大长度(后缀)rmax
- 区间的长度size
- 区间连续子字符串的最大长度dmax
- 区间的左端点的字符lc,区间右端点的字符rc
维护lmax,rmax,size,lc,rc主要是判断出连续子字符串是否出现在区间的中间部分,用来更新区间中连续子字符串的最长长度;由于是单点修改所以线段树节点不需要维护懒标记信息,核心操作是pushup操作,也即在修改完之后如何维护线段树节点中信息,其余操作都是线段树的常规操作。
3. 代码如下:
java:
import java.util.Scanner;
public class Solution {
static Tree []tree;
public static void build(int u, int l, int r, String s){
if (l == r){
tree[u].l = l;
tree[u].r = r;
tree[u].lmax = tree[u].rmax = tree[u].dmax = tree[u].size = 1;
tree[u].lc = tree[u].rc = s.charAt(l);
return;
}
tree[u].l = l;
tree[u].r = r;
int mid = l + r >> 1;
build(u << 1, l, mid, s);
build(u << 1 | 1, mid + 1, r, s);
// 将子节点的信息向上传递到父节点
pushup(u);
}
public static void pushup(int u){
Tree cur = tree[u];
Tree l = tree[u << 1];
Tree r = tree[u << 1 | 1];
cur.lmax = l.lmax;
cur.rmax = r.rmax;
cur.lc = l.lc;
cur.rc = r.rc;
cur.size = l.size + r.size;
cur.dmax = Math.max(l.dmax, r.dmax);
if (l.rc == r.lc){
// 更新左端点的lmax, 说明当前区间可以构成更长的前缀
if (l.lmax == l.size){
cur.lmax += r.lmax;
}
// 更新右端点的rmax, 说明当前区间可以构成更长的后缀
if (r.rmax == r.size){
cur.rmax += l.rmax;
}
// 由于中间存在连续的子字符串所以更新一下答案
cur.dmax = Math.max(cur.dmax, l.rmax + r.lmax);
}
}
public static void update(int u, int k, char c){
if (tree[u].l == tree[u].r && tree[u].l == k){
tree[u].lc = tree[u].rc = c;
return;
}
int mid = tree[u].l + tree[u].r >> 1;
if (mid >= k){
update(u << 1, k, c);
}else{
update(u << 1 | 1, k, c);
}
// 修改完之后需要往上更新区间中连续子字符串的最大长度
pushup(u);
}
public static int[] longestRepeating(String s, String qc, int[] q) {
int n = q.length;
int []res = new int[n];
int len = s.length();
tree = new Tree[4 * len + 10];
for (int i = 0; i < 4 * len + 10; ++i){
tree[i] = new Tree();
}
build(1, 0, len - 1, s);
for (int i = 0; i < n; ++i){
// 由于单点修改所以我们找到需要修改的位置然后将其修改为指定的字符, 在修改的过程中执行pushup操作更新区间中连续子字符串的最大长度,
update(1, q[i], qc.charAt(i));
// 查询的是整个区间而编号为1的节点维护的就是整个区间的信息
res[i] = tree[1].dmax;
}
return res;
}
public static class Tree{
int l, r, lmax, rmax, dmax, size;
char lc, rc;
}
}