数据结构与算法之LeetCode-10.10-数字流的秩(树状数组,什么是树状数组)

欢迎查看和关注一个开源的个人学习计算机科学知识成长记录(前后端,数据结构与算法)

面试题 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之类的

树状结构是怎样的

数据结构与算法之LeetCode-10.10-数字流的秩(树状数组,什么是树状数组)_第1张图片

黑色数组代表原来的数组(下面用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)

你可能感兴趣的:(数据结构,leetcode,剑指Offer,leetcode,算法,数据结构)