欢迎查看和关注一个开源的个人学习计算机科学知识成长记录(前后端,数据结构与算法)
面试题 10.10. 数字流的秩 - 力扣(LeetCode) (leetcode-cn.com)
var StreamRank = function() {
this.arr = new Array(50001).fill(0);
};
/**
* @param {number} x
* @return {void}
*/
StreamRank.prototype.track = function(x) {
this.arr[x] += 1;
};
/**
* @param {number} x
* @return {number}
*/
StreamRank.prototype.getRankOfNumber = function(x) {
let total = 0;
for(let i=0;i<=x;i++){
total += this.arr[i];
}
return total;
};
/**
* Your StreamRank object will be instantiated and called as such:
* var obj = new StreamRank()
* obj.track(x)
* var param_2 = obj.getRankOfNumber(x)
*/
执行结果:通过
执行用时:148 ms, 在所有 JavaScript 提交中击败了30.95%的用户
内存消耗:49.5 MB, 在所有 JavaScript 提交中击败了16.67%的用户
通过测试用例:29 / 29
class StreamRank {
int[] tree;
int n = 50001;
int lowbit(int x) {
return x & -x;
}
int query(int x) {
int ans = 0;
for (int i = x; i > 0; i -= lowbit(i)) ans += tree[i];
return ans;
}
void add(int x, int u) {
for (int i = x; i <= n; i += lowbit(i)) tree[i] += u;
}
public StreamRank() {
tree = new int[n + 1];
}
public void track(int x) {
add(x + 1, 1);
}
public int getRankOfNumber(int x) {
return query(x + 1);
}
}
树状数组
关于数状数组的资料,可以查看参考链接,有一些主要特性,lowbit之类的
树状结构是怎样的
黑色数组代表原来的数组(下面用A[i]代替),红色结构代表我们的树状数组(下面用C[i]代替),发现没有,每个位置只有一个方框,令每个位置存的就是子节点的值的和,则有
- C[1] = A[1];
- C[2] = A[1] + A[2];
- C[3] = A[3];
- C[4] = A[1] + A[2] + A[3] + A[4];
- C[5] = A[5];
- C[6] = A[5] + A[6];
- C[7] = A[7];
- C[8] = A[1] + A[2] + A[3] + A[4] + A[5] + A[6] + A[7] + A[8];
可以发现,这颗树是有规律的
C[i] = A[i - 2^k+1] + A[i -2^k+2] + … + A[i]; //k为i的二进制中从最低位到高位连续零的长度
例如i = 8(1000)时候,k = 3,可自行验证。
这个怎么实现求和呢,比如我们要找前7项和,那么应该是SUM = C[7] + C[6] + C[4];
而根据上面的式子,容易的出SUMi = C[i] + C[i-2^k1] + C[(i - 2^k1) - 2^k2] + …;
其实树状数组就是一个二进制上面的应用。
现在新的问题来了2k该怎么求呢,不难得出2k = i&(i(i-1));但这个还是不好求出呀,前辈的智慧就出来了,2k = i&(-i);
为什么呢?
lowbit
这里利用的负数的存储特性,负数是以补码存储的,对于整数运算 x&(-x)有
● 当x为0时,即 0 & 0,结果为0;
●当x为奇数时,最后一个比特位为1,取反加1没有进位,故x和-x除最后一位外前面的位正好相反,按位与结果为0。结果为1。
●当x为偶数,且为2的m次方时,x的二进制表示中只有一位是1(从右往左的第m+1位),其右边有m位0,故x取反加1后,从右到左第有m个0,第m+1位及其左边全是1。这样,x& (-x) 得到的就是x。
●当x为偶数,却不为2的m次方的形式时,可以写作x= y * (2k)。其中,y的最低位为1。实际上就是把x用一个奇数左移k位来表示。这时,x的二进制表示最右边有k个0,从右往左第k+1位为1。当对x取反时,最右边的k位0变成1,第k+1位变为0;再加1,最右边的k位就又变成了0,第k+1位因为进位的关系变成了1。左边的位因为没有进位,正好和x原来对应的位上的值相反。二者按位与,得到:第k+1位上为1,左边右边都为0。结果为2k。
总结一下:x&(-x),当x为0时结果为0;x为奇数时,结果为1;x为偶数时,结果为x中2的最大次方的因子。
而且这个有一个专门的称呼,叫做lowbit,即取2^k。
参考链接
面试题 10.10. 数字流的秩 - 力扣(LeetCode) (leetcode-cn.com)
差分数组本差 - 数字流的秩 - 力扣(LeetCode) (leetcode-cn.com)
写了三行代码直接过了。。。一脸懵 - 数字流的秩 - 力扣(LeetCode) (leetcode-cn.com)
[树状数组][c++] 树状数组:单点修改,区间查询 - 数字流的秩 - 力扣(LeetCode) (leetcode-cn.com)
树状数组套模板 - 数字流的秩 - 力扣(LeetCode) (leetcode-cn.com)
树状数组详解 - Xenny - 博客园 (cnblogs.com)
【面试题 10.10 数字流的秩】【二分 + 插入排序】【树状数组】Java简单实现 - 数字流的秩 - 力扣(LeetCode) (leetcode-cn.com)