离散化详解

一.概念
       把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。通俗的说,离散在不改变数据相对大小的条件下,对数据进行相应的缩小。
二.适用范围
      数组中元素值域很大,但个数不是很多。
      比如将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]);
        });

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值