树状数组Binary Indexed Tree及相关LeetCode题目

关于我的 Leetcode 题目解答,代码前往 Github:https://github.com/chenxiangcyr/leetcode-answers


问题提出

有一个数组 nums[0 . . . n-1],我们希望能提供如下两个功能:

  • 功能1:求出前 i 个元素的和 sum
  • 功能2:改变某个元素的值,即nums[i] = x

可以看出,功能1的时间复杂度为O(n),功能2的时间复杂度为O(1)
当然,为了改进功能1的时间复杂度,我们可以新建一个数组int[] sum,用于记录前 i 个元素的和,即 sum[i] = nums[0] + nums[1] + ... + nums[i]。但是此时,功能2的时间复杂度变为了O(n),因为需要去维护数组 sum

LeetCode题目:307. Range Sum Query - Mutable
Given an integer array nums, find the sum of the elements between indices i and j (i ≤ j), inclusive.
The update(i, val) function modifies nums by updating the element at index i to val.

Example:
Given nums = [1, 3, 5]
sumRange(0, 2) -> 9
update(1, 2)
sumRange(0, 2) -> 8
Note:
The array is only modifiable by the update function.
You may assume the number of calls to update and sumRange function is distributed evenly.

如何优化

是否可以在 O(log n) 的时间内完成上述的两个功能?
一种方法是使用Segment Tree 线段树,具体参见Segment Tree 线段树 原理及实现

还有一种方法是使用树状数组Binary Indexed Tree。对比Segment Tree 线段树,它的优势是使用空间更小,更容易实现。

树状数组Binary Indexed Tree

使用数组存储,假设为 BITree[],每一个元素存储的是原数组 arr[]某些元素的和

一个特殊的位运算:
获取数字 A 的最低有效位:A & -A 或者 A & ~(A - 1)
例如数字 6(110)的最低位对应 2(10)

清除数字 A 的最低有效位:A = A - (A & -A) 或者 A = A - (A & ~(A - 1))
例如数字 6(110)的最低位对应 2(10),清除后得到 4(100)

更多请参见 Bit Manipulation 位运算常见套路及相应LeetCode算法题

基本思想

每个整数都能表示为一些2的幂次方的和,比如13,其二进制表示为1101,所以它能表示为:
13 = 1 + 4 + 8 = 2^0 + 2^2 + 2^3 = 1 + 100 + 1000
类似的,累积和可表示为其子集合之和。

i记为BITree的索引(从1开始),r记为i的二进制表示中最右边的1后面0的个数, 则BITree[i]记为数组中, 索引从(i-2^r +1)i的所有数的和,包含nums[i-2^r +1]nums[i]
例如:
i = 12 (1100), r = 2, i-2^r +1 = 9, BITree[12] = 第9个元素+...+第12个元素 = nums[8]+...+nums[11]
i = 13 (1101), r = 0, i-2^r +1 = 13, BITree[13] = 第13个元素 = nums[12]
如图所示:

image

可以看出,如果要求前13个元素的和:
sum(13) = BITree[13]+BITree[12]+BITree[8] = BITree[1101]+BITree[1100]+BITree[1000]

可以看出,如果要更新第3个元素的值,会影响到三个BITree元素:

  • BITree[3],即BITree[11]
  • BITree[4],即BITree[11 + 1 = 100]
  • BITree[8],即BITree[100 + 100 = 1000]
image

求出前 i 个元素的和

    // 求出前 `i` 个元素的和
    public int sum(int i) {
        int sum = 0;
        
        i++;
        while(i > 0) {
            sum = sum + BITree[i];
            i = i - (i & -i);
        }
        
        return sum;
    }

求出区间元素的和

    public int sumRange(int i, int j) {
        return sum(j) - sum(i - 1);
    }

改变某个元素的值,即nums[i] = x

    // 改变某个元素的值
    public void update(int i, int val) {
        int diff = val - nums[i];
        
        nums[i] = val;
        
        updateBIT(i, diff);
    }
    
    // 更新树状数组Binary Indexed Tree
    public void updateBIT(int i, int val) {
        i++;
        while(i <= n) {
            BITree[i] = BITree[i] + val;
            i = i + (i & -i);
        }
    }

Binary Indexed Tree的构造

    // 树状数组Binary Indexed Tree
    int[] BITree;
    
    int[] nums;
    
    int n;
    
    public NumArray(int[] nums) {
        this.nums = nums;
        this.n = nums.length;
        
        // 初始化树状数组Binary Indexed Tree
        // 索引都从1开始
        this.BITree = new int[n + 1];
        Arrays.fill(BITree, 0);
        
        for(int i = 0; i < n; i++) {
            updateBIT(i, nums[i]);
        }
    }

