数据结构之SQRT分解
前言
SQRT分解是一种数据结构,使用分组的思想来解决区间问题,如求区间和,区间最大最小值等。支持动态更新指定索引的值。

将一个包含N个元素的数组分成sqrt(N)组,就是对N开平方。如18个元素,sqrt(18)=4.24,所以分成5组,最后一组不足4个。
原理
以求区间和为例,对数据分组,提前计算好每组数据的和。

查询区间在同一组内,只需要从区间的开始索引到结束索引遍历一遍即可。

查询区间在不同组

查询区间在不同组,后两种情况可以合二为一,都是中间间隔了几组数据。
将区间分成3部分
- 第一部分[3,3]
- 第二部分1,2,3组,每组的数据之前已经提前计算好了
- 第三部分[16,16]
总体时间复杂度O(sqrt(N))
代码实现
import java.util.Arrays;
public class SQRT<T> {
private T[] data;//原数组数据
private int N;//原数组长度
private T[] blocks;//存储每组数据的和或最大最小值等数据
private int B;//每组大小 sqrt(N)
private int Bn;//共分成几组 等于B或B+1
private Merger<T> merger; //合并策略
public SQRT(T[] arr, Merger<T> merger) {
this.merger = merger;
N = arr.length;
data = Arrays.copyOf(arr, N);
B = (int) Math.sqrt(N);
Bn = N / B + (N % B == 0 ? 0 : 1);
blocks = (T[]) new Object[Bn];
for (int i = 0; i < data.length; i++) {
if (i % B == 0) {
//每组的开始
blocks[getBlockIndex(i)] = data[i];
} else {
blocks[getBlockIndex(i)] = this.merger.merge(blocks[getBlockIndex(i)], data[i]);
}
}
}
/**
* 查询指定区间的值
*/
public T queryRange(int left, int right) {
int startBlockIndex = getBlockIndex(left);
int endBlockIndex = getBlockIndex(right);
if (startBlockIndex == endBlockIndex) {
return merge(data, left, right);
}
int leftStart = left;
int leftEnd = getEndIndex(startBlockIndex);
int rightStart = getStartIndex(endBlockIndex);
int rightEnd = right;
T[] parts = (T[]) new Object[3];
parts[0] = merge(data, leftStart, leftEnd);
parts[1] = merge(data, rightStart, rightEnd);
//这一部分有可能为空
parts[2] = merge(blocks, startBlockIndex + 1, endBlockIndex - 1);
if (parts[2] == null) {
return merge(parts, 0, parts.length - 2);
} else {
return merge(parts, 0, parts.length - 1);
}
}
/**
* 更新指定索引的值
*/
public SQRT<T> update(int index, T val) {
int blockIndex = getBlockIndex(index);
data[index] = val;
int startIndex = getStartIndex(blockIndex);
int endIndex = getEndIndex(blockIndex);
blocks[blockIndex] = merge(data, startIndex, endIndex);
return this;
}
/**
* 合并指定区间的数组
*/
private T merge(T[] data, int start, int end) {
if (start > end) {
return null;
}
T res = data[start];
for (int i = start + 1; i <= end && i < data.length; i++) {
res = this.merger.merge(res, data[i]);
}
return res;
}
/**
* 根据原数组索引得到分组的索引(第几组)
*/
private int getBlockIndex(int dataIndex) {
return dataIndex / B;
}
/**
* 根据分组索引(第几组)得到此组的开始索引
*/
private int getStartIndex(int blockIndex) {
return blockIndex * B;
}
/**
* 根据分组索引(第几组)得到此组的结束索引
*/
private int getEndIndex(int blockIndex) {
return (blockIndex + 1) * B - 1;
}
public interface Merger<E> {
E merge(E e1, E e2);
}
}
客户端使用
public class TestSQRT {
public static void main(String[] args) {
Integer[] arr = {1, 2, 3, 4};
System.out.println(new SQRT<>(arr, Integer::sum).queryRange(0, 3));//10
System.out.println(new SQRT<>(arr, Integer::sum).update(0, 5).queryRange(0, 3));//14
System.out.println(new SQRT<>(arr, Integer::max).update(0, 5).queryRange(0, 3));//5
System.out.println(new SQRT<>(arr, Integer::min).update(0, 5).queryRange(0, 3));//2
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了