每日一题,防止痴呆 = =
一、题目大意
给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/count-of-smaller-numbers-after-self
二、题目思路以及AC代码
思路:树状数组
这道题暴力的方法没有试,应该就是O(N^2)的复杂度,虽然这题没有给数组长度范围,但很明显是不应该直接暴力做的。
其实没做出这道题的原因还是对树状数组不够熟悉,这道题感觉比较自然的想法是,从后往前遍历,此时当我们遍历到某个数的时候,只需要考虑已经遍历过的数比它小的有多少个,自然就是其右侧比它小的数量了。我相信如果对树状数组熟悉的人肯定很自然的就想到的解法。
我们可以维护一个树状数组,用来动态的求解桶数组的前缀和,由于树状数组的求和和更新都是O(logN)的复杂度,所以整体是O(NlogN)的复杂度,比O(N^2)快了不少。举个例子来说明求解过程。
对于示例数组:[5, 2, 6, 1]
首先我们遍历 1,桶数组 count[1]++,以O(logN)的复杂度更新树状数组以及求前缀和 (count[0]) 为0.
然后遍历6,桶数组 count[6]++,O(logN)的复杂度更新树状数组以及求前缀和 (count[0] + count[1] + … + count[5]) 为1.
然后遍历2,桶数组 count[2]++,O(logN)的复杂度更新树状数组以及求前缀和 (count[0] + count[1] + count[2]) 为1.
最后遍历5,桶数组 count[5]++,O(logN)的复杂度更新树状数组以及求前缀和 (count[0] + count[1] + … + count[4]) 为2.
最终求得正确结果。
关于树状数组的原理以及知识,我之前有个博客讲解过,不过是转载的,这次也是又复习了一遍。https://blog.csdn.net/m0_38055352/article/details/105726733
然后还有一点,这里由于我们不知道数组整数的范围,所以桶数组的大小可能会很大,但是由于这里我们只需要大小关系,而对于值没有要求,所以我们可以将桶数组压缩,相当于是按照大小关系Hash了一下,就可以把长度限制为和数组大小一样了。
AC代码
class Solution {
private:
vector<int> a;
vector<int> c;
void init(int length) {
c.resize(length, 0);
}
int lowbit(int x) {
return x & -x;
}
void update(int i, int val) {
while (i < c.size()) {
c[i] += val;
i += lowbit(i);
}
}
int getSum(int i) {
int res = 0;
while (i > 0) {
res += c[i];
i -= lowbit(i);
}
return res;
}
void Discretization(vector<int> nums) {
a.assign(nums.begin(), nums.end());
sort(a.begin(), a.end());
a.erase(unique(a.begin(), a.end()), a.end());
}
int getId(int x) {
return lower_bound(a.begin(), a.end(), x) - a.begin() + 1;
}
public:
vector<int> countSmaller(vector<int>& nums) {
int n_size = nums.size();
vector<int> res;
if (!n_size) return res;
Discretization(nums);
n_size = nums.size();
init(n_size + 5);
for (int i=n_size-1;i>=0;i--) {
int idx = getId(nums[i]);
update(idx, 1);
res.push_back(getSum(idx - 1));
}
reverse(res.begin(), res.end());
return res;
}
};
如果有问题,欢迎大家指正!!!