二维树状数组Binary Indexed Tree

LeetCode题目:308. Range Sum Query 2D - Mutable
Given a 2D matrix matrix, find the sum of the elements inside the rectangle defined by its upper left corner (row1, col1) and lower right corner (row2, col2).

Range Sum Query 2D

The above rectangle (with the red border) is defined by (row1, col1) = (2, 1) and (row2, col2) = (4, 3), which contains sum = 8.
Example:

Given matrix = [
[3, 0, 1, 4, 2],
[5, 6, 3, 2, 1],
[1, 2, 0, 1, 5],
[4, 1, 0, 1, 7],
[1, 0, 3, 0, 5]
]
sumRegion(2, 1, 4, 3) -> 8
update(3, 2, 2)
sumRegion(2, 1, 4, 3) -> 10

Note:

  1. The matrix is only modifiable by the update function.
  2. You may assume the number of calls to update and sumRegion function is distributed evenly.
  3. You may assume that row1 ≤ row2 and col1 ≤ col2.
class NumMatrix {
    int[][] BITree;
    int[][] nums;
    int m;
    int n;
    
    public NumMatrix(int[][] matrix) {
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) return;
        
        m = matrix.length;
        n = matrix[0].length;
        
        BITree = new int[m + 1][n + 1];
        nums = new int[m][n];
        
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                update(i, j, matrix[i][j]);
            }
        }
    }
    
    public void update(int row, int col, int val) {
        if (m == 0 || n == 0) return;
        
        int delta = val - nums[row][col];
        
        nums[row][col] = val;
        
        int i = row + 1;
        while(i <= m) {
            
            int j = col + 1;
            while(j <= n) {
                BITree[i][j] += delta;
                
                j += j & (-j);
            }
            
            i += i & (-i);
        }
    }
    
    public int sumRegion(int row1, int col1, int row2, int col2) {
        if (m == 0 || n == 0) return 0;
        
        return sum(row2, col2) + sum(row1 - 1, col1 - 1) - sum(row1 - 1, col2) - sum(row2, col1 - 1);
    }
    
    public int sum(int row, int col) {
        int sum = 0;
        
        int i = row + 1;
        while(i > 0) {
            
            int j = col + 1;
            while(j > 0) {
                sum = sum + BITree[i][j];
                
                j -= j & (-j);
            }
            
            i -= i & (-i);
        }
        
        return sum;
    }
}

/**
 * Your NumMatrix object will be instantiated and called as such:
 * NumMatrix obj = new NumMatrix(matrix);
 * obj.update(row,col,val);
 * int param_2 = obj.sumRegion(row1,col1,row2,col2);
 */

树状数组Binary Indexed Tree的扩展

树状数组Binary Indexed Tree不光可以用于求和,还可以求频率。
LeetCode题目:493. Reverse Pairs
Given an array nums, we call (i, j) an important reverse pair if i < j and nums[i] > 2*nums[j].
You need to return the number of important reverse pairs in the given array.

Example1:
Input: [1,3,2,3,1]
Output: 2

Example2:
Input: [2,4,3,5,1]
Output: 3

class Solution {
    private int search(int[] BITree, int i) {
        int sum = 0;

        while (i < BITree.length) {
            sum = sum + BITree[i];
            i = i + (i & -i);
        }

        return sum;
    }

    private void insert(int[] BITree, int i) {
        while (i > 0) {
            BITree[i] += 1;
            i = i - (i & -i);
        }
    }
    
    public int reversePairs(int[] nums) {
        int res = 0;
        int[] copy = Arrays.copyOf(nums, nums.length);
        int[] BITree = new int[copy.length + 1];

        Arrays.sort(copy);

        for (int num : nums) {
            // 先查找在之前的数字中,有多少个val大于等于nums[i] * 2l + 1的数字
            res += search(BITree, index(copy, 2L * num + 1));
            
            // 再插入该数字
            insert(BITree, index(copy, num));
        }

        return res;
    }

    // 在一个有序数组中搜索
    // For each element, the "index" function will return its index in the BIT.
    // Unlike the BST-based solution, this is guaranteed to run at O(nlogn)
    private int index(int[] arr, long val) {
        int l = 0, r = arr.length - 1, m = 0;

        while (l <= r) {
            m = l + ((r - l) >> 1);

            if (arr[m] >= val) {
                r = m - 1;
            } else {
                l = m + 1;
            }
        }

        return l + 1;
    }
}

引用:
树状数组(Binary Indexed Trees)
Binary Indexed Tree or Fenwick Tree

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容