- leetcode 315. 计算右侧小于当前元素的个数
给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。
示例:
输入:nums = [5,2,6,1]
输出:[2,1,1,0]
解释:
5 的右侧有 2 个更小的元素 (2 和 1)
2 的右侧仅有 1 个更小的元素 (1)
6 的右侧有 1 个更小的元素 (1)
1 的右侧有 0 个更小的元素
这题非常经典的可用树状数组来解题的场景。
为什么会想到用树状数组?
首先暴力枚举的时间复杂度O(N^2)是不可能在OJ上通过的。因为需要统计的是右侧的严格小于本元素的数的数量,从统计层面上的高等策略来讲,前缀数和数组和差分数组会比较常用,显然此题不是求静态的区间和,也不是区间更新的场景。我们的解题思路可以是:从右往左遍历,将遇到的数加入到一个容器,再对这个容器进行查询,结果为右侧小于此数的元素的数量,这就涉及到了一个动态的更新,而且是单点更新,然后查询,这就是树状数组的拿手菜了。具体策略为:先对原数组进行去重得到N个数并给出他们各自的排名(从1到N),排名和原数一一映射。借助一个长度为N+1的树状数组arr,该数组中的元素arr[i]表示排名为i的元素有arr[i]个。利用树状数组的更新和查询操作,从右往左遍历原数组,使每个数对应的排名统计位arr[i]加一,这里借助树状数组的更新(会触发连带的更新其他arr[i]),更新的时间复杂度为logN,在这轮更新结束后,便可以进行查询,查询排名比自己低的有多少—累加arr[1]~arr[i-1]就是结果,不过这里可以借助树状数组的区间和查询,时间复杂度为logN,因为总是从右往左遍历更新树状数组的,所以只能统计到右侧的,非常满足题意。
树状数组视频详解
树状数组模板:
int lowbit(int n){return n&(-n);}//arr[i]加上valuevoid update(vector<int>& tree, int i, int value){while(i<tree.size()){tree[i]+=value;i+=lowbit(i);}}int query(vector<int>& tree, int i){int ret=0;while(i>0){ //注意:树状数组是从1开始的ret+=tree[i];i-=lowbit(i);}return ret;}
315题解:
vector<int> countSmaller(vector<int>& nums) {vector<int>N(nums);// 排序、去重、获得各个元素的排名sort(N.begin(), N.end());removeDups(N,1);unordered_map<int,int> map;int rank=1;for(auto n:N){map[n]=rank;rank++;}vector<int> ret(nums.size(), 0);vector<int> C(N.size()+1,0);for(int j=nums.size()-1;j>=0;--j){int rank=map[nums[j]];update(C,rank,1);if(rank==1){ret[j]=0;}else{ret[j]=query(C,rank-1);}}return ret;}void removeDups(vector<int>& arr, int k){int index=k;for(int i=k;i<arr.size();++i){if(arr[i]!=arr[index-k]){arr[index++]=arr[i];}}arr.resize(index);}
如果面试巧遇这题,很大可能是个滑铁卢唉。
