lintCode249【(树状数组)+(求逆序数)总结】(附:最后面浅说,离散化,目的是为了节省树状数组使用空间)

题目链接:https://www.lintcode.com/problem/count-of-smaller-number-before-itself/description

题目大意:

在这里插入图片描述其实说白了,就是找每个数字的前面到底有多少个数比他小的
我们看题目的例子:

输入:
[1,2,7,8,5]
输出:
[0,1,2,3,2]
//我们依次看:首先是“输入”的第一位:“1”,这个数前面没有数,故没有数比它小
								//故输出“0”
						 //第二位:“2”,在他前面比它小的有“1”,代表1位
						 		//故输出“1”
						 //第三位:“7”,在他前面有“1,2”都比他小
						 		//故输出“2”
						 //第四位:“8”,在他前面有“1,2,7”都比它小
						 		//故输出“3”
						 //第五位:“5”,在他前面有“1,2”比它小
						 		//故输出“2”
//所以最终答案输出“0,1,2,3,2”

思路:

其实就是找逆序对,正常来讲逆序对是找多少个前面比它大的数的个数,只不过这个是改成了找小的多少个数罢了。
所以我们可以称之为“反序列对”。

正常我们找这个的思路可以是,两个for遍历去找,按道理来说是可以找到的,不过数据量一大就会导致超时。

所以今天就要用到树状数组去求逆序数,并且用到的是“树状数组中的:区间更新,单点查询”,如果有不懂这个的同学,建议先去学这个知识点再回来看,附上学习这个的大佬链接:https://www.cnblogs.com/xenny/p/9739600.html

当你有了这个知识储备后,咱们继续:
我们举个例子:比如给你一个数组a,里面的值的取值范围由 0 到10000,
那我们就可以构造这样一个树形数组,去按照a数组里给的单个数值作为树状数组下标+1去存储值为1,之后你要得到数组里的哪个数字的“反序列对”,只需要将树状数组的这个数字的下标之前的区间求和,这样就可以求得了。【这段话根据下面的具体例子反复领会即可】

光说不来点实际的也不行,咱们将这个例子具体化点:
比如说,给你a数组里的值分别为:5,6,8,9
按照单个数值作为树状数组下标+1去存储1,我们先是拿到了a数组中的5,
在这里插入图片描述

树状数组就会如图存储在6号位置下,然后根据树状数组独特的update去大大地优化了给后面赋值的时间。
依次存储下来,我们后面比如2号位置去查询前面比它小的数字,那就直接去树状数组6号下标之前数值的和,即0+0+0+0+0+0+1=1,所以6号位置的“反序列数”为1,即只有5.

因此可以写出代码:

vector<int> c(maxn,0);//树状数组  这里的vector<int> 后面创建的时候用的是“()”而不是“[]”,所以创建出来的就是一维数组
                            //而为什么不直接用一维数组呢?
                            //因为这里需要用到传参数,vector有个很好用的方法size()可以直接得到数组长度,而一维数组里面没有
        vector<int> res(A.size(),0);//记录A中每个值前面所包含的反逆序数
        for(int i = 0;i < A.size();i++){
            
            res[i] += getsum(c,A[i]);
            
            update(c,A[i]+1,1);//存放的时候需要向后移动一位,以至于不会包含本身
        }

AC代码:(是c++的哦~)

class Solution {
public:
    /**
     * @param A: an integer array
     * @return: A list of integers includes the index of the first number and the index of the last number
     */
     const int maxn = 10005;
     int lowbit(int x){
         return x&(-x);
     }
     
    void update(vector<int> &c,int i,int k){//这里的vector使用要加上&符号,不然的话外界的vector是不会更新值的
         while(i < c.size()){
             c[i] += k;
             i += lowbit(i);
         }
     }
     
