- 题目描述
- 解法:线段树
- Reference
题目描述
- 2213. 由单个字符重复的最长子字符串
根据题意,本题有
k
k
k次单点修改,
k
k
k次查询最长子字符串,如果每次采用
O
(
n
)
O(n)
O(n)复杂度的暴力查询,那么总的时间复杂度是
O
(
k
∗
n
)
O(k*n)
O(k∗n),会超时。
线段树是一个在
O
(
n
)
O(n)
O(n) 的初始化后可以以
O
(
l
o
g
n
)
O(log\ n)
O(log n)修改、查询与区间有关信息的数据结构。
使用线段树求解,底层使用链表实现,需要考虑每个节点Node
中维护些什么信息?
对于线段树的节点信息设计,通常包含左右端点left
、right
以及查询目标值val
(本题即为该区间最长连续字符个数),本题中还需要维护如下的辅助信息:
prefix
:当前区间[left,right]
内前缀最长连续字符个数suffix
:当前区间[left,right]
内后缀最长连续字符个数
合并两个子区间时,如果左区间 a a a的末尾字符等于右区间 b b b的第一个字符,则:
-
若 a a a的
prefix
等于 a a a区间的长度,那么新区间有prefix=a.pre+b.pre
-
若 b b b的
suffix
等于 b b b区间的长度,那么新区间有suffix=a.suffix+b.suffix
-
a.suf+b.pre
可以考虑成为合并后的区间的val
class Solution {
char[] cs;
public int[] longestRepeating(String s, String queryCharacters, int[] queryIndices) {
cs = s.toCharArray();
char[] qcs = queryCharacters.toCharArray();
int n = cs.length;
Node root = buildSegmentTree(0, n -1);
int[] ans = new int[qcs.length];
for (int i = 0; i < qcs.length; i++) {
update(root, queryIndices[i], qcs[i]);
ans[i] = root.val;
// ans[i] = query(root, 0, n-1).val;
}
return ans;
}
class Node {
int left;
int right;
int val;
int prefix;
int suffix;
Node leftNode;
Node rightNode;
public Node(int left, int right) {
this.left = left;
this.right = right;
this.val = 1;
this.prefix = 1;
this.suffix = 1;
this.leftNode = this.rightNode = null;
}
}
/**
* 创建表示区间[l,r]的线段树,并返回根节点
* @param l
* @param r
* @return
*/
private Node buildSegmentTree(int l, int r) {
if (l == r) {
return new Node(l, r);
}
Node node = new Node(l, r);
int mid = l + (r - l) / 2;
node.leftNode = buildSegmentTree(l, mid);
node.rightNode = buildSegmentTree(mid + 1, r);
merge(node);
return node;
}
/**
* 数据融合
*/
private void merge(Node node) {
if (node.leftNode != null) {
Node lNode = node.leftNode;
Node rNode = node.rightNode;
node.val = Math.max(lNode.val, rNode.val);
node.prefix = lNode.prefix;
node.suffix = rNode.suffix;
// 如果左区间的末尾字符等于右区间的第一个字符
if (cs[lNode.right] == cs[rNode.left]) {
node.val = Math.max(node.val, lNode.suffix + rNode.prefix);
// 若左区间的 prefix 等于左区间的长度
if (lNode.prefix == (lNode.right - lNode.left + 1)) {
node.prefix = lNode.prefix + rNode.prefix;
}
// 若右区间的 suffix 等于右区间的长度
if (rNode.suffix == (rNode.right - rNode.left +1)) {
node.suffix = lNode.suffix + rNode.suffix;
}
}
}
}
/**
* 将idx位置的值更新为val
* @param node
* @param idx
* @param val
*/
private void update(Node node, int idx, char val) {
if (node.left > idx || node.right < idx) {
return ;
}
if (node.left == idx && node.right == idx) {
cs[idx] = val;
return ;
}
update(node.leftNode, idx, val);
update(node.rightNode, idx, val);
merge(node);
}
/**
* 区间查询
* 由于题目查询的是整个字符串的最长重复子串的长度,这块代码是冗余的
* @param node
* @param l
* @param r
* @return
*/
private Node query(Node node, int l, int r) {
if (node.left == l && node.right == r) {
return node;
}
int mid = l + (r - l) / 2;
if (l >= mid + 1) {
return query(node.rightNode, l, r);
} else if (r <= mid) {
return query(node.leftNode, l, r);
}
Node res = new Node(l, r);
res.leftNode = query(node.leftNode, l, mid);
res.rightNode = query(node.rightNode, mid + 1, r);
merge(res);
return res;
}
}
- 时间复杂度: O ( k ∗ log n ) O(k * \log{n}) O(k∗logn)
- 空间复杂度: O ( n ) O(n) O(n)
-
线段树
-
和字符集大小无关的线段树做法
-
线段树运用题
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)