一.概念
把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。通俗的说,离散在不改变数据相对大小的条件下,对数据进行相应的缩小。
二.适用范围
数组中元素值域很大,但个数不是很多。
比如将a[]=[1,3,100,2000,500000]映射到[0,1,2,3,4]这个过程就叫离散化。
三.示例
首先进行问题分析
看到执行n次在某位置x上加上c,并且还询问m次给定区间的和这种问题。第一反应就是这是道考察前缀和的题目。
但仔细看下数据范围:
姑且不说我们是否能开那么大的数组,就算开得了,那么大的范围做前缀和,预处理时的时间复杂度也是无法忍受的。 而再看一下我们实际需要处理的数据量 n,m ,都是量级相对来说很小,我们只需要聚焦处理这量级的数据即可。在这种情况下,我们就可以用离散化了。
首先可以明确的是,在这道题中我们离散化的对象是数轴的坐标。所以对于输入的位置x和对应的数值c,只需要把数轴下标放到数组discretization[n] 中即可,这里的 discretization[n] 为离散化后的映射数组。
(1)对x和c进行一个封装 , 以及对l和r进行封装
static class Pair{
int x;
int c;
Pair(int x , int c){
this.x = x;
this.c = c;
}
}
static class Range{
int l;
int r;
Range(int l , int r){
this.l = l;
this. r = r;
}
}
(2)创建一个存储Pair的集合和一个存储Range的集合以及一个存储x坐标值的集合
//创建一个集合存储Pair
List<Pair> list = new ArrayList<>();
//存储x的值,因为x是坐标轴上的值非常大,所以要对其进行系列化
Set<Integer> points = new TreeSet<>();
List<Range> ranges = new ArrayList<>();
(3)分别填充三个集合
首先我们可以在控制台输入对应的修改次数(n)和查询次数(m)
Scanner scanner = new Scanner(System.in);
int m,n;//n次修改,m次查询
System.out.println("请以此输入m,n");
m = scanner.nextInt();
n = scanner.nextInt();
接下来填充集合
//输入n次修改操作,(将位置为x的值加c)
for (int i = 0; i < n; i++) {
int x = scanner.nextInt();
int c = scanner.nextInt();
list.add(new Pair(x,c));
points.add(x);
}
//进行m次查询操作
for (int i = 0; i < m; i++) {
int l = scanner.nextInt();
int r = scanner.nextInt();
ranges.add(new Range(l,r));
points.add(l);
points.add(r);
}
这个离散化后的新数组(points),有多个相同且无序的值,这肯定是不行的。所以第二步就是对points
[n]
排序、去重.
(4)对存储x的集合进行转换为数组并且去重和排序
我们创建集合时利用的是Set集合, 所以只需要进行排序操作
//排序(离散化的数组)
int[] discretization = points.stream().sorted().mapToInt(Integer::valueOf).toArray();
但是目前为止我们集合中存放的并不是值, 而是坐标位置x , 所以我们还要创建一个数组来存储
discretization[n]数组中对应的值
(5)创建一个数组存储x对应的值
int []c = new int[discretization.length];
//接下来填充数组c
list.stream().forEach(list -> {
int x = list.x;
int v = list.c;
int index = BinnearySearch(x);
c[index] += v;
});
上述代码的解读如下:
首先创建一个长度和discretization[n]数组一样的数组 , 再对存储Pair的数组中循环 , 拿出其中的x和对应的值c , 然后利用二分查找法将x在discretization[n]数组中的索引拿出来 , 填充到对应数组c[]的索引的地方 , 找到要填充的c的索引后, 对其进行操作 , 也就是c[index] += v;
二分查找法代码如下:
//二分查找法
public int BinnearySearch(int x){
int l = 0;
int r = discretization.length-1;
while (l<=r){
int mid = l+(r-l)/2;
if(discretization[mid] == x){
return mid;
}else if(discretization[mid] > x){
r = mid-1;
}else {
l = mid+1;
}
}
return -1;
}
(6) 创建一个sum数组 , 用来存储c中前i项的值的和
//创建一个sum数组
sum = new int[c.length+1];
for (int i = 1; i < sum.length; i++) {
sum[i] = sum[i-1] + c[i-1];
}
(7)用Range数组中的l和r分别对应的sum中的值相减就是l和r中间值的和
ranges.stream().forEach(range ->{
int lrange = range.l;
int rrange = range.r;
System.out.println(sum[rrange+1]-sum[lrange]);
});