    int getsum(vector<int> &c,int i){
         int ans = 0;
         while(i > 0){
             ans += c[i];
             i -= lowbit(i);
             
         }
         return ans;
     }
     
     
    vector<int> countOfSmallerNumberII(vector<int> &A) {
        // write your code here
        vector<int> c(maxn,0);//树状数组  这里的vector<int> 后面创建的时候用的是“()”而不是“[]”,所以创建出来的就是一维数组
                            //而为什么不直接用一维数组呢?
                            //因为这里需要用到传参数,vector有个很好用的方法size()可以直接得到数组长度,而一维数组里面没有
        vector<int> res(A.size(),0);//记录A中每个值前面所包含的反逆序数
        for(int i = 0;i < A.size();i++){
            
            res[i] += getsum(c,A[i]);
            
            update(c,A[i]+1,1);//存放的时候需要向后移动一位,以至于不会包含本身
        }
        
        return res;
        
    }
};

附:面浅说离散化,目的是为了节省树状数组使用空间。

然后上面我们大家也可以看到,树状数组是多么的方便呀。然后嘞,来了一件严肃的事情,因为上面给的a的数组范围为0~10000,所以我们构造的树状数组的大小为10005,比它大一点。但是!如果我们a中数值取值范围为1010 难道我们的树状数组也要这样改成c[1010],会有数组越界的风险!

所以我们就引入了,“离散化”。
其实这个“离散化”的作用就是“将之前以数组的值作为树状数组的下标,转为a数组的下标了”,想想看,最开始是按照数值去存储,那样的话范围是以数值为准的,即会出现c[1010]的情况,而现在改成a数组下标,大小直接缩小到了只要比a数组的存储空间大一点就行了。

好了,重点来了!该怎么将值转化成下标来操作呢?

①首先我们需要创建一个B vector,用于排序,创建个map用于记录值与下标值的关系

vector<int> B(A);//创建一个和A的值一模一样的vector,用于后面排序
map<int,int> mp;

②将B排好序

sort(B.begin(),B.end());

③将排好序的B vector存入map中去

for(int i = 0;i < A.size();i++){
            mp[B[i]] = i;
}

④将之前的A[i]换成mp[A[i]]即可

        for(int i = 0;i < A.size();i++){
            
            res[i] += getsum(c,mp[A[i]]);
            
            update(c,mp[A[i]]+1,1);//存放的时候需要向后移动一位,以至于不会包含本身
        }

接下来举个具体的例子吸收一下:
给一个a为:8,6,5,9
进行排序后为:5,6,8,9
map中存储的就是:
mp[5] = 0,
mp[6] = 1,
mp[8] = 2,
mp[9] = 3
所以后面比如说要更新5的值,那么我就去找5在map中的下标+1,因为这个下标代表着他在数组中的相对位置。
取值的时候自然也是找5在map中的下标,直接getsum就行了。

加入离散化的ac代码:

class Solution {
public:
    /**
     * @param A: an integer array
     * @return: A list of integers includes the index of the first number and the index of the last number
     */
     const int maxn = 10005;
     int lowbit(int x){
         return x&(-x);
     }
     
    void update(vector<int> &c,int i,int k){//这里的vector使用要加上&符号,不然的话外界的vector是不会更新值的
         while(i < c.size()){
             c[i] += k;
             i += lowbit(i);
         }
     }
     
    int getsum(vector<int> &c,int i){
         int ans = 0;
         while(i > 0){
             ans += c[i];
             i -= lowbit(i);
             
         }
         return ans;
     }
     
     
    vector<int> countOfSmallerNumberII(vector<int> &A) {
        // write your code here
        vector<int> c(A.size()+5,0);//这里的大小已经改成了A.size()+5了,
                            //因为利用离散化之后其实只需要比A数组储存范围大一点就行了,节约了大量的空间
        vector<int> res(A.size(),0);//记录A中每个值前面所包含的反逆序数
        
        map<int,int> mp;
        vector<int> B(A);//创建一个和A的值一模一样的vector,用于后面排序
        
        sort(B.begin(),B.end());
        for(int i = 0;i < A.size();i++){
            mp[B[i]] = i;
        }
        
        for(int i = 0;i < A.size();i++){
            
            res[i] += getsum(c,mp[A[i]]);
            
            update(c,mp[A[i]]+1,1);//存放的时候需要向后移动一位,以至于不会包含本身
        }
        
        return res;
        
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值